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.
This commit is contained in:
Armaan Sandhu
2026-07-23 19:16:59 +09:00
committed by Yu Watanabe
parent d336ef5e76
commit 7359f11be6
2 changed files with 29 additions and 8 deletions
+15 -8
View 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 */
+14
View 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) {