xref: /openbmc/libcper/cper-parse.c (revision a3663051b36d33dc4b102109666ee31b0c23582f)
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", &section_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(&section_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 			 &section_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 &section_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(&section_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(&section_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, &section_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