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 118 json_object_array_add( 119 section_descriptors_ir, 120 cper_section_descriptor_to_ir(section_descriptor)); 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 json_object_array_add(sections_ir, section_ir); 127 } 128 129 //Add the header, section descriptors, and sections to a parent object. 130 json_object_object_add(parent, "sectionDescriptors", 131 section_descriptors_ir); 132 json_object_object_add(parent, "sections", sections_ir); 133 134 return parent; 135 136 fail: 137 json_object_put(sections_ir); 138 json_object_put(section_descriptors_ir); 139 json_object_put(parent); 140 cper_print_log("Failed to parse CPER file.\n"); 141 return NULL; 142 } 143 144 //Reads a CPER log file at the given file location, and returns an intermediate 145 //JSON representation of this CPER record. 146 json_object *cper_to_ir(FILE *cper_file) 147 { 148 //Ensure this is really a CPER log. 149 EFI_COMMON_ERROR_RECORD_HEADER header; 150 if (fread(&header, sizeof(EFI_COMMON_ERROR_RECORD_HEADER), 1, 151 cper_file) != 1) { 152 cper_print_log( 153 "Invalid CPER file: Invalid length (log too short).\n"); 154 return NULL; 155 } 156 157 //Check if the header contains the magic bytes ("CPER"). 158 if (header.SignatureStart != EFI_ERROR_RECORD_SIGNATURE_START) { 159 cper_print_log( 160 "Invalid CPER file: Invalid header (incorrect signature).\n"); 161 return NULL; 162 } 163 fseek(cper_file, -sizeof(EFI_COMMON_ERROR_RECORD_HEADER), SEEK_CUR); 164 unsigned char *cper_buf = malloc(header.RecordLength); 165 if (fread(cper_buf, header.RecordLength, 1, cper_file) != 1) { 166 cper_print_log("File read failed\n"); 167 free(cper_buf); 168 return NULL; 169 } 170 171 json_object *ir = cper_buf_to_ir(cper_buf, header.RecordLength); 172 free(cper_buf); 173 return ir; 174 } 175 176 char *cper_to_str_ir(FILE *cper_file) 177 { 178 json_object *jobj = cper_to_ir(cper_file); 179 char *str = jobj ? strdup(json_object_to_json_string(jobj)) : NULL; 180 181 json_object_put(jobj); 182 return str; 183 } 184 185 char *cperbuf_to_str_ir(const unsigned char *cper, size_t size) 186 { 187 FILE *cper_file = fmemopen((void *)cper, size, "r"); 188 189 return cper_file ? cper_to_str_ir(cper_file) : NULL; 190 } 191 192 //Converts a parsed CPER record header into intermediate JSON object format. 193 json_object *cper_header_to_ir(EFI_COMMON_ERROR_RECORD_HEADER *header) 194 { 195 json_object *header_ir = json_object_new_object(); 196 197 //Revision/version information. 198 json_object_object_add(header_ir, "revision", 199 revision_to_ir(header->Revision)); 200 201 //Section count. 202 json_object_object_add(header_ir, "sectionCount", 203 json_object_new_int(header->SectionCount)); 204 205 //Error severity (with interpreted string version). 206 json_object *error_severity = json_object_new_object(); 207 json_object_object_add(error_severity, "code", 208 json_object_new_uint64(header->ErrorSeverity)); 209 json_object_object_add(error_severity, "name", 210 json_object_new_string(severity_to_string( 211 header->ErrorSeverity))); 212 json_object_object_add(header_ir, "severity", error_severity); 213 214 //Total length of the record (including headers) in bytes. 215 json_object_object_add(header_ir, "recordLength", 216 json_object_new_uint64(header->RecordLength)); 217 218 //If a timestamp exists according to validation bits, then add it. 219 if (header->ValidationBits & 0x2) { 220 char timestamp_string[TIMESTAMP_LENGTH]; 221 if (timestamp_to_string(timestamp_string, TIMESTAMP_LENGTH, 222 &header->TimeStamp) < 0) { 223 goto fail; 224 } 225 json_object_object_add( 226 header_ir, "timestamp", 227 json_object_new_string(timestamp_string)); 228 229 json_object_object_add( 230 header_ir, "timestampIsPrecise", 231 json_object_new_boolean(header->TimeStamp.Flag)); 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 fail: 303 json_object_put(header_ir); 304 return NULL; 305 } 306 307 //Converts the given EFI section descriptor into JSON IR format. 308 json_object * 309 cper_section_descriptor_to_ir(EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor) 310 { 311 json_object *section_descriptor_ir = json_object_new_object(); 312 313 //The offset of the section from the base of the record header, length. 314 json_object_object_add( 315 section_descriptor_ir, "sectionOffset", 316 json_object_new_uint64(section_descriptor->SectionOffset)); 317 json_object_object_add( 318 section_descriptor_ir, "sectionLength", 319 json_object_new_uint64(section_descriptor->SectionLength)); 320 321 //Revision. 322 json_object_object_add(section_descriptor_ir, "revision", 323 revision_to_ir(section_descriptor->Revision)); 324 325 //Flag bits. 326 json_object *flags = 327 bitfield_to_ir(section_descriptor->SectionFlags, 8, 328 CPER_SECTION_DESCRIPTOR_FLAGS_BITFIELD_NAMES); 329 json_object_object_add(section_descriptor_ir, "flags", flags); 330 331 //Section type (GUID). 332 json_object *section_type = json_object_new_object(); 333 334 add_guid(section_type, "data", §ion_descriptor->SectionType); 335 //Readable section type, if possible. 336 const char *section_type_readable = "Unknown"; 337 338 CPER_SECTION_DEFINITION *section = 339 select_section_by_guid(§ion_descriptor->SectionType); 340 if (section != NULL) { 341 section_type_readable = section->ReadableName; 342 } 343 344 json_object_object_add(section_type, "type", 345 json_object_new_string(section_type_readable)); 346 json_object_object_add(section_descriptor_ir, "sectionType", 347 section_type); 348 349 //If validation bits indicate it exists, add FRU ID. 350 if (section_descriptor->SecValidMask & 0x1) { 351 add_guid(section_descriptor_ir, "fruID", 352 §ion_descriptor->FruId); 353 } 354 355 //If validation bits indicate it exists, add FRU text. 356 if ((section_descriptor->SecValidMask & 0x2) >> 1) { 357 int fru_text_len = 0; 358 for (; 359 fru_text_len < (int)sizeof(section_descriptor->FruString); 360 fru_text_len++) { 361 char c = section_descriptor->FruString[fru_text_len]; 362 if (c < 0) { 363 //cper_print_log("Fru text contains non-ASCII character\n"); 364 fru_text_len = -1; 365 break; 366 } 367 if (c == '\0') { 368 break; 369 } 370 } 371 if (fru_text_len >= 0) { 372 json_object_object_add( 373 section_descriptor_ir, "fruText", 374 json_object_new_string_len( 375 section_descriptor->FruString, 376 fru_text_len)); 377 } 378 } 379 380 //Section severity. 381 json_object *section_severity = json_object_new_object(); 382 json_object_object_add( 383 section_severity, "code", 384 json_object_new_uint64(section_descriptor->Severity)); 385 json_object_object_add(section_severity, "name", 386 json_object_new_string(severity_to_string( 387 section_descriptor->Severity))); 388 json_object_object_add(section_descriptor_ir, "severity", 389 section_severity); 390 391 return section_descriptor_ir; 392 } 393 394 json_object *read_section(const unsigned char *cper_section_buf, size_t size, 395 CPER_SECTION_DEFINITION *definition) 396 { 397 if (definition->ToIR == NULL) { 398 return NULL; 399 } 400 json_object *section_ir = definition->ToIR(cper_section_buf, size); 401 json_object *result = json_object_new_object(); 402 json_object_object_add(result, definition->ShortName, section_ir); 403 return result; 404 } 405 406 CPER_SECTION_DEFINITION *select_section_by_guid(EFI_GUID *guid) 407 { 408 size_t i = 0; 409 for (; i < section_definitions_len; i++) { 410 if (guid_equal(guid, section_definitions[i].Guid)) { 411 break; 412 } 413 } 414 // It's unlikely fuzzing can reliably come up with a correct guid, given how 415 // much entropy there is. If we're in fuzzing mode, and if we haven't found 416 // a match, try to force a match so we get some coverage. Note, we still 417 // want coverage of the section failed to convert code, so treat index == 418 // size as section failed to convert. 419 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 420 if (i == section_definitions_len) { 421 i = guid->Data1 % (section_definitions_len + 1); 422 } 423 #endif 424 if (i < section_definitions_len) { 425 return §ion_definitions[i]; 426 } 427 428 return NULL; 429 } 430 431 //Converts the section described by a single given section descriptor. 432 json_object *cper_buf_section_to_ir(const void *cper_section_buf, size_t size, 433 EFI_ERROR_SECTION_DESCRIPTOR *descriptor) 434 { 435 if (descriptor->SectionLength > size) { 436 cper_print_log( 437 "Invalid CPER file: Invalid header (incorrect signature).\n"); 438 return NULL; 439 } 440 441 //Parse section to IR based on GUID. 442 json_object *result = NULL; 443 444 json_object *section_ir = NULL; 445 446 CPER_SECTION_DEFINITION *section = 447 select_section_by_guid(&descriptor->SectionType); 448 if (section != NULL) { 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("Failed to allocate encode output buffer. \n"); 461 } else { 462 section_ir = json_object_new_object(); 463 json_object_object_add(section_ir, "data", 464 json_object_new_string_len( 465 encoded, encoded_len)); 466 free(encoded); 467 468 result = json_object_new_object(); 469 json_object_object_add(result, "Unknown", section_ir); 470 } 471 } 472 473 return result; 474 } 475 476 json_object *cper_buf_single_section_to_ir(const unsigned char *cper_buf, 477 size_t size) 478 { 479 const unsigned char *cper_end; 480 const unsigned char *section_begin; 481 json_object *ir; 482 483 cper_end = cper_buf + size; 484 485 //Read the section descriptor out. 486 EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor; 487 if (sizeof(EFI_ERROR_SECTION_DESCRIPTOR) > size) { 488 cper_print_log( 489 "Size of cper buffer was too small to read section descriptor %zu\n", 490 size); 491 return NULL; 492 } 493 494 ir = json_object_new_object(); 495 section_descriptor = (EFI_ERROR_SECTION_DESCRIPTOR *)cper_buf; 496 //Convert the section descriptor to IR. 497 json_object *section_descriptor_ir = 498 cper_section_descriptor_to_ir(section_descriptor); 499 json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir); 500 section_begin = cper_buf + section_descriptor->SectionOffset; 501 502 if (section_begin + section_descriptor->SectionLength >= cper_end) { 503 json_object_put(ir); 504 //cper_print_log("Invalid CPER file: Invalid section descriptor (section offset + length > size).\n"); 505 return NULL; 506 } 507 508 const unsigned char *section = 509 cper_buf + section_descriptor->SectionOffset; 510 511 //Parse the single section. 512 json_object *section_ir = cper_buf_section_to_ir( 513 section, section_descriptor->SectionLength, section_descriptor); 514 json_object_object_add(ir, "section", section_ir); 515 return ir; 516 } 517 518 //Converts a single CPER section, without a header but with a section descriptor, to JSON. 519 json_object *cper_single_section_to_ir(FILE *cper_section_file) 520 { 521 json_object *ir = json_object_new_object(); 522 523 //Read the current file pointer location as base record position. 524 long base_pos = ftell(cper_section_file); 525 526 //Read the section descriptor out. 527 EFI_ERROR_SECTION_DESCRIPTOR section_descriptor; 528 if (fread(§ion_descriptor, sizeof(EFI_ERROR_SECTION_DESCRIPTOR), 1, 529 cper_section_file) != 1) { 530 cper_print_log( 531 "Failed to read section descriptor for CPER single section (fread() returned an unexpected value).\n"); 532 json_object_put(ir); 533 return NULL; 534 } 535 536 //Convert the section descriptor to IR. 537 json_object *section_descriptor_ir = 538 cper_section_descriptor_to_ir(§ion_descriptor); 539 json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir); 540 541 //Save our current position in the stream. 542 long position = ftell(cper_section_file); 543 544 //Read section as described by the section descriptor. 545 fseek(cper_section_file, base_pos + section_descriptor.SectionOffset, 546 SEEK_SET); 547 void *section = malloc(section_descriptor.SectionLength); 548 if (fread(section, section_descriptor.SectionLength, 1, 549 cper_section_file) != 1) { 550 cper_print_log( 551 "Section read failed: Could not read %u bytes from global offset %d.\n", 552 section_descriptor.SectionLength, 553 section_descriptor.SectionOffset); 554 json_object_put(ir); 555 free(section); 556 return NULL; 557 } 558 559 //Seek back to our original position. 560 fseek(cper_section_file, position, SEEK_SET); 561 562 //Parse the single section. 563 json_object *section_ir = cper_buf_section_to_ir( 564 section, section_descriptor.SectionLength, §ion_descriptor); 565 json_object_object_add(ir, "section", section_ir); 566 free(section); 567 return ir; 568 } 569 570 char *cperbuf_single_section_to_str_ir(const unsigned char *cper_section, 571 size_t size) 572 { 573 json_object *jobj = cper_buf_single_section_to_ir(cper_section, size); 574 char *str = jobj ? strdup(json_object_to_json_string(jobj)) : NULL; 575 576 json_object_put(jobj); 577 return str; 578 } 579