]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
escape: reject UTF-16 surrogates in \u escapes in cunescape_one() (#43079)
authorArmaan Sandhu <74664101+Ar-maan05@users.noreply.github.com>
Tue, 21 Jul 2026 14:40:26 +0000 (20:10 +0530)
committerGitHub <noreply@github.com>
Tue, 21 Jul 2026 14:40:26 +0000 (23:40 +0900)
A `\u` escape in `cunescape_one()` was accepted for any 16-bit value
except NUL. That range `0x0000`-`0xFFFF` includes the UTF-16 surrogates
`0xD800`-`0xDFFF`, which are not valid Unicode scalar values and cannot
be encoded as valid UTF-8.

src/basic/escape.c
src/test/test-escape.c

index 0b8f2755e9f57b833535273b8dccf7ed6d8753b9..ecd7ea6b401e1da51220829a22c68c9576758e33 100644 (file)
@@ -202,6 +202,13 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit,
                 if (c == 0 && !accept_nul)
                         return -EINVAL;
 
+                /* Don't allow UTF-16 surrogates, they cannot be encoded as valid UTF-8. Note we
+                 * deliberately do *not* use unichar_is_valid() here (unlike the \U case below):
+                 * it also rejects noncharacters such as U+FFFE, which callers legitimately round-trip
+                 * through \u (e.g. systemd.mount-extra= parsing, see test-fstab-generator). */
+                if (utf16_is_surrogate(c))
+                        return -EINVAL;
+
                 *ret = c;
                 r = 5;
                 break;
index 1ab5b61eb6e0752d28b4ffa8d11bc08a7159162f..86b598920b71677af34c0e813796415bc6730228 100644 (file)
@@ -111,6 +111,21 @@ TEST(cunescape) {
         ASSERT_STREQ(unescaped, "ßßΠA");
         unescaped = mfree(unescaped);
 
+        /* UTF-16 surrogates cannot be encoded as valid UTF-8 and must be rejected */
+        ASSERT_ERROR(cunescape("\\ud800", 0, &unescaped), EINVAL);
+        ASSERT_ERROR(cunescape("\\udfff", 0, &unescaped), EINVAL);
+
+        /* The code points immediately outside the surrogate range must still decode */
+        ASSERT_OK(cunescape("\\ud7ff", 0, &unescaped));
+        unescaped = mfree(unescaped);
+        ASSERT_OK(cunescape("\\ue000", 0, &unescaped));
+        unescaped = mfree(unescaped);
+
+        /* Noncharacters (e.g. U+FFFE) are valid scalar values, encode fine as UTF-8, and are
+         * relied upon by callers (systemd.mount-extra=), so unlike \U they stay accepted here */
+        ASSERT_OK(cunescape("\\ufffe", 0, &unescaped));
+        unescaped = mfree(unescaped);
+
         assert_se(cunescape("\\073", 0, &unescaped) >= 0);
         ASSERT_STREQ(unescaped, ";");
         unescaped = mfree(unescaped);