1 /** 2 * Describes high level functions for converting an entire CPER log, and functions for parsing 3 * CPER headers and section descriptions into an intermediate JSON format. 4 * 5 * Author: Lawrence.Tang@arm.com 6 **/ 7 8 #include <limits.h> 9 #include <stdio.h> 10 #include <string.h> 11 #include <json.h> 12 13 #include <libcper/base64.h> 14 #include <libcper/Cper.h> 15 #include <libcper/log.h> 16 #include <libcper/cper-parse.h> 17 #include <libcper/cper-parse-str.h> 18 #include <libcper/cper-utils.h> 19 #include <libcper/sections/cper-section.h> 20 21 //Private pre-definitions. 22 json_object *cper_header_to_ir(EFI_COMMON_ERROR_RECORD_HEADER *header); 23 json_object * 24 cper_section_descriptor_to_ir(EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor); 25 26 json_object *cper_buf_section_to_ir(const void *cper_section_buf, size_t size, 27 EFI_ERROR_SECTION_DESCRIPTOR *descriptor); 28 29 json_object *cper_buf_to_ir(const unsigned char *cper_buf, size_t size) 30 { 31 json_object *parent = NULL; 32 json_object *header_ir = NULL; 33 json_object *section_descriptors_ir = NULL; 34 json_object *sections_ir = NULL; 35 36 const unsigned char *pos = cper_buf; 37 unsigned int remaining = size; 38 39 if (remaining < sizeof(EFI_COMMON_ERROR_RECORD_HEADER)) { 40 cper_print_log( 41 "Invalid CPER file: Invalid header (incorrect signature).\n"); 42 goto fail; 43 } 44 45 EFI_COMMON_ERROR_RECORD_HEADER *header = NULL; 46 header = (EFI_COMMON_ERROR_RECORD_HEADER *)cper_buf; 47 pos += sizeof(EFI_COMMON_ERROR_RECORD_HEADER); 48 remaining -= sizeof(EFI_COMMON_ERROR_RECORD_HEADER); 49 if (header->SignatureStart != EFI_ERROR_RECORD_SIGNATURE_START) { 50 cper_print_log( 51 "Invalid CPER file: Invalid header (incorrect signature).\n"); 52 goto fail; 53 } 54 if (header->SectionCount == 0) { 55 cper_print_log( 56 "Invalid CPER file: Invalid section count (0).\n"); 57 goto fail; 58 } 59 if (remaining < sizeof(EFI_ERROR_SECTION_DESCRIPTOR)) { 60 cper_print_log( 61 "Invalid CPER file: Invalid section descriptor (section offset + length > size).\n"); 62 goto fail; 63 } 64 65 //Create the header JSON object from the read bytes. 66 parent = json_object_new_object(); 67 header_ir = cper_header_to_ir(header); 68 69 json_object_object_add(parent, "header", header_ir); 70 71 //Read the appropriate number of section descriptors & sections, and convert them into IR format. 72 section_descriptors_ir = json_object_new_array(); 73 sections_ir = json_object_new_array(); 74 for (int i = 0; i < header->SectionCount; i++) { 75 //Create the section descriptor. 76 if (remaining < sizeof(EFI_ERROR_SECTION_DESCRIPTOR)) { 77 cper_print_log( 78 "Invalid number of section headers: Header states %d sections, could not read section %d.\n", 79 header->SectionCount, i + 1); 80 goto fail; 81 } 82 83 EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor; 84 section_descriptor = (EFI_ERROR_SECTION_DESCRIPTOR *)(pos); 85 pos += sizeof(EFI_ERROR_SECTION_DESCRIPTOR); 86 remaining -= sizeof(EFI_ERROR_SECTION_DESCRIPTOR); 87 88 if (section_descriptor->SectionOffset > size) { 89 cper_print_log( 90 "Invalid section descriptor: Section offset > size.\n"); 91 goto fail; 92 } 93 94 if (section_descriptor->SectionLength <= 0) { 95 cper_print_log( 96 "Invalid section descriptor: Section length <= 0.\n"); 97 goto fail; 98 } 99 100 if (section_descriptor->SectionOffset > 101 UINT_MAX - section_descriptor->SectionLength) { 102 cper_print_log( 103 "Invalid section descriptor: Section offset + length would overflow.\n"); 104 goto fail; 105 } 106 107 if (section_descriptor->SectionOffset + 108 section_descriptor->SectionLength > 109 size) { 110 cper_print_log( 111 "Invalid section descriptor: Section offset + length > size.\n"); 112 goto fail; 113 } 114 115 const unsigned char *section_begin = 116 cper_buf + section_descriptor->SectionOffset; 117 json_object *section_descriptor_ir = 118 cper_section_descriptor_to_ir(section_descriptor); 119 120 json_object_array_add(section_descriptors_ir, 121 section_descriptor_ir); 122 123 //Read the section itself. 124 json_object *section_ir = cper_buf_section_to_ir( 125 section_begin, section_descriptor->SectionLength, 126 section_descriptor); 127 json_object_array_add(sections_ir, section_ir); 128 } 129 130 //Add the header, section descriptors, and sections to a parent object. 131 json_object_object_add(parent, "sectionDescriptors", 132 section_descriptors_ir); 133 json_object_object_add(parent, "sections", sections_ir); 134 135 return parent; 136 137 fail: 138 json_object_put(sections_ir); 139 json_object_put(section_descriptors_ir); 140 json_object_put(parent); 141 cper_print_log("Failed to parse CPER file.\n"); 142 return NULL; 143 } 144 145 //Reads a CPER log file at the given file location, and returns an intermediate 146 //JSON representation of this CPER record. 147 json_object *cper_to_ir(FILE *cper_file) 148 { 149 //Ensure this is really a CPER log. 150 EFI_COMMON_ERROR_RECORD_HEADER header; 151 if (fread(&header, sizeof(EFI_COMMON_ERROR_RECORD_HEADER), 1, 152 cper_file) != 1) { 153 cper_print_log( 154 "Invalid CPER file: Invalid length (log too short).\n"); 155 return NULL; 156 } 157 158 //Check if the header contains the magic bytes ("CPER"). 159 if (header.SignatureStart != EFI_ERROR_RECORD_SIGNATURE_START) { 160 cper_print_log( 161 "Invalid CPER file: Invalid header (incorrect signature).\n"); 162 return NULL; 163 } 164 fseek(cper_file, -sizeof(EFI_COMMON_ERROR_RECORD_HEADER), SEEK_CUR); 165 unsigned char *cper_buf = malloc(header.RecordLength); 166 int bytes_read = fread(cper_buf, 1, header.RecordLength, cper_file); 167 if (bytes_read < 0) { 168 cper_print_log("File read failed with code %u\n", bytes_read); 169 free(cper_buf); 170 return NULL; 171 } 172 if ((UINT32)bytes_read != header.RecordLength) { 173 int position = ftell(cper_file); 174 cper_print_log( 175 "File read failed file was %u bytes, expecting %u bytes from header.\n", 176 position, header.RecordLength); 177 free(cper_buf); 178 return NULL; 179 } 180 181 json_object *ir = cper_buf_to_ir(cper_buf, bytes_read); 182 free(cper_buf); 183 return ir; 184 } 185 186 char *cper_to_str_ir(FILE *cper_file) 187 { 188 json_object *jobj = cper_to_ir(cper_file); 189 char *str = jobj ? strdup(json_object_to_json_string(jobj)) : NULL; 190 191 json_object_put(jobj); 192 return str; 193 } 194 195 char *cperbuf_to_str_ir(const unsigned char *cper, size_t size) 196 { 197 FILE *cper_file = fmemopen((void *)cper, size, "r"); 198 199 return cper_file ? cper_to_str_ir(cper_file) : NULL; 200 } 201 202 //Converts a parsed CPER record header into intermediate JSON object format. 203 json_object *cper_header_to_ir(EFI_COMMON_ERROR_RECORD_HEADER *header) 204 { 205 json_object *header_ir = json_object_new_object(); 206 207 //Revision/version information. 208 json_object_object_add(header_ir, "revision", 209 revision_to_ir(header->Revision)); 210 211 //Section count. 212 json_object_object_add(header_ir, "sectionCount", 213 json_object_new_int(header->SectionCount)); 214 215 //Error severity (with interpreted string version). 216 json_object *error_severity = json_object_new_object(); 217 json_object_object_add(error_severity, "code", 218 json_object_new_uint64(header->ErrorSeverity)); 219 json_object_object_add(error_severity, "name", 220 json_object_new_string(severity_to_string( 221 header->ErrorSeverity))); 222 json_object_object_add(header_ir, "severity", error_severity); 223 224 //Total length of the record (including headers) in bytes. 225 json_object_object_add(header_ir, "recordLength", 226 json_object_new_uint64(header->RecordLength)); 227 228 //If a timestamp exists according to validation bits, then add it. 229 if (header->ValidationBits & 0x2) { 230 char timestamp_string[TIMESTAMP_LENGTH]; 231 if (timestamp_to_string(timestamp_string, TIMESTAMP_LENGTH, 232 &header->TimeStamp) >= 0) { 233 json_object_object_add( 234 header_ir, "timestamp", 235 json_object_new_string(timestamp_string)); 236 237 json_object_object_add(header_ir, "timestampIsPrecise", 238 json_object_new_boolean( 239 header->TimeStamp.Flag)); 240 } 241 } 242 243 //If a platform ID exists according to the validation bits, then add it. 244 if (header->ValidationBits & 0x1) { 245 add_guid(header_ir, "platformID", &header->PlatformID); 246 } 247 248 //If a partition ID exists according to the validation bits, then add it. 249 if (header->ValidationBits & 0x4) { 250 add_guid(header_ir, "partitionID", &header->PartitionID); 251 } 252 253 //Creator ID of the header. 254 add_guid(header_ir, "creatorID", &header->CreatorID); 255 //Notification type for the header. Some defined types are available. 256 json_object *notification_type = json_object_new_object(); 257 add_guid(notification_type, "guid", &header->NotificationType); 258 259 //Add the human readable notification type if possible. 260 const char *notification_type_readable = "Unknown"; 261 262 EFI_GUID *guids[] = { 263 &gEfiEventNotificationTypeCmcGuid, 264 &gEfiEventNotificationTypeCpeGuid, 265 &gEfiEventNotificationTypeMceGuid, 266 &gEfiEventNotificationTypePcieGuid, 267 &gEfiEventNotificationTypeInitGuid, 268 &gEfiEventNotificationTypeNmiGuid, 269 &gEfiEventNotificationTypeBootGuid, 270 &gEfiEventNotificationTypeDmarGuid, 271 &gEfiEventNotificationTypeSeaGuid, 272 &gEfiEventNotificationTypeSeiGuid, 273 &gEfiEventNotificationTypePeiGuid, 274 &gEfiEventNotificationTypeCxlGuid, 275 }; 276 277 const char *readable_names[] = { 278 "CMC", "CPE", "MCE", "PCIe", "INIT", "NMI", 279 "Boot", "DMAr", "SEA", "SEI", "PEI", "CXL Component" 280 }; 281 282 int index = select_guid_from_list(&header->NotificationType, guids, 283 sizeof(guids) / sizeof(EFI_GUID *)); 284 if (index < (int)(sizeof(readable_names) / sizeof(char *))) { 285 notification_type_readable = readable_names[index]; 286 } 287 288 json_object_object_add( 289 notification_type, "type", 290 json_object_new_string(notification_type_readable)); 291 json_object_object_add(header_ir, "notificationType", 292 notification_type); 293 294 //The record ID for this record, unique on a given system. 295 json_object_object_add(header_ir, "recordID", 296 json_object_new_uint64(header->RecordID)); 297 298 //Flag for the record, and a human readable form. 299 json_object *flags = integer_to_readable_pair( 300 header->Flags, 301 sizeof(CPER_HEADER_FLAG_TYPES_KEYS) / sizeof(int), 302 CPER_HEADER_FLAG_TYPES_KEYS, CPER_HEADER_FLAG_TYPES_VALUES, 303 "Unknown"); 304 json_object_object_add(header_ir, "flags", flags); 305 306 //Persistence information. Outside the scope of specification, so just a uint32 here. 307 json_object_object_add(header_ir, "persistenceInfo", 308 json_object_new_uint64(header->PersistenceInfo)); 309 return header_ir; 310 } 311 312 //Converts the given EFI section descriptor into JSON IR format. 313 json_object * 314 cper_section_descriptor_to_ir(EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor) 315 { 316 json_object *section_descriptor_ir = json_object_new_object(); 317 318 //The offset of the section from the base of the record header, length. 319 json_object_object_add( 320 section_descriptor_ir, "sectionOffset", 321 json_object_new_uint64(section_descriptor->SectionOffset)); 322 json_object_object_add( 323 section_descriptor_ir, "sectionLength", 324 json_object_new_uint64(section_descriptor->SectionLength)); 325 326 //Revision. 327 json_object_object_add(section_descriptor_ir, "revision", 328 revision_to_ir(section_descriptor->Revision)); 329 330 //Flag bits. 331 json_object *flags = 332 bitfield_to_ir(section_descriptor->SectionFlags, 8, 333 CPER_SECTION_DESCRIPTOR_FLAGS_BITFIELD_NAMES); 334 json_object_object_add(section_descriptor_ir, "flags", flags); 335 336 //Section type (GUID). 337 json_object *section_type = json_object_new_object(); 338 339 add_guid(section_type, "data", §ion_descriptor->SectionType); 340 //Readable section type, if possible. 341 const char *section_type_readable = "Unknown"; 342 343 CPER_SECTION_DEFINITION *section = 344 select_section_by_guid(§ion_descriptor->SectionType); 345 if (section != NULL) { 346 section_type_readable = section->ReadableName; 347 } 348 349 json_object_object_add(section_type, "type", 350 json_object_new_string(section_type_readable)); 351 json_object_object_add(section_descriptor_ir, "sectionType", 352 section_type); 353 354 //If validation bits indicate it exists, add FRU ID. 355 if (section_descriptor->SecValidMask & 0x1) { 356 add_guid(section_descriptor_ir, "fruID", 357 §ion_descriptor->FruId); 358 } 359 360 //If validation bits indicate it exists, add FRU text. 361 if ((section_descriptor->SecValidMask & 0x2) >> 1) { 362 add_untrusted_string(section_descriptor_ir, "fruText", 363 section_descriptor->FruString, 364 sizeof(section_descriptor->FruString)); 365 } 366 367 //Section severity. 368 json_object *section_severity = json_object_new_object(); 369 json_object_object_add( 370 section_severity, "code", 371 json_object_new_uint64(section_descriptor->Severity)); 372 json_object_object_add(section_severity, "name", 373 json_object_new_string(severity_to_string( 374 section_descriptor->Severity))); 375 json_object_object_add(section_descriptor_ir, "severity", 376 section_severity); 377 378 return section_descriptor_ir; 379 } 380 381 json_object *read_section(const unsigned char *cper_section_buf, size_t size, 382 CPER_SECTION_DEFINITION *definition) 383 { 384 if (definition->ToIR == NULL) { 385 return NULL; 386 } 387 json_object *section_ir = definition->ToIR(cper_section_buf, size); 388 if (section_ir == NULL) { 389 return NULL; 390 } 391 json_object *result = json_object_new_object(); 392 json_object_object_add(result, definition->ShortName, section_ir); 393 return result; 394 } 395 396 CPER_SECTION_DEFINITION *select_section_by_guid(EFI_GUID *guid) 397 { 398 size_t i = 0; 399 for (; i < section_definitions_len; i++) { 400 if (guid_equal(guid, section_definitions[i].Guid)) { 401 break; 402 } 403 } 404 // It's unlikely fuzzing can reliably come up with a correct guid, given how 405 // much entropy there is. If we're in fuzzing mode, and if we haven't found 406 // a match, try to force a match so we get some coverage. Note, we still 407 // want coverage of the section failed to convert code, so treat index == 408 // size as section failed to convert. 409 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 410 if (i == section_definitions_len) { 411 i = guid->Data1 % (section_definitions_len + 1); 412 } 413 #endif 414 if (i < section_definitions_len) { 415 return §ion_definitions[i]; 416 } 417 418 return NULL; 419 } 420 421 //Converts the section described by a single given section descriptor. 422 json_object *cper_buf_section_to_ir(const void *cper_section_buf, size_t size, 423 EFI_ERROR_SECTION_DESCRIPTOR *descriptor) 424 { 425 if (descriptor->SectionLength > size) { 426 cper_print_log( 427 "Invalid CPER file: Invalid header (incorrect signature).\n"); 428 return NULL; 429 } 430 431 //Parse section to IR based on GUID. 432 json_object *result = NULL; 433 json_object *section_ir = NULL; 434 435 CPER_SECTION_DEFINITION *section = 436 select_section_by_guid(&descriptor->SectionType); 437 if (section == NULL) { 438 cper_print_log("Unknown section type guid\n"); 439 } else { 440 result = read_section(cper_section_buf, size, section); 441 } 442 443 //Was it an unknown GUID/failed read? 444 if (result == NULL) { 445 //Output the data as formatted base64. 446 int32_t encoded_len = 0; 447 char *encoded = base64_encode(cper_section_buf, 448 descriptor->SectionLength, 449 &encoded_len); 450 if (encoded == NULL) { 451 cper_print_log( 452 "Failed to allocate encode output buffer. \n"); 453 } else { 454 section_ir = json_object_new_object(); 455 json_object_object_add(section_ir, "data", 456 json_object_new_string_len( 457 encoded, encoded_len)); 458 free(encoded); 459 460 result = json_object_new_object(); 461 json_object_object_add(result, "Unknown", section_ir); 462 } 463 } 464 if (result == NULL) { 465 cper_print_log("RETURNING NULL!! !!\n"); 466 } 467 return result; 468 } 469 470 json_object *cper_buf_single_section_to_ir(const unsigned char *cper_buf, 471 size_t size) 472 { 473 const unsigned char *cper_end; 474 const unsigned char *section_begin; 475 json_object *ir; 476 477 cper_end = cper_buf + size; 478 479 //Read the section descriptor out. 480 EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor; 481 if (sizeof(EFI_ERROR_SECTION_DESCRIPTOR) > size) { 482 cper_print_log( 483 "Size of cper buffer was too small to read section descriptor %zu\n", 484 size); 485 return NULL; 486 } 487 488 ir = json_object_new_object(); 489 section_descriptor = (EFI_ERROR_SECTION_DESCRIPTOR *)cper_buf; 490 //Convert the section descriptor to IR. 491 json_object *section_descriptor_ir = 492 cper_section_descriptor_to_ir(section_descriptor); 493 json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir); 494 section_begin = cper_buf + section_descriptor->SectionOffset; 495 496 if (section_begin + section_descriptor->SectionLength >= cper_end) { 497 json_object_put(ir); 498 //cper_print_log("Invalid CPER file: Invalid section descriptor (section offset + length > size).\n"); 499 return NULL; 500 } 501 502 const unsigned char *section = 503 cper_buf + section_descriptor->SectionOffset; 504 505 //Parse the single section. 506 json_object *section_ir = cper_buf_section_to_ir( 507 section, section_descriptor->SectionLength, section_descriptor); 508 if (section_ir == NULL) { 509 cper_print_log("RETURNING NULL2!! !!\n"); 510 } 511 json_object_object_add(ir, "section", section_ir); 512 return ir; 513 } 514 515 //Converts a single CPER section, without a header but with a section descriptor, to JSON. 516 json_object *cper_single_section_to_ir(FILE *cper_section_file) 517 { 518 json_object *ir = json_object_new_object(); 519 520 //Read the current file pointer location as base record position. 521 long base_pos = ftell(cper_section_file); 522 523 //Read the section descriptor out. 524 EFI_ERROR_SECTION_DESCRIPTOR section_descriptor; 525 if (fread(§ion_descriptor, sizeof(EFI_ERROR_SECTION_DESCRIPTOR), 1, 526 cper_section_file) != 1) { 527 cper_print_log( 528 "Failed to read section descriptor for CPER single section (fread() returned an unexpected value).\n"); 529 json_object_put(ir); 530 return NULL; 531 } 532 533 //Convert the section descriptor to IR. 534 json_object *section_descriptor_ir = 535 cper_section_descriptor_to_ir(§ion_descriptor); 536 json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir); 537 538 //Save our current position in the stream. 539 long position = ftell(cper_section_file); 540 541 //Read section as described by the section descriptor. 542 fseek(cper_section_file, base_pos + section_descriptor.SectionOffset, 543 SEEK_SET); 544 void *section = malloc(section_descriptor.SectionLength); 545 if (fread(section, section_descriptor.SectionLength, 1, 546 cper_section_file) != 1) { 547 cper_print_log( 548 "Section read failed: Could not read %u bytes from global offset %d.\n", 549 section_descriptor.SectionLength, 550 section_descriptor.SectionOffset); 551 json_object_put(ir); 552 free(section); 553 return NULL; 554 } 555 556 //Seek back to our original position. 557 fseek(cper_section_file, position, SEEK_SET); 558 559 //Parse the single section. 560 json_object *section_ir = cper_buf_section_to_ir( 561 section, section_descriptor.SectionLength, §ion_descriptor); 562 json_object_object_add(ir, "section", section_ir); 563 free(section); 564 return ir; 565 } 566 567 char *cperbuf_single_section_to_str_ir(const unsigned char *cper_section, 568 size_t size) 569 { 570 json_object *jobj = cper_buf_single_section_to_ir(cper_section, size); 571 char *str = jobj ? strdup(json_object_to_json_string(jobj)) : NULL; 572 573 json_object_put(jobj); 574 return str; 575 } 576