Blob Blame History Raw
From cef0317b83e06fdca25ef52a8bfd59b74d318e5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
Date: Thu, 29 Sep 2022 10:48:36 -0400
Subject: [PATCH 5/9] tpm2_eventlog_yaml: fix malformed YAML for EV_IPL data
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The code for printing EV_IPL data was fairly crude and often
did not generate valid YAML syntax. Some problems

 * Data starting with a space would result in invalid
   indentation, a leading space requires a quoted string
 * Non-printable cahracters must generally be escaped,
   using a quoted string
 * Embedded NUL bytes were turned into newlines, which
   mangled any UTF16 encoded data.

This change attempts to make the YAML output much safer. It
is not pefect as it just processes the data bytewise and
thus could potentially emit invalid UTF-8 bytes. In practice
this won't be a problem for known bootloader emitting EV_IPL
events.

This changes the formatting slightly

  - All strings are now surrounded with double quotes

  - All NUL bytes, including the final trailing NUL
    are displayed in escaped format.

  - Non-printable ASCII chars are escaped, including
    the tab character, per YAML recommendations

A much better long term solution would be to switch to
using libyaml for generating the output which would give
a strong guarantee of correct formatting.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 lib/tpm2_eventlog_yaml.c | 141 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 130 insertions(+), 11 deletions(-)

diff --git a/lib/tpm2_eventlog_yaml.c b/lib/tpm2_eventlog_yaml.c
index fee78027..66a20701 100644
--- a/lib/tpm2_eventlog_yaml.c
+++ b/lib/tpm2_eventlog_yaml.c
@@ -571,6 +571,125 @@ bool yaml_uefi_action(UINT8 const *action, size_t size) {
 
     return true;
 }
+
+
+/*
+ * The yaml_ipl description is received as raw bytes, but the
+ * data will represent a printable string. Unfortunately we
+ * are not told its encoding, and this can vary. For example,
+ * grub will use UTF8, while sd-boot will UTF16LE.
+ *
+ * We need to emit YAML with some rules:
+ *
+ *  - No leading ' ' without quoting it
+ *  - Escape non-printable ascii chars
+ *  - Double quotes if using escape sequences
+ *  - Valid UTF8 string
+ *
+ * This method will ignore the question of original data
+ * encoding and apply a few simple rules to make the data
+ * mostly YAML compliant. Where it falls down is not
+ * guaranteeing valid UTF8, if the input was not already
+ * valid UTF8. In practice this limitation shouldn't be
+ * a problem given expected measured data.
+ *
+ * Note: one consequence of this approach is that most
+ * UTF16LE data will be rendered with lots of \0 bytes
+ * escaped.
+ *
+ * For ease of output reading, the data is also split on newlines
+ */
+char **yaml_split_escape_string(UINT8 const *description, size_t size)
+{
+    char **lines = NULL, **tmp;
+    size_t nlines = 0;
+    size_t i, j, k;
+    size_t len;
+    UINT8 *nl;
+
+    i = 0;
+    do {
+        nl = memchr(description + i, '\n', size - i);
+        len = nl ? (size_t)(nl - (description + i)) : size - i;
+
+        tmp = realloc(lines, sizeof(char *) * (nlines + 2));
+        if (!tmp) {
+            LOG_ERR("failed to allocate memory for description lines: %s\n",
+                    strerror(errno));
+            goto error;
+        }
+        lines = tmp;
+        lines[nlines + 1] = NULL;
+        k = 0;
+
+        /* Worst case: every byte needs escaping, plus start/end quotes, plus nul */
+        lines[nlines] = calloc(1, (len * 2) + 2 + 1);
+        if (!lines[nlines]) {
+            LOG_ERR("failed to allocate memory for escaped string: %s\n",
+                    strerror(errno));
+            goto error;
+        }
+
+        lines[nlines][k++] = '"';
+        for (j = i; j < (i + len); j++) {
+            char escape = '\0';
+
+            switch (description[j]) {
+            case '\0':
+              escape = '0';
+              break;
+            case '\a':
+              escape = 'a';
+              break;
+            case '\b':
+              escape = 'b';
+              break;
+            case '\t':
+              escape = 't';
+              break;
+            case '\v':
+              escape = 'v';
+              break;
+            case '\f':
+              escape = 'f';
+              break;
+            case '\r':
+              escape = 'r';
+              break;
+            case '\e':
+              escape = 'e';
+              break;
+            case '\'':
+              escape = '\'';
+              break;
+            case '\\':
+              escape = '\\';
+              break;
+            }
+
+            if (escape == '\0') {
+                lines[nlines][k++] = description[j];
+            } else {
+                lines[nlines][k++] = '\\';
+                lines[nlines][k++] = escape;
+            }
+        }
+        lines[nlines][k++] = '"';
+
+        nlines++;
+        i += len + 1;
+    } while (i < size);
+
+    return lines;
+
+ error:
+    for (i = 0; lines != NULL && lines[i] != NULL; i++) {
+      free(lines[i]);
+    }
+    free(lines);
+    return NULL;
+}
+
 /*
  * TCG PC Client PFP section 9.4.1
  * This event type is extensively used by the Shim and Grub on a wide varities
@@ -578,21 +697,21 @@ bool yaml_uefi_action(UINT8 const *action, size_t size) {
  * the loading of grub, kernel, and initrd images.
  */
 bool yaml_ipl(UINT8 const *description, size_t size) {
-
+    char **lines = NULL;
+    size_t i;
     tpm2_tool_output("  Event:\n"
                      "    String: |-\n");
 
-    /* We need to handle when description contains multiple lines. */
-    size_t i, j;
-    for (i = 0; i < size; i++) {
-        for (j = i; j < size; j++) {
-            if (description[j] == '\n' || description[j] == '\0') {
-                break;
-            }
-        }
-        tpm2_tool_output("      %.*s\n", (int)(j - i), description+i);
-        i = j;
+    lines = yaml_split_escape_string(description, size);
+    if (!lines) {
+        return false;
+    }
+
+    for (i = 0; lines[i] != NULL; i++) {
+        tpm2_tool_output("      %s\n", lines[i]);
+        free(lines[i]);
     }
+    free(lines);
 
     return true;
 }
-- 
2.37.3