From: Armaan Sandhu Date: Thu, 23 Jul 2026 07:31:03 +0000 (+0530) Subject: strv: refuse invalid UTF-8 in strv_rebreak_lines() X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=7359f11be6958b6b8e6d021dda24ebe6fae3b994;p=thirdparty%2Fsystemd.git strv: refuse invalid UTF-8 in strv_rebreak_lines() 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. --- diff --git a/src/basic/strv.c b/src/basic/strv.c index 0be282eb598..eb3cd8d4b21 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -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 */ diff --git a/src/test/test-strv.c b/src/test/test-strv.c index 386c54f7871..53156a94127 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -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) {