xref: /openbmc/libcper/cper-parse.c (revision 8870c0742a163ca85e11bd0696d6dbeb87c43f44)
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 		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.
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 
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 
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.
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 *
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", &section_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(&section_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 			 &section_descriptor->FruId);
358 	}
359 
360 	//If validation bits indicate it exists, add FRU text.
361 	if ((section_descriptor->SecValidMask & 0x2) >> 1) {
362 		add_untrusted_string(section_descriptor_ir, "fruText",
363 				     section_descriptor->FruString,
364 				     sizeof(section_descriptor->FruString));
365 	}
366 
367 	//Section severity.
368 	json_object *section_severity = json_object_new_object();
369 	json_object_object_add(
370 		section_severity, "code",
371 		json_object_new_uint64(section_descriptor->Severity));
372 	json_object_object_add(section_severity, "name",
373 			       json_object_new_string(severity_to_string(
374 				       section_descriptor->Severity)));
375 	json_object_object_add(section_descriptor_ir, "severity",
376 			       section_severity);
377 
378 	return section_descriptor_ir;
379 }
380 
381 json_object *read_section(const unsigned char *cper_section_buf, size_t size,
382 			  CPER_SECTION_DEFINITION *definition)
383 {
384 	if (definition->ToIR == NULL) {
385 		return NULL;
386 	}
387 	json_object *section_ir = definition->ToIR(cper_section_buf, size);
388 	if (section_ir == NULL) {
389 		return NULL;
390 	}
391 	json_object *result = json_object_new_object();
392 	json_object_object_add(result, definition->ShortName, section_ir);
393 	return result;
394 }
395 
396 CPER_SECTION_DEFINITION *select_section_by_guid(EFI_GUID *guid)
397 {
398 	size_t i = 0;
399 	for (; i < section_definitions_len; i++) {
400 		if (guid_equal(guid, section_definitions[i].Guid)) {
401 			break;
402 		}
403 	}
404 	// It's unlikely fuzzing can reliably come up with a correct guid, given how
405 	// much entropy there is.  If we're in fuzzing mode, and if we haven't found
406 	// a match, try to force a match so we get some coverage.  Note, we still
407 	// want coverage of the section failed to convert code, so treat index ==
408 	// size as section failed to convert.
409 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
410 	if (i == section_definitions_len) {
411 		i = guid->Data1 % (section_definitions_len + 1);
412 	}
413 #endif
414 	if (i < section_definitions_len) {
415 		return &section_definitions[i];
416 	}
417 
418 	return NULL;
419 }
420 
421 //Converts the section described by a single given section descriptor.
422 json_object *cper_buf_section_to_ir(const void *cper_section_buf, size_t size,
423 				    EFI_ERROR_SECTION_DESCRIPTOR *descriptor)
424 {
425 	if (descriptor->SectionLength > size) {
426 		cper_print_log(
427 			"Invalid CPER file: Invalid header (incorrect signature).\n");
428 		return NULL;
429 	}
430 
431 	//Parse section to IR based on GUID.
432 	json_object *result = NULL;
433 	json_object *section_ir = NULL;
434 
435 	CPER_SECTION_DEFINITION *section =
436 		select_section_by_guid(&descriptor->SectionType);
437 	if (section == NULL) {
438 		cper_print_log("Unknown section type guid\n");
439 	} else {
440 		result = read_section(cper_section_buf, size, section);
441 	}
442 
443 	//Was it an unknown GUID/failed read?
444 	if (result == NULL) {
445 		//Output the data as formatted base64.
446 		int32_t encoded_len = 0;
447 		char *encoded = base64_encode(cper_section_buf,
448 					      descriptor->SectionLength,
449 					      &encoded_len);
450 		if (encoded == NULL) {
451 			cper_print_log(
452 				"Failed to allocate encode output buffer. \n");
453 		} else {
454 			section_ir = json_object_new_object();
455 			json_object_object_add(section_ir, "data",
456 					       json_object_new_string_len(
457 						       encoded, encoded_len));
458 			free(encoded);
459 
460 			result = json_object_new_object();
461 			json_object_object_add(result, "Unknown", section_ir);
462 		}
463 	}
464 	if (result == NULL) {
465 		cper_print_log("RETURNING NULL!! !!\n");
466 	}
467 	return result;
468 }
469 
470 json_object *cper_buf_single_section_to_ir(const unsigned char *cper_buf,
471 					   size_t size)
472 {
473 	const unsigned char *cper_end;
474 	const unsigned char *section_begin;
475 	json_object *ir;
476 
477 	cper_end = cper_buf + size;
478 
479 	//Read the section descriptor out.
480 	EFI_ERROR_SECTION_DESCRIPTOR *section_descriptor;
481 	if (sizeof(EFI_ERROR_SECTION_DESCRIPTOR) > size) {
482 		cper_print_log(
483 			"Size of cper buffer was too small to read section descriptor %zu\n",
484 			size);
485 		return NULL;
486 	}
487 
488 	ir = json_object_new_object();
489 	section_descriptor = (EFI_ERROR_SECTION_DESCRIPTOR *)cper_buf;
490 	//Convert the section descriptor to IR.
491 	json_object *section_descriptor_ir =
492 		cper_section_descriptor_to_ir(section_descriptor);
493 	json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir);
494 	section_begin = cper_buf + section_descriptor->SectionOffset;
495 
496 	if (section_begin + section_descriptor->SectionLength >= cper_end) {
497 		json_object_put(ir);
498 		//cper_print_log("Invalid CPER file: Invalid section descriptor (section offset + length > size).\n");
499 		return NULL;
500 	}
501 
502 	const unsigned char *section =
503 		cper_buf + section_descriptor->SectionOffset;
504 
505 	//Parse the single section.
506 	json_object *section_ir = cper_buf_section_to_ir(
507 		section, section_descriptor->SectionLength, section_descriptor);
508 	if (section_ir == NULL) {
509 		cper_print_log("RETURNING NULL2!! !!\n");
510 	}
511 	json_object_object_add(ir, "section", section_ir);
512 	return ir;
513 }
514 
515 //Converts a single CPER section, without a header but with a section descriptor, to JSON.
516 json_object *cper_single_section_to_ir(FILE *cper_section_file)
517 {
518 	json_object *ir = json_object_new_object();
519 
520 	//Read the current file pointer location as base record position.
521 	long base_pos = ftell(cper_section_file);
522 
523 	//Read the section descriptor out.
524 	EFI_ERROR_SECTION_DESCRIPTOR section_descriptor;
525 	if (fread(&section_descriptor, sizeof(EFI_ERROR_SECTION_DESCRIPTOR), 1,
526 		  cper_section_file) != 1) {
527 		cper_print_log(
528 			"Failed to read section descriptor for CPER single section (fread() returned an unexpected value).\n");
529 		json_object_put(ir);
530 		return NULL;
531 	}
532 
533 	//Convert the section descriptor to IR.
534 	json_object *section_descriptor_ir =
535 		cper_section_descriptor_to_ir(&section_descriptor);
536 	json_object_object_add(ir, "sectionDescriptor", section_descriptor_ir);
537 
538 	//Save our current position in the stream.
539 	long position = ftell(cper_section_file);
540 
541 	//Read section as described by the section descriptor.
542 	fseek(cper_section_file, base_pos + section_descriptor.SectionOffset,
543 	      SEEK_SET);
544 	void *section = malloc(section_descriptor.SectionLength);
545 	if (fread(section, section_descriptor.SectionLength, 1,
546 		  cper_section_file) != 1) {
547 		cper_print_log(
548 			"Section read failed: Could not read %u bytes from global offset %d.\n",
549 			section_descriptor.SectionLength,
550 			section_descriptor.SectionOffset);
551 		json_object_put(ir);
552 		free(section);
553 		return NULL;
554 	}
555 
556 	//Seek back to our original position.
557 	fseek(cper_section_file, position, SEEK_SET);
558 
559 	//Parse the single section.
560 	json_object *section_ir = cper_buf_section_to_ir(
561 		section, section_descriptor.SectionLength, &section_descriptor);
562 	json_object_object_add(ir, "section", section_ir);
563 	free(section);
564 	return ir;
565 }
566 
567 char *cperbuf_single_section_to_str_ir(const unsigned char *cper_section,
568 				       size_t size)
569 {
570 	json_object *jobj = cper_buf_single_section_to_ir(cper_section, size);
571 	char *str = jobj ? strdup(json_object_to_json_string(jobj)) : NULL;
572 
573 	json_object_put(jobj);
574 	return str;
575 }
576