]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fix ellipsis buffer overflow in xescape_full and utf8 escapers
authorjmestwa-coder <jmestwa@gmail.com>
Tue, 9 Jun 2026 05:06:58 +0000 (10:36 +0530)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 17 Jun 2026 06:53:55 +0000 (08:53 +0200)
src/basic/escape.c
src/basic/utf8.c
src/test/test-escape.c
src/test/test-utf8.c

index 6b6612d33bc45aaa104367c0ac091002158d16fa..1ca3519b574f089f221f7a6f966edcc698667095 100644 (file)
@@ -379,12 +379,13 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape
         if (console_width == 0)
                 return strdup("");
 
-        ans = new(char, MIN(strlen(s), console_width) * 4 + 1);
+        size_t body = MIN(strlen(s), console_width) * 4;
+        ans = new(char, body + STRLEN("...") + 1);
         if (!ans)
                 return NULL;
 
-        memset(ans, '_', MIN(strlen(s), console_width) * 4);
-        ans[MIN(strlen(s), console_width) * 4] = 0;
+        memset(ans, '_', body);
+        ans[body] = 0;
 
         bool force_ellipsis = FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS);
 
index edb0ea1ca551341b7e46eedb03542435367748c5..d1a9ff07f8c597e0d37e2f7cd164b02b4a377bfb 100644 (file)
@@ -200,7 +200,8 @@ char* utf8_escape_non_printable_full(const char *str, size_t console_width, bool
         if (console_width == 0)
                 return strdup("");
 
-        p = s = prev_s = malloc(strlen(str) * 4 + 1);
+        size_t body = strlen(str) * 4;
+        p = s = prev_s = malloc(body + STRLEN("…") + 1);
         if (!p)
                 return NULL;
 
index 7162f7ff2dc3c304471f49c087a4172e2cfa565d..1ab5b61eb6e0752d28b4ffa8d11bc08a7159162f 100644 (file)
@@ -63,6 +63,15 @@ TEST(xescape_full) {
         test_xescape_full_one(true);
 }
 
+TEST(xescape_full_ellipsis) {
+        _cleanup_free_ char *t = NULL;
+
+        /* Every byte escapes to four columns, so the escaped output fills strlen*4 bytes and the
+         * forced ellipsis used to be written past the end of the buffer. */
+        assert_se(t = xescape_full("\001\001\001", /* bad= */ NULL, 80, XESCAPE_FORCE_ELLIPSIS));
+        ASSERT_STREQ(t, "\\x01\\x01\\x01...");
+}
+
 TEST(cunescape) {
         _cleanup_free_ char *unescaped = NULL;
 
index ed7625061ac22705b63ff1aab1bcda6d06927568..9ab62976814fff11f599bfdb57213fccb0b363e3 100644 (file)
@@ -167,6 +167,15 @@ TEST(utf8_escape_non_printable_full) {
                 }
 }
 
+TEST(utf8_escape_non_printable_full_ellipsis) {
+        _cleanup_free_ char *p = NULL;
+
+        /* Every byte escapes to four columns, so the escaped output fills strlen*4 bytes and the
+         * forced ellipsis used to be written past the end of the buffer. */
+        assert_se(p = utf8_escape_non_printable_full("\001\001\001", 80, /* force_ellipsis= */ true));
+        ASSERT_STREQ(p, "\\x01\\x01\\x01…");
+}
+
 TEST(utf16_to_utf8) {
         const char16_t utf16[] = { htole16('a'), htole16(0xd800), htole16('b'), htole16(0xdc00), htole16('c'), htole16(0xd801), htole16(0xdc37) };
         static const char utf8[] = { 'a', 'b', 'c', 0xf0, 0x90, 0x90, 0xb7 };