Blame SOURCES/0005-tpm2_eventlog_yaml-fix-malformed-YAML-for-EV_IPL-dat.patch

c8bd4f
From cef0317b83e06fdca25ef52a8bfd59b74d318e5a Mon Sep 17 00:00:00 2001
c8bd4f
From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
c8bd4f
Date: Thu, 29 Sep 2022 10:48:36 -0400
c8bd4f
Subject: [PATCH 5/9] tpm2_eventlog_yaml: fix malformed YAML for EV_IPL data
c8bd4f
MIME-Version: 1.0
c8bd4f
Content-Type: text/plain; charset=UTF-8
c8bd4f
Content-Transfer-Encoding: 8bit
c8bd4f
c8bd4f
The code for printing EV_IPL data was fairly crude and often
c8bd4f
did not generate valid YAML syntax. Some problems
c8bd4f
c8bd4f
 * Data starting with a space would result in invalid
c8bd4f
   indentation, a leading space requires a quoted string
c8bd4f
 * Non-printable cahracters must generally be escaped,
c8bd4f
   using a quoted string
c8bd4f
 * Embedded NUL bytes were turned into newlines, which
c8bd4f
   mangled any UTF16 encoded data.
c8bd4f
c8bd4f
This change attempts to make the YAML output much safer. It
c8bd4f
is not pefect as it just processes the data bytewise and
c8bd4f
thus could potentially emit invalid UTF-8 bytes. In practice
c8bd4f
this won't be a problem for known bootloader emitting EV_IPL
c8bd4f
events.
c8bd4f
c8bd4f
This changes the formatting slightly
c8bd4f
c8bd4f
  - All strings are now surrounded with double quotes
c8bd4f
c8bd4f
  - All NUL bytes, including the final trailing NUL
c8bd4f
    are displayed in escaped format.
c8bd4f
c8bd4f
  - Non-printable ASCII chars are escaped, including
c8bd4f
    the tab character, per YAML recommendations
c8bd4f
c8bd4f
A much better long term solution would be to switch to
c8bd4f
using libyaml for generating the output which would give
c8bd4f
a strong guarantee of correct formatting.
c8bd4f
c8bd4f
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
c8bd4f
---
c8bd4f
 lib/tpm2_eventlog_yaml.c | 141 ++++++++++++++++++++++++++++++++++++---
c8bd4f
 1 file changed, 130 insertions(+), 11 deletions(-)
c8bd4f
c8bd4f
diff --git a/lib/tpm2_eventlog_yaml.c b/lib/tpm2_eventlog_yaml.c
c8bd4f
index fee78027..66a20701 100644
c8bd4f
--- a/lib/tpm2_eventlog_yaml.c
c8bd4f
+++ b/lib/tpm2_eventlog_yaml.c
c8bd4f
@@ -571,6 +571,125 @@ bool yaml_uefi_action(UINT8 const *action, size_t size) {
c8bd4f
 
c8bd4f
     return true;
c8bd4f
 }
c8bd4f
+
c8bd4f
+
c8bd4f
+/*
c8bd4f
+ * The yaml_ipl description is received as raw bytes, but the
c8bd4f
+ * data will represent a printable string. Unfortunately we
c8bd4f
+ * are not told its encoding, and this can vary. For example,
c8bd4f
+ * grub will use UTF8, while sd-boot will UTF16LE.
c8bd4f
+ *
c8bd4f
+ * We need to emit YAML with some rules:
c8bd4f
+ *
c8bd4f
+ *  - No leading ' ' without quoting it
c8bd4f
+ *  - Escape non-printable ascii chars
c8bd4f
+ *  - Double quotes if using escape sequences
c8bd4f
+ *  - Valid UTF8 string
c8bd4f
+ *
c8bd4f
+ * This method will ignore the question of original data
c8bd4f
+ * encoding and apply a few simple rules to make the data
c8bd4f
+ * mostly YAML compliant. Where it falls down is not
c8bd4f
+ * guaranteeing valid UTF8, if the input was not already
c8bd4f
+ * valid UTF8. In practice this limitation shouldn't be
c8bd4f
+ * a problem given expected measured data.
c8bd4f
+ *
c8bd4f
+ * Note: one consequence of this approach is that most
c8bd4f
+ * UTF16LE data will be rendered with lots of \0 bytes
c8bd4f
+ * escaped.
c8bd4f
+ *
c8bd4f
+ * For ease of output reading, the data is also split on newlines
c8bd4f
+ */
c8bd4f
+char **yaml_split_escape_string(UINT8 const *description, size_t size)
c8bd4f
+{
c8bd4f
+    char **lines = NULL, **tmp;
c8bd4f
+    size_t nlines = 0;
c8bd4f
+    size_t i, j, k;
c8bd4f
+    size_t len;
c8bd4f
+    UINT8 *nl;
c8bd4f
+
c8bd4f
+    i = 0;
c8bd4f
+    do {
c8bd4f
+        nl = memchr(description + i, '\n', size - i);
c8bd4f
+        len = nl ? (size_t)(nl - (description + i)) : size - i;
c8bd4f
+
c8bd4f
+        tmp = realloc(lines, sizeof(char *) * (nlines + 2));
c8bd4f
+        if (!tmp) {
c8bd4f
+            LOG_ERR("failed to allocate memory for description lines: %s\n",
c8bd4f
+                    strerror(errno));
c8bd4f
+            goto error;
c8bd4f
+        }
c8bd4f
+        lines = tmp;
c8bd4f
+        lines[nlines + 1] = NULL;
c8bd4f
+        k = 0;
c8bd4f
+
c8bd4f
+        /* Worst case: every byte needs escaping, plus start/end quotes, plus nul */
c8bd4f
+        lines[nlines] = calloc(1, (len * 2) + 2 + 1);
c8bd4f
+        if (!lines[nlines]) {
c8bd4f
+            LOG_ERR("failed to allocate memory for escaped string: %s\n",
c8bd4f
+                    strerror(errno));
c8bd4f
+            goto error;
c8bd4f
+        }
c8bd4f
+
c8bd4f
+        lines[nlines][k++] = '"';
c8bd4f
+        for (j = i; j < (i + len); j++) {
c8bd4f
+            char escape = '\0';
c8bd4f
+
c8bd4f
+            switch (description[j]) {
c8bd4f
+            case '\0':
c8bd4f
+              escape = '0';
c8bd4f
+              break;
c8bd4f
+            case '\a':
c8bd4f
+              escape = 'a';
c8bd4f
+              break;
c8bd4f
+            case '\b':
c8bd4f
+              escape = 'b';
c8bd4f
+              break;
c8bd4f
+            case '\t':
c8bd4f
+              escape = 't';
c8bd4f
+              break;
c8bd4f
+            case '\v':
c8bd4f
+              escape = 'v';
c8bd4f
+              break;
c8bd4f
+            case '\f':
c8bd4f
+              escape = 'f';
c8bd4f
+              break;
c8bd4f
+            case '\r':
c8bd4f
+              escape = 'r';
c8bd4f
+              break;
c8bd4f
+            case '\e':
c8bd4f
+              escape = 'e';
c8bd4f
+              break;
c8bd4f
+            case '\'':
c8bd4f
+              escape = '\'';
c8bd4f
+              break;
c8bd4f
+            case '\\':
c8bd4f
+              escape = '\\';
c8bd4f
+              break;
c8bd4f
+            }
c8bd4f
+
c8bd4f
+            if (escape == '\0') {
c8bd4f
+                lines[nlines][k++] = description[j];
c8bd4f
+            } else {
c8bd4f
+                lines[nlines][k++] = '\\';
c8bd4f
+                lines[nlines][k++] = escape;
c8bd4f
+            }
c8bd4f
+        }
c8bd4f
+        lines[nlines][k++] = '"';
c8bd4f
+
c8bd4f
+        nlines++;
c8bd4f
+        i += len + 1;
c8bd4f
+    } while (i < size);
c8bd4f
+
c8bd4f
+    return lines;
c8bd4f
+
c8bd4f
+ error:
c8bd4f
+    for (i = 0; lines != NULL && lines[i] != NULL; i++) {
c8bd4f
+      free(lines[i]);
c8bd4f
+    }
c8bd4f
+    free(lines);
c8bd4f
+    return NULL;
c8bd4f
+}
c8bd4f
+
c8bd4f
 /*
c8bd4f
  * TCG PC Client PFP section 9.4.1
c8bd4f
  * This event type is extensively used by the Shim and Grub on a wide varities
c8bd4f
@@ -578,21 +697,21 @@ bool yaml_uefi_action(UINT8 const *action, size_t size) {
c8bd4f
  * the loading of grub, kernel, and initrd images.
c8bd4f
  */
c8bd4f
 bool yaml_ipl(UINT8 const *description, size_t size) {
c8bd4f
-
c8bd4f
+    char **lines = NULL;
c8bd4f
+    size_t i;
c8bd4f
     tpm2_tool_output("  Event:\n"
c8bd4f
                      "    String: |-\n");
c8bd4f
 
c8bd4f
-    /* We need to handle when description contains multiple lines. */
c8bd4f
-    size_t i, j;
c8bd4f
-    for (i = 0; i < size; i++) {
c8bd4f
-        for (j = i; j < size; j++) {
c8bd4f
-            if (description[j] == '\n' || description[j] == '\0') {
c8bd4f
-                break;
c8bd4f
-            }
c8bd4f
-        }
c8bd4f
-        tpm2_tool_output("      %.*s\n", (int)(j - i), description+i);
c8bd4f
-        i = j;
c8bd4f
+    lines = yaml_split_escape_string(description, size);
c8bd4f
+    if (!lines) {
c8bd4f
+        return false;
c8bd4f
+    }
c8bd4f
+
c8bd4f
+    for (i = 0; lines[i] != NULL; i++) {
c8bd4f
+        tpm2_tool_output("      %s\n", lines[i]);
c8bd4f
+        free(lines[i]);
c8bd4f
     }
c8bd4f
+    free(lines);
c8bd4f
 
c8bd4f
     return true;
c8bd4f
 }
c8bd4f
-- 
c8bd4f
2.37.3
c8bd4f