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