win: fix off-by-one in utf-16 to wtf-8 conversion (#5050)
Reserve one byte for the NUL terminator when passing the buffer size
to uv_utf16_to_wtf8() in the TTY line-read path. Without this, when
all input characters encode to exactly 3 UTF-8 bytes (e.g. CJK) and
the buffer size is divisible by 3, the NUL terminator is written one
byte past the allocated buffer.
The other two call sites in src/win/util.c already subtract 1 before
calling uv_utf16_to_wtf8(). This aligns tty.c with that convention.
Fixes commit f3889085 ("win,tty: convert line-read UTF-16 to WTF-8")
from October 2023.
Refs: https://github.com/libuv/libuv/security/advisories/GHSA-4prr-4742-3ccf
This commit is contained in:
parent
d2a45ce364
commit
ec0ab5d77d
@ -555,7 +555,8 @@ static DWORD CALLBACK uv_tty_line_read_thread(void* data) {
|
|||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
if (read_console_success) {
|
if (read_console_success) {
|
||||||
read_bytes = bytes;
|
assert(bytes > 0);
|
||||||
|
read_bytes = bytes - 1;
|
||||||
uv_utf16_to_wtf8(utf16,
|
uv_utf16_to_wtf8(utf16,
|
||||||
read_chars,
|
read_chars,
|
||||||
&handle->tty.rd.read_line_buffer.base,
|
&handle->tty.rd.read_line_buffer.base,
|
||||||
|
|||||||
@ -246,3 +246,47 @@ TEST_IMPL(wtf8) {
|
|||||||
uv_wtf8_to_utf16(input_max, buf, len);
|
uv_wtf8_to_utf16(input_max, buf, len);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_IMPL(utf16_to_wtf8_exact_fill) {
|
||||||
|
/* Regression test for the off-by-one NUL write in uv_utf16_to_wtf8().
|
||||||
|
*
|
||||||
|
* The API contract says target_len_ptr excludes space for the NUL terminator.
|
||||||
|
* The caller must pass (buffer_size - 1) so that the NUL written at
|
||||||
|
* target[target_len] stays in bounds.
|
||||||
|
*
|
||||||
|
* U+4E2D encodes to 3 UTF-8 bytes (0xE4 0xB8 0xAD). With a buffer of size N
|
||||||
|
* (divisible by 3) and N/3 input characters, the worst-case output exactly
|
||||||
|
* fills the data portion. Passing target_len = N - 1 must keep the NUL inside
|
||||||
|
* the buffer, and passing target_len = N would write one byte past the end.
|
||||||
|
*/
|
||||||
|
static const size_t sizes[] = { 3, 6, 48, 96, 192 };
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(sizes); i++) {
|
||||||
|
size_t buf_size = sizes[i];
|
||||||
|
size_t num_chars = buf_size / 3;
|
||||||
|
char mem[200];
|
||||||
|
uint16_t utf16[200];
|
||||||
|
char* target;
|
||||||
|
size_t target_len;
|
||||||
|
size_t j;
|
||||||
|
|
||||||
|
ASSERT_NOT_NULL(mem);
|
||||||
|
ASSERT_NOT_NULL(utf16);
|
||||||
|
|
||||||
|
/* Fill entire region including canary with 0xAA. */
|
||||||
|
memset(mem, 0xAA, buf_size + 1);
|
||||||
|
for (j = 0; j < num_chars; j++)
|
||||||
|
utf16[j] = 0x4E2D; /* U+4E2D (中) — 3-byte UTF-8 */
|
||||||
|
|
||||||
|
/* Correct usage: target_len = buf_size - 1 reserves space for NUL. */
|
||||||
|
target = mem;
|
||||||
|
target_len = buf_size - 1;
|
||||||
|
uv_utf16_to_wtf8(utf16, num_chars, &target, &target_len);
|
||||||
|
|
||||||
|
/* NUL must land inside the buffer; canary byte must be untouched. */
|
||||||
|
ASSERT_EQ((unsigned char) mem[buf_size], 0xAA);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|||||||
@ -586,6 +586,7 @@ TEST_DECLARE (fork_threadpool_queue_work_simple)
|
|||||||
TEST_DECLARE (iouring_pollhup)
|
TEST_DECLARE (iouring_pollhup)
|
||||||
|
|
||||||
TEST_DECLARE (wtf8)
|
TEST_DECLARE (wtf8)
|
||||||
|
TEST_DECLARE (utf16_to_wtf8_exact_fill)
|
||||||
TEST_DECLARE (idna_toascii)
|
TEST_DECLARE (idna_toascii)
|
||||||
TEST_DECLARE (utf8_decode1)
|
TEST_DECLARE (utf8_decode1)
|
||||||
TEST_DECLARE (utf8_decode1_overrun)
|
TEST_DECLARE (utf8_decode1_overrun)
|
||||||
@ -1251,6 +1252,7 @@ TASK_LIST_START
|
|||||||
TEST_ENTRY (iouring_pollhup)
|
TEST_ENTRY (iouring_pollhup)
|
||||||
|
|
||||||
TEST_ENTRY (wtf8)
|
TEST_ENTRY (wtf8)
|
||||||
|
TEST_ENTRY (utf16_to_wtf8_exact_fill)
|
||||||
TEST_ENTRY (utf8_decode1)
|
TEST_ENTRY (utf8_decode1)
|
||||||
TEST_ENTRY (utf8_decode1_overrun)
|
TEST_ENTRY (utf8_decode1_overrun)
|
||||||
TEST_ENTRY (uname)
|
TEST_ENTRY (uname)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user