[PATCH 2/4] strings: skip invalid UTF-8 encodings
Alice Carlotti
alice.carlotti@arm.com
Thu Sep 25 18:08:01 GMT 2025
Add more rigorous checks to is_valid_utf8. Previously we would
incorrectly accept:
- overlong encodings
- out of range encodings (greater than 0x10ffff)
- UTF-16 surrogate pair encoding values
- 4 byte encodings with bit pattern 0b11111... in the leading byte
Since this requires computing the codepoint, save it in an out parameter
so that the callers can pass it in to display_utf8_char.
Additionally, change the escape sequence format for 4-byte encodings
from \uxxxxxx to \Uxxxxxxxx.
diff --git a/binutils/strings.c b/binutils/strings.c
index a188564db0c7995e5dcdb2e0830e7d08b5ddeae4..b39001711cd9747571c20cef3ede6a50a4a294de 100644
--- a/binutils/strings.c
+++ b/binutils/strings.c
@@ -671,65 +671,79 @@ print_filename_and_address (const char * filename, file_ptr address)
}
}
-/* Return non-zero if the bytes starting at BUFFER form a valid UTF-8 encoding.
- If the encoding is valid then returns the number of bytes it uses. */
+/* Return non-zero if the bytes starting at BUFFER form a valid non-ASCII UTF-8
+ encoding. If the encoding is valid then return the number of bytes used.
+ If CODEPOINT is non-NULL, set it to the decoded character number.
+ */
static unsigned int
-is_valid_utf8 (const unsigned char * buffer, unsigned long buflen)
+is_valid_utf8 (const unsigned char * buffer, unsigned long buflen,
+ unsigned int * codepoint)
{
- if (buffer[0] < 0xc0)
- return 0;
-
- if (buflen < 2)
- return 0;
-
- if ((buffer[1] & 0xc0) != 0x80)
- return 0;
-
- if ((buffer[0] & 0x20) == 0)
- return 2;
-
- if (buflen < 3)
- return 0;
-
- if ((buffer[2] & 0xc0) != 0x80)
- return 0;
-
- if ((buffer[0] & 0x10) == 0)
- return 3;
-
- if (buflen < 4)
- return 0;
+ unsigned int utf8_len;
+ unsigned int value;
- if ((buffer[3] & 0xc0) != 0x80)
+ if ((buffer[0] & 0xc0) != 0xc0)
return 0;
- return 4;
-}
-
-/* Display a UTF-8 encoded character in BUFFER according to the setting
- of unicode_display. The character is known to be valid.
- Returns the number of bytes consumed. */
-
-static unsigned int
-display_utf8_char (const unsigned char * buffer)
-{
- unsigned int j;
- unsigned int utf8_len;
-
switch (buffer[0] & 0x30)
{
case 0x00:
case 0x10:
utf8_len = 2;
+ value = buffer[0] & 0x1f;
break;
case 0x20:
utf8_len = 3;
+ value = buffer[0] & 0x0f;
break;
- default:
+ default: /* 0x30 */
utf8_len = 4;
+ value = buffer[0] & 0x0f; /* The top bit is checked later. */
+ }
+
+ if (utf8_len > buflen)
+ return 0;
+
+ for (unsigned int i = 1; i < utf8_len; i++)
+ {
+ if ((buffer[i] & 0xc0) != 0x80)
+ return 0;
+ value = (value << 6) | (buffer[i] & 0x3f);
}
+ /* Check for overlong encodings, UTF-16 surrogate pair encodings, or out of
+ range values. */
+ switch (utf8_len)
+ {
+ case 2:
+ if (value < 0x80)
+ return 0;
+ break;
+ case 3:
+ if (value < 0x800 || (value >= 0xd800 && value <= 0xdfff))
+ return 0;
+ break;
+ default:
+ if (value < 0x10000 || value > 0x10ffff)
+ return 0;
+ }
+
+ if (codepoint)
+ * codepoint = value;
+ return utf8_len;
+}
+
+/* Display a character with Unicode character number CODEPOINT, and a UTF-8
+ encoding of length UTF8_LEN stored in BUFFER, according to the setting of
+ unicode_display. */
+
+static void
+display_utf8_char (const unsigned char * buffer, unsigned int utf8_len,
+ unsigned int codepoint)
+{
+ unsigned int j;
+
switch (unicode_display)
{
default:
@@ -740,32 +754,10 @@ display_utf8_char (const unsigned char * buffer)
case unicode_highlight:
if (unicode_display == unicode_highlight && isatty (1))
printf ("\x1B[31;47m"); /* Red. */
-
- switch (utf8_len)
- {
- case 2:
- printf ("\\u%02x%02x",
- ((buffer[0] & 0x1c) >> 2),
- ((buffer[0] & 0x03) << 6) | (buffer[1] & 0x3f));
- break;
-
- case 3:
- printf ("\\u%02x%02x",
- ((buffer[0] & 0x0f) << 4) | ((buffer[1] & 0x3c) >> 2),
- ((buffer[1] & 0x03) << 6) | ((buffer[2] & 0x3f)));
- break;
-
- case 4:
- printf ("\\u%02x%02x%02x",
- ((buffer[0] & 0x07) << 2) | ((buffer[1] & 0x30) >> 4),
- ((buffer[1] & 0x0f) << 4) | ((buffer[2] & 0x3c) >> 2),
- ((buffer[2] & 0x03) << 6) | ((buffer[3] & 0x3f)));
- break;
- default:
- /* URG. */
- break;
- }
-
+ if (codepoint > 0xffff)
+ printf("\\U%08x", codepoint);
+ else
+ printf("\\u%04x", codepoint);
if (unicode_display == unicode_highlight && isatty (1))
printf ("\033[0m"); /* Default colour. */
break;
@@ -782,8 +774,6 @@ display_utf8_char (const unsigned char * buffer)
printf ("%.1s", buffer);
break;
}
-
- return utf8_len;
}
/* Display strings in BUFFER. Treat any UTF-8 encoded characters encountered
@@ -845,7 +835,7 @@ print_unicode_buffer (const char * filename,
continue;
}
- if ((char_len = is_valid_utf8 (buffer + i, buflen - i)) == 0)
+ if ((char_len = is_valid_utf8 (buffer + i, buflen - i, NULL)) == 0)
{
char_len = 1;
num_found = 0;
@@ -887,12 +877,19 @@ print_unicode_buffer (const char * filename,
break;
else if (c < 127)
putchar (c);
- else if (! is_valid_utf8 (buffer + i, buflen - i))
- break;
else if (unicode_display == unicode_invalid)
break;
else
- char_len = display_utf8_char (buffer + i);
+ {
+ unsigned int codepoint;
+ char_len = is_valid_utf8 (buffer + i, buflen - i, &codepoint);
+ if (char_len == 0)
+ {
+ char_len = 1;
+ break;
+ }
+ display_utf8_char (buffer + i, char_len, codepoint);
+ }
}
if (output_separator)
@@ -970,7 +967,14 @@ print_unicode_stream_body (const char * filename,
if (print_buf[i] < 127)
putchar (print_buf[i++]);
else
- i += display_utf8_char (print_buf + i);
+ {
+ unsigned int codepoint;
+ unsigned int utf8_len;
+ utf8_len = is_valid_utf8 (print_buf + i, num_print - i,
+ & codepoint);
+ display_utf8_char (print_buf + i, utf8_len, codepoint);
+ i += utf8_len;
+ }
}
}
@@ -1022,7 +1026,8 @@ print_unicode_stream_body (const char * filename,
}
utf8[i] = c;
}
- if (is_valid_utf8 (utf8, utf8_len) == 0)
+ unsigned int codepoint;
+ if (is_valid_utf8 (utf8, utf8_len, & codepoint) == 0)
{
/* Invalid UTF-8, but last byte read was a continuation byte. */
goto restart_next;
@@ -1030,7 +1035,7 @@ print_unicode_stream_body (const char * filename,
/* Valid UTF-8. */
num_chars++;
if (num_chars > string_min)
- display_utf8_char (utf8);
+ display_utf8_char (utf8, utf8_len, codepoint);
else
for (unsigned int i = 0; i < utf8_len; i++)
print_buf[num_print++] = utf8[i];
More information about the Binutils
mailing list