From: Armaan Sandhu <74664101+Ar-maan05@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:40:26 +0000 (+0530) Subject: escape: reject UTF-16 surrogates in \u escapes in cunescape_one() (#43079) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eb0326707078a67e0b98341176671f99fbf83ee5;p=thirdparty%2Fsystemd.git escape: reject UTF-16 surrogates in \u escapes in cunescape_one() (#43079) 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. --- diff --git a/src/basic/escape.c b/src/basic/escape.c index 0b8f2755e9f..ecd7ea6b401 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -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; diff --git a/src/test/test-escape.c b/src/test/test-escape.c index 1ab5b61eb6e..86b598920b7 100644 --- a/src/test/test-escape.c +++ b/src/test/test-escape.c @@ -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);