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 if (fread(cper_buf, header.RecordLength, 1, cper_file) != 1) { 167 cper_print_log("File read failed\n"); 168 free(cper_buf); 169 return NULL; 170 } 171 172 json_object *ir = cper_buf_to_ir(cper_buf, header.RecordLength); 173 free(cper_buf); 174 return ir; 175 } 176 177 char *cper_to_str_ir(FILE *cper_file) 178 { 179 json_object *jobj = cper_to_ir(cper_file); 180 char *str = jobj ? strdup(json_object_to_json_string(jobj)) : NULL; 181 182 json_object_put(jobj); 183 return str; 184 } 185 186 char *cperbuf_to_str_ir(const unsigned char *cper, size_t size) 187 { 188 FILE *cper_file = fmemopen((void *)cper, size, "r"); 189 190 return cper_file ? cper_to_str_ir(cper_file) : NULL; 191 } 192 193 //Converts a parsed CPER record header into intermediate JSON object format. 194 json_object *cper_header_to_ir(EFI_COMMON_ERROR_RECORD_HEADER *header) 195 { 196 json_object *header_ir = json_object_new_object(); 197 198 //Revision/version information. 199 json_object_object_add(header_ir, "revision", 200 revision_to_ir(header->Revision)); 201 202 //Section count. 203 json_object_object_add(header_ir, "sectionCount", 204 json_object_new_int(header->SectionCount)); 205 206 //Error severity (with interpreted string version). 207 json_object *error_severity = json_object_new_object(); 208 json_object_object_add(error_severity, "code", 209 json_object_new_uint64(header->ErrorSeverity)); 210 json_object_object_add(error_severity, "name", 211 json_object_new_string(severity_to_string( 212 header->ErrorSeverity))); 213 json_object_object_add(header_ir, "severity", error_severity); 214 215 //Total length of the record (including headers) in bytes. 216 json_object_object_add(header_ir, "recordLength", 217 json_object_new_uint64(header->RecordLength)); 218 219 //If a timestamp exists according to validation bits, then add it. 220 if (header->ValidationBits & 0x2) { 221 char timestamp_string[TIMESTAMP_LENGTH]; 222 if (timestamp_to_string(timestamp_string, TIMESTAMP_LENGTH, 223 &header->TimeStamp) >= 0) { 224 json_object_object_add( 225 header_ir, "timestamp", 226 json_object_new_string(timestamp_string)); 227 228 json_object_object_add(header_ir, "timestampIsPrecise", 229 json_object_new_boolean( 230 header->TimeStamp.Flag)); 231 } 232 } 233 234 //If a platform ID exists according to the validation bits, then add it. 235 if (header->ValidationBits & 0x1) { 236 add_guid(header_ir, "platformID", &header->PlatformID); 237 } 238 239 //If a partition ID exists according to the validation bits, then add it. 240 if (header->ValidationBits & 0x4) { 241 add_guid(header_ir, "partitionID", &header->PartitionID); 242 } 243 244 //Creator ID of the header. 245 add_guid(header_ir, "creatorID", &header->CreatorID); 246 //Notification type for the header. Some defined types are available. 247 json_object *notification_type = json_object_new_object(); 248 add_guid(notification_type, "guid", &header->NotificationType); 249 250 //Add the human readable notification type if possible. 251 const char *notification_type_readable = "Unknown"; 252 253 EFI_GUID *guids[] = { 254 &gEfiEventNotificationTypeCmcGuid, 255 &gEfiEventNotificationTypeCpeGuid, 256 &gEfiEventNotificationTypeMceGuid, 257 &gEfiEventNotificationTypePcieGuid, 258 &gEfiEventNotificationTypeInitGuid, 259 &gEfiEventNotificationTypeNmiGuid, 260 &gEfiEventNotificationTypeBootGuid, 261 &gEfiEventNotificationTypeDmarGuid, 262 &gEfiEventNotificationTypeSeaGuid, 263 &gEfiEventNotificationTypeSeiGuid, 264 &gEfiEventNotificationTypePeiGuid, 265 &gEfiEventNotificationTypeCxlGuid, 266 }; 267 268 const char *readable_names[] = { 269 "CMC", "CPE", "MCE", "PCIe", "INIT", "NMI", 270 "Boot", "DMAr", "SEA", "SEI", "PEI", "CXL Component" 271 }; 272 273 int index = select_guid_from_list(&header->NotificationType, guids, 274 sizeof(guids) / sizeof(EFI_GUID *)); 275 if (index < (int)(sizeof(readable_names) / sizeof(char *))) { 276 notification_type_readable = readable_names[index]; 277 } 278 279 json_object_object_add( 280 notification_type, "type", 281 json_object_new_string(notification_type_readable)); 282 json_object_object_add(header_ir, "notificationType", 283 notification_type); 284 285 //The record ID for this record, unique on a given system. 286 json_object_object_add(header_ir, "recordID", 287 json_object_new_uint64(header->RecordID)); 288 289 //Flag for the record, and a human readable form. 290 json_object *flags = integer_to_readable_pair( 291 header->Flags, 292 sizeof(CPER_HEADER_FLAG_TYPES_KEYS) / sizeof(int), 293 CPER_HEADER_FLAG_TYPES_KEYS, CPER_HEADER_FLAG_TYPES_VALUES, 294 "Unknown"); 295 json_object_object_add(header_ir, "flags", flags); 296 297 //Persistence information. Outside the scope of specification, so just a uint32 here. 298 json_object_object_add(header_ir, "persistenceInfo", 299 json_object_new_uint64(header->PersistenceInfo)); 300 return header_ir; 301 } 302 303 //Converts the given EFI section descriptor into JSON IR format. 304 json_object * 305 cper_section_descriptor_to_ir(EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor) 306 { 307 json_object *section_descriptor_ir = json_object_new_object(); 308 309 //The offset of the section from the base of the record header, length. 310 json_object_object_add( 311 section_descriptor_ir, "sectionOffset", 312 json_object_new_uint64(section_descriptor->SectionOffset)); 313 json_object_object_add( 314 section_descriptor_ir, "sectionLength", 315 json_object_new_uint64(section_descriptor->SectionLength)); 316 317 //Revision. 318 json_object_object_add(section_descriptor_ir, "revision", 319 revision_to_ir(section_descriptor->Revision)); 320 321 //Flag bits. 322 json_object *flags = 323 bitfield_to_ir(section_descriptor->SectionFlags, 8, 324 CPER_SECTION_DESCRIPTOR_FLAGS_BITFIELD_NAMES); 325 json_object_object_add(section_descriptor_ir, "flags", flags); 326 327 //Section type (GUID). 328 json_object *section_type = json_object_new_object(); 329 330 add_guid(section_type, "data", §ion_descriptor->SectionType); 331 //Readable section type, if possible. 332 const char *section_type_readable = "Unknown"; 333 334 CPER_SECTION_DEFINITION *section = 335 select_section_by_guid(§ion_descriptor->SectionType); 336 if (section != NULL) { 337 section_type_readable = section->ReadableName; 338 } 339 340 json_object_object_add(section_type, "type", 341 json_object_new_string(section_type_readable)); 342 json_object_object_add(section_descriptor_ir, "sectionType", 343 section_type); 344 345 //If validation bits indicate it exists, add FRU ID. 346 if (section_descriptor->SecValidMask & 0x1) { 347 add_guid(section_descriptor_ir, "fruID", 348 §ion_descriptor->FruId); 349 } 350 351 //If validation bits indicate it exists, add FRU text. 352 if ((section_descriptor->SecValidMask & 0x2) >> 1) { 353 int fru_text_len = 0; 354 for (; 355 fru_text_len < (int)sizeof(section_descriptor->FruString); 356 fru_text_len++) { 357 char c = section_descriptor->FruString[fru_text_len]; 358 if (c < 0) { 359 //cper_print_log("Fru text contains non-ASCII character\n"); 360 fru_text_len = -1; 361 break; 362 } 363 if (c == '\0') { 364 break; 365 } 366 } 367 if (fru_text_len >= 0) { 368 json_object_object_add( 369 section_descriptor_ir, "fruText", 370 json_object_new_string_len( 371 section_descriptor->FruString, 372 fru_text_len)); 373 } 374 } 375 376 //Section severity. 377 json_object *section_severity = json_object_new_object(); 378 json_object_object_add( 379 section_severity, "code", 380 json_object_new_uint64(section_descriptor->Severity)); 381 json_object_object_add(section_severity, "name", 382 json_object_new_string(severity_to_string( 383 section_descriptor->Severity))); 384 json_object_object_add(section_descriptor_ir, "severity", 385 section_severity); 386 387 return section_descriptor_ir; 388 } 389 390 json_object *read_section(const unsigned char *cper_section_buf, size_t size, 391 CPER_SECTION_DEFINITION *definition) 392 { 393 if (definition->ToIR == NULL) { 394 return NULL; 395 } 396 json_object *section_ir = definition->ToIR(cper_section_buf, size); 397 if (section_ir == NULL) { 398 return NULL; 399 } 400 json_object *result = json_object_new_object(); 401 json_object_object_add(result, definition->ShortName, section_ir); 402 return result; 403 } 404 405 CPER_SECTION_DEFINITION *select_section_by_guid(EFI_GUID *guid) 406 { 407 size_t i = 0; 408 for (; i < section_definitions_len; i++) { 409 if (guid_equal(guid, section_definitions[i].Guid)) { 410 break; 411 } 412 } 413 // It's unlikely fuzzing can reliably come up with a correct guid, given how 414 // much entropy there is. If we're in fuzzing mode, and if we haven't found 415 // a match, try to force a match so we get some coverage. Note, we still 416 // want coverage of the section failed to convert code, so treat index == 417 // size as section failed to convert. 418 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 419 if (i == section_definitions_len) { 420 i = guid->Data1 % (section_definitions_len + 1); 421 } 422 #endif 423 if (i < section_definitions_len) { 424 return §ion_definitions[i]; 425 } 426 427 return NULL; 428 } 429 430 //Converts the section described by a single given section descriptor. 431 json_object *cper_buf_section_to_ir(const void *cper_section_buf, size_t size, 432 EFI_ERROR_SECTION_DESCRIPTOR *descriptor) 433 { 434 if (descriptor->SectionLength > size) { 435 cper_print_log( 436 "Invalid CPER file: Invalid header (incorrect signature).\n"); 437 return NULL; 438 } 439 440 //Parse section to IR based on GUID. 441 json_object *result = NULL; 442 json_object *section_ir = NULL; 443 444 CPER_SECTION_DEFINITION *section = 445 select_section_by_guid(&descriptor->SectionType); 446 if (section == NULL) { 447 cper_print_log("Unknown section type guid\n"); 448 } else { 449 result = read_section(cper_section_buf, size, section); 450 } 451 452 //Was it an unknown GUID/failed read? 453 if (result == NULL) { 454 //Output the data as formatted base64. 455 int32_t encoded_len = 0; 456 char *encoded = base64_encode(cper_section_buf, 457 descriptor->SectionLength, 458 &encoded_len); 459 if (encoded == NULL) { 460 cper_print_log( 461 "Failed to allocate encode output buffer. \n"); 462 } else { 463 section_ir = json_object_new_object(); 464 json_object_object_add(section_ir, "data", 465 json_object_new_string_len( 466 encoded, encoded_len)); 467 free(encoded); 468 469 result = json_object_new_object(); 470 json_object_object_add(result, "Unknown", section_ir); 471 } 472 } 473 if (result == NULL) { 474 cper_print_log("RETURNING NULL!! !!\n"); 475 } 476 return result; 477 } 478 479 json_object *cper_buf_single_section_to_ir(const unsigned char *cper_buf, 480 size_t size) 481 { 482 const unsigned char *cper_end; 483 const unsigned char *section_begin; 484 json_object *ir; 485 486 cper_end = cper_buf + size; 487 488 //Read the section descriptor out. 489 EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor; 490 if (sizeof(EFI_ERROR_SECTION_DESCRIPTOR) > size) { 491 cper_print_log( 492 "Size of cper buffer was too small to read section descriptor %zu\n", 493 size); 494 return NULL; 495 } 496 497 ir = json_object_new_object(); 498 section_descriptor = (EFI_ERROR_SECTION_DESCRIPTOR *)cper_buf; 499 //Convert the section descriptor to IR. 500 json_object *section_descriptor_ir = 501 cper_section_descriptor_to_ir(section_descriptor); 502 json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir); 503 section_begin = cper_buf + section_descriptor->SectionOffset; 504 505 if (section_begin + section_descriptor->SectionLength >= cper_end) { 506 json_object_put(ir); 507 //cper_print_log("Invalid CPER file: Invalid section descriptor (section offset + length > size).\n"); 508 return NULL; 509 } 510 511 const unsigned char *section = 512 cper_buf + section_descriptor->SectionOffset; 513 514 //Parse the single section. 515 json_object *section_ir = cper_buf_section_to_ir( 516 section, section_descriptor->SectionLength, section_descriptor); 517 if (section_ir == NULL) { 518 cper_print_log("RETURNING NULL2!! !!\n"); 519 } 520 json_object_object_add(ir, "section", section_ir); 521 return ir; 522 } 523 524 //Converts a single CPER section, without a header but with a section descriptor, to JSON. 525 json_object *cper_single_section_to_ir(FILE *cper_section_file) 526 { 527 json_object *ir = json_object_new_object(); 528 529 //Read the current file pointer location as base record position. 530 long base_pos = ftell(cper_section_file); 531 532 //Read the section descriptor out. 533 EFI_ERROR_SECTION_DESCRIPTOR section_descriptor; 534 if (fread(§ion_descriptor, sizeof(EFI_ERROR_SECTION_DESCRIPTOR), 1, 535 cper_section_file) != 1) { 536 cper_print_log( 537 "Failed to read section descriptor for CPER single section (fread() returned an unexpected value).\n"); 538 json_object_put(ir); 539 return NULL; 540 } 541 542 //Convert the section descriptor to IR. 543 json_object *section_descriptor_ir = 544 cper_section_descriptor_to_ir(§ion_descriptor); 545 json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir); 546 547 //Save our current position in the stream. 548 long position = ftell(cper_section_file); 549 550 //Read section as described by the section descriptor. 551 fseek(cper_section_file, base_pos + section_descriptor.SectionOffset, 552 SEEK_SET); 553 void *section = malloc(section_descriptor.SectionLength); 554 if (fread(section, section_descriptor.SectionLength, 1, 555 cper_section_file) != 1) { 556 cper_print_log( 557 "Section read failed: Could not read %u bytes from global offset %d.\n", 558 section_descriptor.SectionLength, 559 section_descriptor.SectionOffset); 560 json_object_put(ir); 561 free(section); 562 return NULL; 563 } 564 565 //Seek back to our original position. 566 fseek(cper_section_file, position, SEEK_SET); 567 568 //Parse the single section. 569 json_object *section_ir = cper_buf_section_to_ir( 570 section, section_descriptor.SectionLength, §ion_descriptor); 571 json_object_object_add(ir, "section", section_ir); 572 free(section); 573 return ir; 574 } 575 576 char *cperbuf_single_section_to_str_ir(const unsigned char *cper_section, 577 size_t size) 578 { 579 json_object *jobj = cper_buf_single_section_to_ir(cper_section, size); 580 char *str = jobj ? strdup(json_object_to_json_string(jobj)) : NULL; 581 582 json_object_put(jobj); 583 return str; 584 } 585