xref: /openbmc/libcper/cper-parse.c (revision 50b966f7afa31fe39fda70e10eb9139cce39e025)
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 
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.
cper_to_ir(FILE * cper_file)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 
cper_to_str_ir(FILE * cper_file)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 
cperbuf_to_str_ir(const unsigned char * cper,size_t size)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.
cper_header_to_ir(EFI_COMMON_ERROR_RECORD_HEADER * header)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 *
cper_section_descriptor_to_ir(EFI_ERROR_SECTION_DESCRIPTOR * section_descriptor)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", &section_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(&section_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 			 &section_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 
read_section(const unsigned char * cper_section_buf,size_t size,CPER_SECTION_DEFINITION * definition)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 
select_section_by_guid(EFI_GUID * guid)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 &section_definitions[i];
426 	}
427 
428 	return NULL;
429 }
430 
431 //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)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 
cper_buf_single_section_to_ir(const unsigned char * cper_buf,size_t size)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.
cper_single_section_to_ir(FILE * cper_section_file)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(&section_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(&section_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, &section_descriptor);
565 	json_object_object_add(ir, "section", section_ir);
566 	free(section);
567 	return ir;
568 }
569 
cperbuf_single_section_to_str_ir(const unsigned char * cper_section,size_t size)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