]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
strv: refuse invalid UTF-8 in strv_rebreak_lines()
authorArmaan Sandhu <armaan.sandhu0504@gmail.com>
Thu, 23 Jul 2026 07:31:03 +0000 (13:01 +0530)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 23 Jul 2026 10:16:59 +0000 (19:16 +0900)
The scan pointer was advanced with utf8_next_char(), which blindly skips
utf8_skip_data[lead byte] bytes, so a line ending in a truncated multibyte
sequence like "foo\xF0" stepped over the NUL and the loop read past the end
of the string. Decode each character with utf8_encoded_to_unichar() and
propagate the error instead of measuring broken characters.

Fixes #43052.

src/basic/strv.c
src/test/test-strv.c

index 0be282eb598d82eb44241e6a8d529df4877a67df..eb3cd8d4b213f16d1d770b32d2c1f60b63acefd6 100644 (file)
@@ -8,7 +8,6 @@
 #include "escape.h"
 #include "extract-word.h"
 #include "fileio.h"
-#include "gunicode.h"
 #include "hashmap.h"
 #include "log.h"
 #include "memory-util.h"
@@ -1241,7 +1240,7 @@ int strv_rebreak_lines(char **l, size_t width, char ***ret) {
                 bool in_prefix = true; /* still in the whitespace in the beginning of the line? */
                 size_t w = 0;
 
-                for (const char *p = start; *p != 0; p = utf8_next_char(p)) {
+                for (const char *p = start; *p != 0; ) {
                         if (strchr(NEWLINE, *p)) {
                                 in_prefix = true;
                                 whitespace_begin = whitespace_end = NULL;
@@ -1258,11 +1257,13 @@ int strv_rebreak_lines(char **l, size_t width, char ***ret) {
                                 in_prefix = false;
                         }
 
-                        int cw = utf8_char_console_width(p);
-                        if (cw < 0) {
-                                log_debug_errno(cw, "Comment to line break contains invalid UTF-8, ignoring.");
-                                cw = 1;
-                        }
+                        char32_t c;
+                        int n = utf8_encoded_to_unichar(p, &c);
+                        if (n < 0)
+                                return log_debug_errno(n, "Line to break contains invalid UTF-8, refusing: %m");
+
+                        int cw = unichar_console_width(c);
+                        assert(cw >= 0);
 
                         w += cw;
 
@@ -1277,10 +1278,16 @@ int strv_rebreak_lines(char **l, size_t width, char ***ret) {
                                 if (r < 0)
                                         return r;
 
+                                /* Continue with the next line, starting at the first character after the
+                                 * whitespace we broke at. Note, that character has not been measured for
+                                 * the new line yet, hence reset the width and reprocess it. */
                                 p = start = whitespace_end;
                                 whitespace_begin = whitespace_end = NULL;
-                                w = cw;
+                                w = 0;
+                                continue;
                         }
+
+                        p += n;
                 }
 
                 /* Process rest of the line */
index 386c54f78719eb331cda006e71db23f26f09e86a..53156a94127babb514b0845f3370daa54f75fcd1 100644 (file)
@@ -1267,6 +1267,20 @@ TEST(strv_rebreak_lines) {
 
                 assert_se(strv_equal(a, b));
         }
+
+        /* Invalid UTF-8 is refused rather than measured. Previously a truncated multibyte lead byte at the
+         * end of a line was blind-skipped by its expected length, stepping over the NUL and reading past
+         * the end of the string. */
+        ASSERT_ERROR(strv_rebreak_lines(STRV_MAKE("foo\xF0"), 10, &l), EINVAL);
+        ASSERT_NULL(l);
+
+        ASSERT_ERROR(strv_rebreak_lines(STRV_MAKE("bar\xFC"), 10, &l), EINVAL);
+        ASSERT_NULL(l);
+
+        /* Same, but reached after a line break, i.e. with the scan restarted at the character following
+         * the whitespace we broke at. */
+        ASSERT_ERROR(strv_rebreak_lines(STRV_MAKE("a \xF0" "b"), 3, &l), EINVAL);
+        ASSERT_NULL(l);
 }
 
 TEST(strv_find_closest) {