https://sourceware.org/bugzilla/show_bug.cgi?id=34902
Bug ID: 34902
Summary: strings -U: invalid UTF-8 (overlong, surrogates, >
U+10FFFF, F8-FF leads) is accepted as characters, and
-U locale prints only the first byte of each multibyte
character
Product: binutils
Version: 2.48 (HEAD)
Status: UNCONFIRMED
Severity: normal
Priority: P2
Component: binutils
Assignee: unassigned at sourceware dot org
Reporter: martin.mirchev at u dot nus.edu
Target Milestone: ---
The --unicode documentation for strings says the option "Controls the display
of UTF-8 encoded multibyte characters": invalid treats them as non-graphic,
and locale/hex/escape/highlight treat them as string characters and display
them in the locale, as <hex>, or as \uxxxx. There are two problems.
1. Invalid UTF-8 is accepted as characters.
is_valid_utf8() in binutils/strings.c (and the same inline checks in
print_unicode_stream_body) only checks the continuation-byte bit patterns.
The length comes from bits 0x20/0x10 of the lead byte, so these are all
treated as valid:
- overlong forms: C0 80, C1 BF, E0 80 AF, F0 80 80 AF
- UTF-16 surrogates: ED A0 80 (U+D800), ED BF BF (U+DFFF)
- code points above U+10FFFF: F4 90 80 80, F7 BF BF BF
- lead bytes F8-FF, which are read as 4-byte sequences
These bytes become part of the reported string (hex shows them as one
character), and escape prints code points that cannot exist, e.g.
"abcd\u0000efgh" for C0 80, "\ud800" for a surrogate, "ᄀ00" and
"ff" above the Unicode range. In escape mode an overlong sequence
shows as the character it spells: E0 80 AF prints as / ("/"). nm.c,
objdump.c and readelf.c have checks with the same structure for their
--unicode options.
2. -U locale writes only the lead byte.
In display_utf8_char() the locale case is
case unicode_locale:
printf ("%.1s", buffer);
which writes one byte, while the function returns the full sequence length,
so the continuation bytes are skipped. So "naïve café €10" (valid UTF-8) is
printed as "na\xc3ve caf\xc3 \xe210". The output is never valid UTF-8 if the
input contains any multibyte character, in any locale (checked with LC_ALL=C
and C.UTF-8). nm's locale mode copies all the bytes. Ubuntu's 2.38 strings
gives the same output, so this has been the case since --unicode was added.
Tested on master 1ec1cee48577c7b1960ddce5a6054b3f9fab6ef5 (GNU strings
2.47.50.20261027), Ubuntu 22.04, gcc 11 -g -O2. There are no sanitizer
findings: ASan+UBSan is clean on all inputs.
Attached (all files in strings-U-utf8-validation.tar.gz): gen.py writes one
file per case ("abcd" + sequence + "efgh"),
all.bin (the cases separated by NULs) and utf8.s (the same bytes in .data,
for the section-buffer path). Also valid.txt and run.sh.
Each sequence was also checked with Python's bytes.decode("utf-8"); the
column after the name shows the result.
case decode -U e -U x
valid-2byte-U+00E9 valid abcdéefgh abcd<0xc3a9>efgh
valid-4byte-U+1F600 valid abcdǶ00efgh abcd<0xf09f9880>efgh
overlong-C0-80-U+0000 INVALID abcd\u0000efgh abcd<0xc080>efgh
overlong-C1-BF-U+007F INVALID abcd\u007fefgh abcd<0xc1bf>efgh
overlong-E0-80-AF-slash INVALID abcd/efgh abcd<0xe080af>efgh
overlong-F0-80-80-AF INVALID abcd\u00002fefgh
abcd<0xf08080af>efgh
surrogate-ED-A0-80-D800 INVALID abcd\ud800efgh abcd<0xeda080>efgh
surrogate-ED-BF-BF-DFFF INVALID abcd\udfffefgh abcd<0xedbfbf>efgh
above-F4-90-80-80-110000 INVALID abcdᄀ00efgh abcd<0xf4908080>efgh
above-F7-BF-BF-BF INVALID abcdffefgh abcd<0xf7bfbfbf>efgh
lead-F8-80-80-80 INVALID abcd\u000000efgh
abcd<0xf8808080>efgh
lead-FF-BF-BF-BF INVALID abcdffefgh abcd<0xffbfbfbf>efgh
(strings -a -U e FILE / strings -a -U x FILE.) For the invalid rows one
would expect "abcd" and "efgh" as two separate strings, as -U i prints. The
section-buffer path (strings -d -U x utf8.o, via print_unicode_buffer) gives
the same twelve hex lines.
$ od -An -tx1 valid.txt
6e 61 c3 af 76 65 20 63 61 66 c3 a9 20 e2 82 ac 31 30 0a
$ strings -a -U l valid.txt | od -An -tx1
6e 61 c3 76 65 20 63 61 66 c3 20 e2 31 30 0a
A likely fix is to reject C0, C1 and F5-FF leads, overlong E0/F0 second
bytes, ED A0-BF and F4 90+ in one shared check, and to write all utf8_len
bytes in the locale case.
This issue was found by an LLM-Enhanced Static Analysis Engine and verified
manually.
--
You are receiving this mail because:
You are on the CC list for the bug.