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