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