]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: Make efi_fnmatch non-backtracking 24808/head
authorJan Janssen <medhefgo@web.de>
Sat, 24 Sep 2022 11:33:10 +0000 (13:33 +0200)
committerJan Janssen <medhefgo@web.de>
Sun, 25 Sep 2022 12:26:00 +0000 (14:26 +0200)
src/boot/efi/efi-string.c
src/boot/efi/test-efi-string.c

index 5c05dfeb06693383125dbc3bd43e848d55614fba..b877c6f224b010a4d2cd1e9796b53f9a6aaab941 100644 (file)
@@ -138,13 +138,11 @@ DEFINE_STRCHR(char16_t, strchr16);
 DEFINE_STRNDUP(char, xstrndup8, strnlen8);
 DEFINE_STRNDUP(char16_t, xstrndup16, strnlen16);
 
-/* Patterns are fnmatch-compatible (with reduced feature support). */
-static bool efi_fnmatch_internal(const char16_t *p, const char16_t *h, int max_depth) {
+static bool efi_fnmatch_prefix(const char16_t *p, const char16_t *h, const char16_t **ret_p, const char16_t **ret_h) {
         assert(p);
         assert(h);
-
-        if (max_depth == 0)
-                return false;
+        assert(ret_p);
+        assert(ret_h);
 
         for (;; p++, h++)
                 switch (*p) {
@@ -166,17 +164,12 @@ static bool efi_fnmatch_internal(const char16_t *p, const char16_t *h, int max_d
                         break;
 
                 case '*':
-                        /* No need to recurse for consecutive '*'. */
+                        /* Point ret_p at the remainder of the pattern. */
                         while (*p == '*')
                                 p++;
-
-                        for (; *h != '\0'; h++)
-                                /* Try matching haystack with remaining pattern. */
-                                if (efi_fnmatch_internal(p, h, max_depth - 1))
-                                        return true;
-
-                        /* End of haystack. Pattern needs to be empty too for a match. */
-                        return *p == '\0';
+                        *ret_p = p;
+                        *ret_h = h;
+                        return true;
 
                 case '[':
                         if (*h == '\0')
@@ -236,8 +229,45 @@ static bool efi_fnmatch_internal(const char16_t *p, const char16_t *h, int max_d
                 }
 }
 
+/* Patterns are fnmatch-compatible (with reduced feature support). */
 bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack) {
-        return efi_fnmatch_internal(pattern, haystack, 32);
+        /* Patterns can be considered as simple patterns (without '*') concatenated by '*'. By doing so we
+         * simply have to make sure the very first simple pattern matches the start of haystack. Then we just
+         * look for the remaining simple patterns *somewhere* within the haystack (in order) as any extra
+         * characters in between would be matches by the '*'. We then only have to ensure that the very last
+         * simple pattern matches at the actual end of the haystack.
+         *
+         * This means we do not need to use backtracking which could have catastrophic runtimes with the
+         * right input data. */
+
+        for (bool first = true;;) {
+                const char16_t *pattern_tail = NULL, *haystack_tail = NULL;
+                bool match = efi_fnmatch_prefix(pattern, haystack, &pattern_tail, &haystack_tail);
+                if (first) {
+                        if (!match)
+                                /* Initial simple pattern must match. */
+                                return false;
+                        if (!pattern_tail)
+                                /* No '*' was in pattern, we can return early. */
+                                return true;
+                        first = false;
+                }
+
+                if (pattern_tail) {
+                        assert(match);
+                        pattern = pattern_tail;
+                        haystack = haystack_tail;
+                } else {
+                        /* If we have a match this must be at the end of the haystack. Note that
+                         * efi_fnmatch_prefix compares the NUL-bytes at the end, so we cannot match the end
+                         * of pattern in the middle of haystack). */
+                        if (match || *haystack == '\0')
+                                return match;
+
+                        /* Match one character using '*'. */
+                        haystack++;
+                }
+        }
 }
 
 #define DEFINE_PARSE_NUMBER(type, name)                                    \
index 0bfb564e3d66fca4704fbacf1bc1ebdfb21df671..2b2359fe5c6e329224ed463dd567ce9f25afc3eb 100644 (file)
@@ -345,6 +345,11 @@ TEST(efi_fnmatch) {
         TEST_FNMATCH_ONE("**", "abcd", true);
         TEST_FNMATCH_ONE("*b*", "abcd", true);
         TEST_FNMATCH_ONE("abc*d", "abc", false);
+        TEST_FNMATCH_ONE("start*end", "startend", true);
+        TEST_FNMATCH_ONE("start*end", "startendend", true);
+        TEST_FNMATCH_ONE("start*end", "startenddne", false);
+        TEST_FNMATCH_ONE("start*end", "startendstartend", true);
+        TEST_FNMATCH_ONE("start*end", "starten", false);
         TEST_FNMATCH_ONE("*.conf", "arch.conf", true);
         TEST_FNMATCH_ONE("debian-*.conf", "debian-wheezy.conf", true);
         TEST_FNMATCH_ONE("debian-*.*", "debian-wheezy.efi", true);
@@ -369,6 +374,17 @@ TEST(efi_fnmatch) {
         TEST_FNMATCH_ONE("[b]", "z-a", false);
         TEST_FNMATCH_ONE("[a\\-z]", "b", false);
         TEST_FNMATCH_ONE("?a*b[.-0]c", "/a/b/c", true);
+        TEST_FNMATCH_ONE("debian-*-*-*.*", "debian-jessie-2018-06-17-kernel-image-5.10.0-16-amd64.efi", true);
+
+        /* These would take forever with a backtracking implementation. */
+        TEST_FNMATCH_ONE(
+                        "a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*",
+                        "aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyy",
+                        false);
+        TEST_FNMATCH_ONE(
+                        "a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*",
+                        "aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz!!!!",
+                        true);
 }
 
 TEST(parse_number8) {