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
header_signature_valid(EFI_COMMON_ERROR_RECORD_HEADER * header)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
header_valid(const char * cper_buf,size_t size)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
cper_buf_to_ir(const unsigned char * cper_buf,size_t size)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.
cper_to_ir(FILE * cper_file)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
cper_to_str_ir(FILE * cper_file)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
cperbuf_to_str_ir(const unsigned char * cper,size_t size)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.
cper_header_to_ir(EFI_COMMON_ERROR_RECORD_HEADER * header)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 *
cper_section_descriptor_to_ir(EFI_ERROR_SECTION_DESCRIPTOR * section_descriptor)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
read_section(const unsigned char * cper_section_buf,size_t size,CPER_SECTION_DEFINITION * definition)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 char *cper_description = NULL;
413
414 json_object *section_ir =
415 definition->ToIR(cper_section_buf, size, &cper_description);
416 if (section_ir == NULL) {
417 return NULL;
418 }
419 json_object *result = json_object_new_object();
420 if (cper_description != NULL) {
421 json_object_object_add(
422 result, "message",
423 json_object_new_string(cper_description));
424 free(cper_description);
425 }
426 json_object_object_add(result, definition->ShortName, section_ir);
427 return result;
428 }
429
select_section_by_guid(EFI_GUID * guid)430 CPER_SECTION_DEFINITION *select_section_by_guid(EFI_GUID *guid)
431 {
432 size_t i = 0;
433 for (; i < section_definitions_len; i++) {
434 if (guid_equal(guid, section_definitions[i].Guid)) {
435 break;
436 }
437 }
438 // It's unlikely fuzzing can reliably come up with a correct guid, given how
439 // much entropy there is. If we're in fuzzing mode, and if we haven't found
440 // a match, try to force a match so we get some coverage. Note, we still
441 // want coverage of the section failed to convert code, so treat index ==
442 // size as section failed to convert.
443 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
444 if (i == section_definitions_len) {
445 i = guid->Data1 % (section_definitions_len + 1);
446 }
447 #endif
448 if (i < section_definitions_len) {
449 return §ion_definitions[i];
450 }
451
452 return NULL;
453 }
454
455 //Converts the section described by a single given section descriptor.
cper_buf_section_to_ir(const void * cper_section_buf,size_t size,EFI_ERROR_SECTION_DESCRIPTOR * descriptor)456 json_object *cper_buf_section_to_ir(const void *cper_section_buf, size_t size,
457 EFI_ERROR_SECTION_DESCRIPTOR *descriptor)
458 {
459 if (descriptor->SectionLength > size) {
460 cper_print_log(
461 "Invalid CPER file: Invalid header (incorrect signature).\n");
462 return NULL;
463 }
464
465 //Parse section to IR based on GUID.
466 json_object *result = NULL;
467 json_object *section_ir = NULL;
468
469 CPER_SECTION_DEFINITION *section =
470 select_section_by_guid(&descriptor->SectionType);
471 if (section == NULL) {
472 cper_print_log("Unknown section type guid\n");
473 } else {
474 result = read_section(cper_section_buf, size, section);
475 }
476
477 //Was it an unknown GUID/failed read?
478 if (result == NULL) {
479 //Output the data as formatted base64.
480 int32_t encoded_len = 0;
481 char *encoded = base64_encode(cper_section_buf,
482 descriptor->SectionLength,
483 &encoded_len);
484 if (encoded == NULL) {
485 cper_print_log(
486 "Failed to allocate encode output buffer. \n");
487 } else {
488 section_ir = json_object_new_object();
489 json_object_object_add(section_ir, "data",
490 json_object_new_string_len(
491 encoded, encoded_len));
492 free(encoded);
493
494 result = json_object_new_object();
495 json_object_object_add(result, "Unknown", section_ir);
496 }
497 }
498 if (result == NULL) {
499 cper_print_log("RETURNING NULL!! !!\n");
500 }
501 return result;
502 }
503
cper_buf_single_section_to_ir(const unsigned char * cper_buf,size_t size)504 json_object *cper_buf_single_section_to_ir(const unsigned char *cper_buf,
505 size_t size)
506 {
507 const unsigned char *cper_end;
508 const unsigned char *section_begin;
509 json_object *ir;
510
511 cper_end = cper_buf + size;
512
513 //Read the section descriptor out.
514 EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor;
515 if (sizeof(EFI_ERROR_SECTION_DESCRIPTOR) > size) {
516 cper_print_log(
517 "Size of cper buffer was too small to read section descriptor %zu\n",
518 size);
519 return NULL;
520 }
521
522 ir = json_object_new_object();
523 section_descriptor = (EFI_ERROR_SECTION_DESCRIPTOR *)cper_buf;
524 //Convert the section descriptor to IR.
525 json_object *section_descriptor_ir =
526 cper_section_descriptor_to_ir(section_descriptor);
527 json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir);
528 section_begin = cper_buf + section_descriptor->SectionOffset;
529
530 if (section_begin + section_descriptor->SectionLength >= cper_end) {
531 json_object_put(ir);
532 //cper_print_log("Invalid CPER file: Invalid section descriptor (section offset + length > size).\n");
533 return NULL;
534 }
535
536 const unsigned char *section =
537 cper_buf + section_descriptor->SectionOffset;
538
539 //Parse the single section.
540 json_object *section_ir = cper_buf_section_to_ir(
541 section, section_descriptor->SectionLength, section_descriptor);
542 if (section_ir == NULL) {
543 cper_print_log("RETURNING NULL2!! !!\n");
544 }
545 json_object_object_add(ir, "section", section_ir);
546 return ir;
547 }
548
549 //Converts a single CPER section, without a header but with a section descriptor, to JSON.
cper_single_section_to_ir(FILE * cper_section_file)550 json_object *cper_single_section_to_ir(FILE *cper_section_file)
551 {
552 json_object *ir = json_object_new_object();
553
554 //Read the current file pointer location as base record position.
555 long base_pos = ftell(cper_section_file);
556
557 //Read the section descriptor out.
558 EFI_ERROR_SECTION_DESCRIPTOR section_descriptor;
559 if (fread(§ion_descriptor, sizeof(EFI_ERROR_SECTION_DESCRIPTOR), 1,
560 cper_section_file) != 1) {
561 cper_print_log(
562 "Failed to read section descriptor for CPER single section (fread() returned an unexpected value).\n");
563 json_object_put(ir);
564 return NULL;
565 }
566
567 //Convert the section descriptor to IR.
568 json_object *section_descriptor_ir =
569 cper_section_descriptor_to_ir(§ion_descriptor);
570 json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir);
571
572 //Save our current position in the stream.
573 long position = ftell(cper_section_file);
574
575 //Read section as described by the section descriptor.
576 fseek(cper_section_file, base_pos + section_descriptor.SectionOffset,
577 SEEK_SET);
578 void *section = malloc(section_descriptor.SectionLength);
579 if (fread(section, section_descriptor.SectionLength, 1,
580 cper_section_file) != 1) {
581 cper_print_log(
582 "Section read failed: Could not read %u bytes from global offset %d.\n",
583 section_descriptor.SectionLength,
584 section_descriptor.SectionOffset);
585 json_object_put(ir);
586 free(section);
587 return NULL;
588 }
589
590 //Seek back to our original position.
591 fseek(cper_section_file, position, SEEK_SET);
592
593 //Parse the single section.
594 json_object *section_ir = cper_buf_section_to_ir(
595 section, section_descriptor.SectionLength, §ion_descriptor);
596 json_object_object_add(ir, "section", section_ir);
597 free(section);
598 return ir;
599 }
600
cperbuf_single_section_to_str_ir(const unsigned char * cper_section,size_t size)601 char *cperbuf_single_section_to_str_ir(const unsigned char *cper_section,
602 size_t size)
603 {
604 json_object *jobj = cper_buf_single_section_to_ir(cper_section, size);
605 char *str = jobj ? strdup(json_object_to_json_string(jobj)) : NULL;
606
607 json_object_put(jobj);
608 return str;
609 }
610