Blame SOURCES/expat-2.2.5-Add-missing-validation-of-encoding.patch

589ceb
commit e8f285b522a907603501329e5b4212755f525fdf
589ceb
Author: Tomas Korbar <tkorbar@redhat.com>
589ceb
Date:   Thu Mar 3 12:04:09 2022 +0100
589ceb
589ceb
    CVE-2022-25235
589ceb
589ceb
diff --git a/lib/xmltok.c b/lib/xmltok.c
589ceb
index 6b415d8..b55732a 100644
589ceb
--- a/lib/xmltok.c
589ceb
+++ b/lib/xmltok.c
589ceb
@@ -103,13 +103,6 @@
589ceb
                       + ((((byte)[2]) >> 5) & 1)] \
589ceb
          & (1u << (((byte)[2]) & 0x1F)))
589ceb
 
589ceb
-#define UTF8_GET_NAMING(pages, p, n) \
589ceb
-  ((n) == 2 \
589ceb
-  ? UTF8_GET_NAMING2(pages, (const unsigned char *)(p)) \
589ceb
-  : ((n) == 3 \
589ceb
-     ? UTF8_GET_NAMING3(pages, (const unsigned char *)(p)) \
589ceb
-     : 0))
589ceb
-
589ceb
 /* Detection of invalid UTF-8 sequences is based on Table 3.1B
589ceb
    of Unicode 3.2: http://www.unicode.org/unicode/reports/tr28/
589ceb
    with the additional restriction of not allowing the Unicode
589ceb
diff --git a/lib/xmltok_impl.c b/lib/xmltok_impl.c
589ceb
index 0403dd3..56d7a40 100644
589ceb
--- a/lib/xmltok_impl.c
589ceb
+++ b/lib/xmltok_impl.c
589ceb
@@ -61,7 +61,7 @@
589ceb
    case BT_LEAD ## n: \
589ceb
      if (end - ptr < n) \
589ceb
        return XML_TOK_PARTIAL_CHAR; \
589ceb
-     if (!IS_NAME_CHAR(enc, ptr, n)) { \
589ceb
+     if (IS_INVALID_CHAR(enc, ptr, n) || ! IS_NAME_CHAR(enc, ptr, n)) { \
589ceb
        *nextTokPtr = ptr; \
589ceb
        return XML_TOK_INVALID; \
589ceb
      } \
589ceb
@@ -89,7 +89,7 @@
589ceb
    case BT_LEAD ## n: \
589ceb
      if (end - ptr < n) \
589ceb
        return XML_TOK_PARTIAL_CHAR; \
589ceb
-     if (!IS_NMSTRT_CHAR(enc, ptr, n)) { \
589ceb
+     if (IS_INVALID_CHAR(enc, ptr, n) || ! IS_NMSTRT_CHAR(enc, ptr, n)) { \
589ceb
        *nextTokPtr = ptr; \
589ceb
        return XML_TOK_INVALID; \
589ceb
      } \
589ceb
@@ -1117,6 +1117,10 @@ PREFIX(prologTok)(const ENCODING *enc, const char *ptr, const char *end,
589ceb
   case BT_LEAD ## n: \
589ceb
     if (end - ptr < n) \
589ceb
       return XML_TOK_PARTIAL_CHAR; \
589ceb
+    if (IS_INVALID_CHAR(enc, ptr, n)) {                                        \
589ceb
+      *nextTokPtr = ptr;                                                       \
589ceb
+      return XML_TOK_INVALID;                                                  \
589ceb
+    }                                                                          \
589ceb
     if (IS_NMSTRT_CHAR(enc, ptr, n)) { \
589ceb
       ptr += n; \
589ceb
       tok = XML_TOK_NAME; \
589ceb
diff --git a/tests/runtests.c b/tests/runtests.c
589ceb
index 278bfa1..0f3afde 100644
589ceb
--- a/tests/runtests.c
589ceb
+++ b/tests/runtests.c
589ceb
@@ -6540,6 +6540,106 @@ START_TEST(test_utf8_in_cdata_section_2)
589ceb
 }
589ceb
 END_TEST
589ceb
 
589ceb
+START_TEST(test_utf8_in_start_tags) {
589ceb
+  struct test_case {
589ceb
+    bool goodName;
589ceb
+    bool goodNameStart;
589ceb
+    const char *tagName;
589ceb
+  };
589ceb
+
589ceb
+  // The idea with the tests below is this:
589ceb
+  // We want to cover 1-, 2- and 3-byte sequences, 4-byte sequences
589ceb
+  // go to isNever and are hence not a concern.
589ceb
+  //
589ceb
+  // We start with a character that is a valid name character
589ceb
+  // (or even name-start character, see XML 1.0r4 spec) and then we flip
589ceb
+  // single bits at places where (1) the result leaves the UTF-8 encoding space
589ceb
+  // and (2) we stay in the same n-byte sequence family.
589ceb
+  //
589ceb
+  // The flipped bits are highlighted in angle brackets in comments,
589ceb
+  // e.g. "[<1>011 1001]" means we had [0011 1001] but we now flipped
589ceb
+  // the most significant bit to 1 to leave UTF-8 encoding space.
589ceb
+  struct test_case cases[] = {
589ceb
+      // 1-byte UTF-8: [0xxx xxxx]
589ceb
+      {true, true, "\x3A"},   // [0011 1010] = ASCII colon ':'
589ceb
+      {false, false, "\xBA"}, // [<1>011 1010]
589ceb
+      {true, false, "\x39"},  // [0011 1001] = ASCII nine '9'
589ceb
+      {false, false, "\xB9"}, // [<1>011 1001]
589ceb
+
589ceb
+      // 2-byte UTF-8: [110x xxxx] [10xx xxxx]
589ceb
+      {true, true, "\xDB\xA5"},   // [1101 1011] [1010 0101] =
589ceb
+                                  // Arabic small waw U+06E5
589ceb
+      {false, false, "\x9B\xA5"}, // [1<0>01 1011] [1010 0101]
589ceb
+      {false, false, "\xDB\x25"}, // [1101 1011] [<0>010 0101]
589ceb
+      {false, false, "\xDB\xE5"}, // [1101 1011] [1<1>10 0101]
589ceb
+      {true, false, "\xCC\x81"},  // [1100 1100] [1000 0001] =
589ceb
+                                  // combining char U+0301
589ceb
+      {false, false, "\x8C\x81"}, // [1<0>00 1100] [1000 0001]
589ceb
+      {false, false, "\xCC\x01"}, // [1100 1100] [<0>000 0001]
589ceb
+      {false, false, "\xCC\xC1"}, // [1100 1100] [1<1>00 0001]
589ceb
+
589ceb
+      // 3-byte UTF-8: [1110 xxxx] [10xx xxxx] [10xxxxxx]
589ceb
+      {true, true, "\xE0\xA4\x85"},   // [1110 0000] [1010 0100] [1000 0101] =
589ceb
+                                      // Devanagari Letter A U+0905
589ceb
+      {false, false, "\xA0\xA4\x85"}, // [1<0>10 0000] [1010 0100] [1000 0101]
589ceb
+      {false, false, "\xE0\x24\x85"}, // [1110 0000] [<0>010 0100] [1000 0101]
589ceb
+      {false, false, "\xE0\xE4\x85"}, // [1110 0000] [1<1>10 0100] [1000 0101]
589ceb
+      {false, false, "\xE0\xA4\x05"}, // [1110 0000] [1010 0100] [<0>000 0101]
589ceb
+      {false, false, "\xE0\xA4\xC5"}, // [1110 0000] [1010 0100] [1<1>00 0101]
589ceb
+      {true, false, "\xE0\xA4\x81"},  // [1110 0000] [1010 0100] [1000 0001] =
589ceb
+                                      // combining char U+0901
589ceb
+      {false, false, "\xA0\xA4\x81"}, // [1<0>10 0000] [1010 0100] [1000 0001]
589ceb
+      {false, false, "\xE0\x24\x81"}, // [1110 0000] [<0>010 0100] [1000 0001]
589ceb
+      {false, false, "\xE0\xE4\x81"}, // [1110 0000] [1<1>10 0100] [1000 0001]
589ceb
+      {false, false, "\xE0\xA4\x01"}, // [1110 0000] [1010 0100] [<0>000 0001]
589ceb
+      {false, false, "\xE0\xA4\xC1"}, // [1110 0000] [1010 0100] [1<1>00 0001]
589ceb
+  };
589ceb
+  const bool atNameStart[] = {true, false};
589ceb
+
589ceb
+  size_t i = 0;
589ceb
+  char doc[1024];
589ceb
+  size_t failCount = 0;
589ceb
+
589ceb
+  for (; i < sizeof(cases) / sizeof(cases[0]); i++) {
589ceb
+    size_t j = 0;
589ceb
+    for (; j < sizeof(atNameStart) / sizeof(atNameStart[0]); j++) {
589ceb
+      const bool expectedSuccess
589ceb
+          = atNameStart[j] ? cases[i].goodNameStart : cases[i].goodName;
589ceb
+      sprintf(doc, "<%s%s>
589ceb
+      XML_Parser parser = XML_ParserCreate(NULL);
589ceb
+
589ceb
+      const enum XML_Status status
589ceb
+          = XML_Parse(parser, doc, (int)strlen(doc), /*isFinal=*/XML_FALSE);
589ceb
+
589ceb
+      bool success = true;
589ceb
+      if ((status == XML_STATUS_OK) != expectedSuccess) {
589ceb
+        success = false;
589ceb
+      }
589ceb
+      if ((status == XML_STATUS_ERROR)
589ceb
+          && (XML_GetErrorCode(parser) != XML_ERROR_INVALID_TOKEN)) {
589ceb
+        success = false;
589ceb
+      }
589ceb
+
589ceb
+      if (! success) {
589ceb
+        fprintf(
589ceb
+            stderr,
589ceb
+            "FAIL case %2u (%sat name start, %u-byte sequence, error code %d)\n",
589ceb
+            (unsigned)i + 1u, atNameStart[j] ? "    " : "not ",
589ceb
+            (unsigned)strlen(cases[i].tagName), XML_GetErrorCode(parser));
589ceb
+        failCount++;
589ceb
+      }
589ceb
+
589ceb
+      XML_ParserFree(parser);
589ceb
+    }
589ceb
+  }
589ceb
+
589ceb
+  if (failCount > 0) {
589ceb
+    fail("UTF-8 regression detected");
589ceb
+  }
589ceb
+}
589ceb
+END_TEST
589ceb
+
589ceb
+
589ceb
 /* Test trailing spaces in elements are accepted */
589ceb
 static void XMLCALL
589ceb
 record_element_end_handler(void *userData,
589ceb
@@ -6734,6 +6834,15 @@ START_TEST(test_bad_doctype)
589ceb
 }
589ceb
 END_TEST
589ceb
 
589ceb
+START_TEST(test_bad_doctype_utf8) {
589ceb
+  const char *text = "
589ceb
+                     "doc><doc/>"; // [1101 1011] [<0>010 0101]
589ceb
+  expect_failure(text, XML_ERROR_INVALID_TOKEN,
589ceb
+                 "Invalid UTF-8 in DOCTYPE not faulted");
589ceb
+}
589ceb
+END_TEST
589ceb
+
589ceb
+
589ceb
 START_TEST(test_bad_doctype_utf16)
589ceb
 {
589ceb
     const char text[] =
589ceb
@@ -12256,6 +12365,7 @@ make_suite(void)
589ceb
     tcase_add_test(tc_basic, test_ext_entity_utf8_non_bom);
589ceb
     tcase_add_test(tc_basic, test_utf8_in_cdata_section);
589ceb
     tcase_add_test(tc_basic, test_utf8_in_cdata_section_2);
589ceb
+    tcase_add_test(tc_basic, test_utf8_in_start_tags);
589ceb
     tcase_add_test(tc_basic, test_trailing_spaces_in_elements);
589ceb
     tcase_add_test(tc_basic, test_utf16_attribute);
589ceb
     tcase_add_test(tc_basic, test_utf16_second_attr);
589ceb
@@ -12264,6 +12374,7 @@ make_suite(void)
589ceb
     tcase_add_test(tc_basic, test_bad_attr_desc_keyword);
589ceb
     tcase_add_test(tc_basic, test_bad_attr_desc_keyword_utf16);
589ceb
     tcase_add_test(tc_basic, test_bad_doctype);
589ceb
+    tcase_add_test(tc_basic, test_bad_doctype_utf8);
589ceb
     tcase_add_test(tc_basic, test_bad_doctype_utf16);
589ceb
     tcase_add_test(tc_basic, test_bad_doctype_plus);
589ceb
     tcase_add_test(tc_basic, test_bad_doctype_star);