Allow parsing base64 files
Redfish outputs base64 strings. It would be useful if the CLI app could
read those strings in directly.
This commit breaks out a new method "header_valid" to allow tooling to
do an initial reading of a buffer to determine if it appears to be
correct before going further. This allows the CLI app to attempt to
parse as a buffer, if that fails, attempt to parse as base64.
To support as many inputs as possible, this commit makes padding
optional. It also allows a trailing \n as is present in many files.
Change-Id: I4fb759ecefc8ce1c757f1a9e7c4a2b2d220105d0
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/base64.c b/base64.c
index 5ed39c6..7ace17a 100644
--- a/base64.c
+++ b/base64.c
@@ -95,59 +95,70 @@
*/
UINT8 *base64_decode(const CHAR8 *src, INT32 len, INT32 *out_len)
{
- UINT8 *out;
- UINT8 *pos;
+ UINT8 *out = NULL;
+ UINT8 *pos = NULL;
UINT8 block[4];
- UINT8 tmp;
- INT32 block_index;
- INT32 src_index;
- UINT32 pad_count = 0;
+ INT32 block_index = 0;
+ INT32 src_index = 0;
if (!out_len) {
- return NULL;
+ goto error;
}
// Malloc might be up to 2 larger dependent on padding
- *out_len = len / 4 * 3;
+ *out_len = len / 4 * 3 + 2;
pos = out = malloc(*out_len);
if (out == NULL) {
- return NULL;
+ goto error;
}
block_index = 0;
for (src_index = 0; src_index < len; src_index++) {
- tmp = decode_table[(UINT8)src[src_index]];
- if (tmp == 0x80) {
- free(out);
- return NULL;
+ char current_char = src[src_index];
+ if (current_char == '=') {
+ break;
+ }
+ // If the final character is a newline, as can occur in many editors
+ // then ignore it.
+ if (src_index + 1 == len && current_char == '\n') {
+ printf("Ignoring trailing newline.\n");
+ break;
}
- if (src[src_index] == '=') {
- pad_count++;
+ block[block_index] = decode_table[(UINT8)current_char];
+ if (block[block_index] == 0x80) {
+ printf("Invalid character \"%c\".\n", current_char);
+ goto error;
}
- block[block_index] = tmp;
block_index++;
if (block_index == 4) {
*pos++ = (block[0] << 2) | (block[1] >> 4);
*pos++ = (block[1] << 4) | (block[2] >> 2);
*pos++ = (block[2] << 6) | block[3];
- if (pad_count > 0) {
- if (pad_count == 1) {
- pos--;
- } else if (pad_count == 2) {
- pos -= 2;
- } else {
- /* Invalid pad_counting */
- free(out);
- return NULL;
- }
- break;
- }
block_index = 0;
}
}
+ if (block_index == 0) {
+ // mod 4 Even number of characters, no padding.
+ } else if (block_index == 1) {
+ printf("Invalid base64 input length. Last character truncated.\n");
+ goto error;
+ } else if (block_index == 2) {
+ *pos++ = (block[0] << 2) | (block[1] >> 4);
+ } else if (block_index == 3) {
+ *pos++ = (block[0] << 2) | (block[1] >> 4);
+ *pos++ = (block[1] << 4) | (block[2] >> 2);
+ } else {
+ /* Invalid pad_counting */
+ printf("Invalid base64 input length %d.\n", block_index);
+ goto error;
+ }
*out_len = pos - out;
return out;
+
+error:
+ free(out);
+ return NULL;
}
diff --git a/cli-app/cper-convert.c b/cli-app/cper-convert.c
index 942d258..d1caeb6 100644
--- a/cli-app/cper-convert.c
+++ b/cli-app/cper-convert.c
@@ -13,6 +13,8 @@
#include <libcper/log.h>
#include <libcper/cper-parse.h>
#include <libcper/json-schema.h>
+#include <libcper/Cper.h>
+#include <libcper/base64.h>
void cper_to_json(char *in_file, char *out_file, int is_single_section);
void json_to_cper(const char *in_file, const char *out_file);
@@ -94,12 +96,49 @@
return;
}
+ fseek(cper_file, 0, SEEK_END);
+ long fsize = ftell(cper_file);
+ fseek(cper_file, 0, SEEK_SET);
+
+ char *fbuff = malloc(fsize);
+ size_t readsize = fread(fbuff, 1, (long)fsize, cper_file);
+ if (readsize != (size_t)fsize) {
+ printf("Could not read CPER file '%s', read returned %ld bytes.\n",
+ in_file, readsize);
+ return;
+ }
+
+ if (!header_valid(fbuff, readsize)) {
+ // Check if it's base64 encoded
+ int32_t decoded_len = 0;
+ UINT8 *decoded = base64_decode(fbuff, readsize, &decoded_len);
+ if (decoded == NULL) {
+ printf("base64 decode failed for CPER file '%s'.\n",
+ in_file);
+ free(fbuff);
+ free(decoded);
+ return;
+ }
+ if (!header_valid((const char *)decoded, decoded_len)) {
+ printf("Invalid CPER file '%s'.\n", in_file);
+ free(fbuff);
+ free(decoded);
+ return;
+ }
+ // Swap the buffer to the base64 decoded buffer.
+ free(fbuff);
+ fbuff = (char *)decoded;
+
+ fsize = decoded_len;
+ decoded = NULL;
+ }
+
//Convert.
json_object *ir;
if (is_single_section) {
- ir = cper_single_section_to_ir(cper_file);
+ ir = cper_buf_single_section_to_ir((UINT8 *)fbuff, readsize);
} else {
- ir = cper_to_ir(cper_file);
+ ir = cper_buf_to_ir((UINT8 *)fbuff, fsize);
}
fclose(cper_file);
diff --git a/cper-parse.c b/cper-parse.c
index af7d424..87d577b 100644
--- a/cper-parse.c
+++ b/cper-parse.c
@@ -26,6 +26,41 @@
json_object *cper_buf_section_to_ir(const void *cper_section_buf, size_t size,
EFI_ERROR_SECTION_DESCRIPTOR *descriptor);
+static int header_signature_valid(EFI_COMMON_ERROR_RECORD_HEADER *header)
+{
+ if (header->SignatureStart != EFI_ERROR_RECORD_SIGNATURE_START) {
+ cper_print_log(
+ "Invalid CPER file: Invalid header (incorrect signature). %x\n",
+ header->SignatureStart);
+ return 0;
+ }
+ if (header->SignatureEnd != EFI_ERROR_RECORD_SIGNATURE_END) {
+ cper_print_log(
+ "Invalid CPER file: Invalid header (incorrect signature end). %x\n",
+ header->SignatureEnd);
+ return 0;
+ }
+ if (header->SectionCount == 0) {
+ cper_print_log(
+ "Invalid CPER file: Invalid section count (0).\n");
+ return 0;
+ }
+ return 1;
+}
+
+int header_valid(const char *cper_buf, size_t size)
+{
+ if (size < sizeof(EFI_COMMON_ERROR_RECORD_HEADER)) {
+ return 0;
+ }
+ EFI_COMMON_ERROR_RECORD_HEADER *header =
+ (EFI_COMMON_ERROR_RECORD_HEADER *)cper_buf;
+ if (!header_signature_valid(header)) {
+ return 0;
+ }
+ return header_signature_valid(header);
+}
+
json_object *cper_buf_to_ir(const unsigned char *cper_buf, size_t size)
{
json_object *parent = NULL;
@@ -35,28 +70,17 @@
const unsigned char *pos = cper_buf;
unsigned int remaining = size;
-
- if (remaining < sizeof(EFI_COMMON_ERROR_RECORD_HEADER)) {
- cper_print_log(
- "Invalid CPER file: Invalid header (not enough bytes).\n");
+ if (size < sizeof(EFI_COMMON_ERROR_RECORD_HEADER)) {
goto fail;
}
-
- EFI_COMMON_ERROR_RECORD_HEADER *header = NULL;
- header = (EFI_COMMON_ERROR_RECORD_HEADER *)cper_buf;
+ EFI_COMMON_ERROR_RECORD_HEADER *header =
+ (EFI_COMMON_ERROR_RECORD_HEADER *)cper_buf;
+ if (!header_signature_valid(header)) {
+ goto fail;
+ }
pos += sizeof(EFI_COMMON_ERROR_RECORD_HEADER);
remaining -= sizeof(EFI_COMMON_ERROR_RECORD_HEADER);
- if (header->SignatureStart != EFI_ERROR_RECORD_SIGNATURE_START) {
- cper_print_log(
- "Invalid CPER file: Invalid header (incorrect signature). %x\n",
- header->SignatureStart);
- goto fail;
- }
- if (header->SectionCount == 0) {
- cper_print_log(
- "Invalid CPER file: Invalid section count (0).\n");
- goto fail;
- }
+
if (remaining < sizeof(EFI_ERROR_SECTION_DESCRIPTOR)) {
cper_print_log(
"Invalid CPER file: Invalid section descriptor (section offset + length > size).\n");
diff --git a/include/libcper/cper-parse.h b/include/libcper/cper-parse.h
index 7f05817..06949a4 100644
--- a/include/libcper/cper-parse.h
+++ b/include/libcper/cper-parse.h
@@ -28,6 +28,8 @@
"HW_ERROR_FLAGS_PREVERR", \
"HW_ERROR_FLAGS_SIMULATED" }
+int header_valid(const char *cper_buf, size_t size);
+
json_object *cper_to_ir(FILE *cper_file);
json_object *cper_buf_to_ir(const unsigned char *cper_buf, size_t size);
json_object *cper_single_section_to_ir(FILE *cper_section_file);
diff --git a/log.c b/log.c
index 2938a60..16f671f 100644
--- a/log.c
+++ b/log.c
@@ -13,7 +13,7 @@
CPER_LOG_NONE,
CPER_LOG_STDIO,
CPER_LOG_CUSTOM,
-} log_type = CPER_LOG_NONE;
+} log_type = CPER_LOG_STDIO;
static void (*log_custom_fn)(const char *, va_list);
diff --git a/tests/base64_test.c b/tests/base64_test.c
index abc540c..6fc7825 100644
--- a/tests/base64_test.c
+++ b/tests/base64_test.c
@@ -5,23 +5,49 @@
#include <assert.h>
#include <string.h>
+const char *good_encode_outputs[] = {
+ "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy",
+};
+const char *good_encode_inputs[] = {
+ "f", "fo", "foo", "foob", "fooba", "foobar",
+};
+
void test_base64_encode_good()
{
int32_t encoded_len = 0;
- const char *data = "f";
- char *encoded = base64_encode((unsigned char *)data, strlen(data),
- &encoded_len);
- assert(encoded_len == 4);
- assert(memcmp(encoded, "Zg==", encoded_len) == 0);
- free(encoded);
+ for (long unsigned int i = 0;
+ i < sizeof(good_encode_inputs) / sizeof(good_encode_inputs[0]);
+ i++) {
+ const char *data = good_encode_inputs[i];
+ char *encoded = base64_encode((unsigned char *)data,
+ strlen(data), &encoded_len);
+ assert((size_t)encoded_len == strlen(good_encode_outputs[i]));
+ assert(memcmp(encoded, good_encode_outputs[i], encoded_len) ==
+ 0);
+ free(encoded);
+ }
}
+const char *good_decode_inputs[] = {
+ "Zg==", "Zg", "Zm8=", "Zm8", "Zm9v",
+};
+const char *good_decode_outputs[] = {
+ "f", "f", "fo", "fo", "foo",
+};
+
void test_base64_decode_good()
{
- int32_t decoded_len = 0;
- const char *data = "Zg==";
- UINT8 *decoded = base64_decode(data, strlen(data), &decoded_len);
- assert(decoded_len == 1);
- assert(decoded[0] == 'f');
- free(decoded);
+ for (long unsigned int i = 0;
+ i < sizeof(good_decode_inputs) / sizeof(good_decode_inputs[0]);
+ i++) {
+ int32_t decoded_len = 0;
+ const char *data = good_decode_inputs[i];
+ UINT8 *decoded =
+ base64_decode(data, strlen(data), &decoded_len);
+ assert(decoded != NULL);
+ assert((size_t)decoded_len == strlen(good_decode_outputs[i]));
+ assert(memcmp(decoded, good_decode_outputs[i], decoded_len) ==
+ 0);
+ free(decoded);
+ }
}