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