test: add regression test for off-by-one NUL in uv_utf16_to_wtf8

Verify that when the caller correctly passes target_len = buf_size - 1
(reserving space for the NUL terminator), the NUL stays within the
buffer boundary. Uses U+4E2D (3-byte UTF-8) input to exercise the
worst-case exact-fill path across multiple buffer sizes divisible by 3.
This commit is contained in:
Ali Raza 2026-03-09 19:47:49 +05:00
parent 60e67eb7d7
commit 05c2113b5d
2 changed files with 49 additions and 0 deletions

View File

@ -246,3 +246,50 @@ TEST_IMPL(wtf8) {
uv_wtf8_to_utf16(input_max, buf, len);
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 = malloc(buf_size + 1);
uint16_t* utf16 = malloc(num_chars * sizeof(*utf16));
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);
free(utf16);
free(mem);
}
return 0;
}

View File

@ -586,6 +586,7 @@ TEST_DECLARE (fork_threadpool_queue_work_simple)
TEST_DECLARE (iouring_pollhup)
TEST_DECLARE (wtf8)
TEST_DECLARE (utf16_to_wtf8_exact_fill)
TEST_DECLARE (idna_toascii)
TEST_DECLARE (utf8_decode1)
TEST_DECLARE (utf8_decode1_overrun)
@ -1251,6 +1252,7 @@ TASK_LIST_START
TEST_ENTRY (iouring_pollhup)
TEST_ENTRY (wtf8)
TEST_ENTRY (utf16_to_wtf8_exact_fill)
TEST_ENTRY (utf8_decode1)
TEST_ENTRY (utf8_decode1_overrun)
TEST_ENTRY (uname)