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

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