diff options
| author | Avi Halachmi (:avih) <avihpit@yahoo.com> | 2026-03-22 15:58:31 +0200 |
|---|---|---|
| committer | Ron Yorston <rmy@pobox.com> | 2026-03-25 14:53:20 +0000 |
| commit | 4e7a8bfc75b129b3a671229544f9e5c4168daabf (patch) | |
| tree | f90ba5d38df63bf143e6c0921f1ab71706f18e0a | |
| parent | 8d97f41766c905bf527ee1b08d021fc4ae9d5af3 (diff) | |
| download | busybox-w32-4e7a8bfc75b129b3a671229544f9e5c4168daabf.tar.gz busybox-w32-4e7a8bfc75b129b3a671229544f9e5c4168daabf.tar.bz2 busybox-w32-4e7a8bfc75b129b3a671229544f9e5c4168daabf.zip | |
win32: UTF8_OUTPUT: reject overlong encoding
Overlong UTF8 encoding means that a codepoint is encoded using more
bytes than necessary, and it's disallowed.
Until now we didn't reject it, e.g. 2-bytes sequence of 0xc0 0x9b
was incorrectly decoded as codepoint 0x1b (ESC), but it's overlong
because 1-byte 0x1b is enough for this codepoint.
Now we reject such sequences and print '?' (CONFIG_SUBST_WCHAR).
Additionally, we now also reject 4-bytes sequences which end up above
the maximum valid codepoint value (0x10ffff). No such issue with 1/2/3
bytes UTF-8 because technically only 4 can encode a too big value.
| -rw-r--r-- | win32/winansi.c | 15 |
1 files changed, 13 insertions, 2 deletions
diff --git a/win32/winansi.c b/win32/winansi.c index 53f55d09a..213620991 100644 --- a/win32/winansi.c +++ b/win32/winansi.c | |||
| @@ -1458,6 +1458,7 @@ static int writeCon_utf8(int fd, const char *u8buf, size_t u8siz) | |||
| 1458 | { | 1458 | { |
| 1459 | // state during/between calls | 1459 | // state during/between calls |
| 1460 | static int state = 0; // 0-3: remaining cp bytes (0: done/new) | 1460 | static int state = 0; // 0-3: remaining cp bytes (0: done/new) |
| 1461 | static int tail = 0; // init like state, used for overlong rejection | ||
| 1461 | static uint32_t codepoint = 0; // accumulated from up to 4 UTF8 bytes | 1462 | static uint32_t codepoint = 0; // accumulated from up to 4 UTF8 bytes |
| 1462 | 1463 | ||
| 1463 | // wbuf is not a state, but it's kept between calls to avoid repeated | 1464 | // wbuf is not a state, but it's kept between calls to avoid repeated |
| @@ -1488,15 +1489,25 @@ static int writeCon_utf8(int fd, const char *u8buf, size_t u8siz) | |||
| 1488 | codepoint = c; | 1489 | codepoint = c; |
| 1489 | 1490 | ||
| 1490 | } else if (state > 0 && topbits == 1) { | 1491 | } else if (state > 0 && topbits == 1) { |
| 1491 | // valid continuation byte | 1492 | // valid continuation/final byte (2/3/4 bytes UTF8) |
| 1493 | // min value for 1/2/3/4 bytes UTF-8 (cpmin[0] is unused) | ||
| 1494 | static const uint32_t cpmin[] = {0, 0x80, 0x800, 0x10000}; | ||
| 1495 | |||
| 1492 | codepoint = (codepoint << 6) | (c & 0x3f); | 1496 | codepoint = (codepoint << 6) | (c & 0x3f); |
| 1493 | if (--state) | 1497 | if (--state) |
| 1494 | continue; | 1498 | continue; |
| 1495 | 1499 | ||
| 1500 | // done. ensure codepoint is not too small (overlong | ||
| 1501 | // with 2/3/4 bytes), and not too big (with 4 bytes). | ||
| 1502 | // can be optimized further, with longer explanation: | ||
| 1503 | // if ((codepoint - cpmin[tail]) & ~0xfffffu) ... | ||
| 1504 | if (codepoint < cpmin[tail] || codepoint > 0x10ffff) | ||
| 1505 | codepoint = CONFIG_SUBST_WCHAR; | ||
| 1506 | |||
| 1496 | } else if (state == 0 && topbits >= 2 && topbits <= 4) { | 1507 | } else if (state == 0 && topbits >= 2 && topbits <= 4) { |
| 1497 | // valid UTF8 lead of 2/3/4 bytes codepoint | 1508 | // valid UTF8 lead of 2/3/4 bytes codepoint |
| 1498 | codepoint = c & (0x7f >> topbits); | 1509 | codepoint = c & (0x7f >> topbits); |
| 1499 | state = topbits - 1; // remaining bytes after lead | 1510 | tail = state = topbits - 1; // expected bytes after lead |
| 1500 | continue; | 1511 | continue; |
| 1501 | 1512 | ||
| 1502 | } else { | 1513 | } else { |
