Skip to content

Commit 84ef8a0

Browse files
committed
src: fix crash when writing odd-length hex string via Writev
StringBytes::StorageSize had a CHECK that fatal-asserted when a hex-encoded string with an odd number of characters was written through Writev (e.g. via HTTP requests which are automatically corked). Writing the same string via a single Write did not crash because StringBytes::Write delegates to HexDecode, which silently drops the trailing incomplete nibble. Remove the CHECK and let integer division handle odd lengths, which is consistent with StringBytes::Size and HexDecode. Fixes: #45150 Signed-off-by: RajeshKumar11 <kakumanurajeshkumar@gmail.com>
1 parent 30a7e28 commit 84ef8a0

2 files changed

Lines changed: 182 additions & 1 deletion

File tree

src/string_bytes.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,6 @@ Maybe<size_t> StringBytes::StorageSize(Isolate* isolate,
472472
break;
473473

474474
case HEX:
475-
CHECK(view.length() % 2 == 0 && "invalid hex string length");
476475
data_size = view.length() / 2;
477476
break;
478477

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('node:assert');
5+
const http = require('node:http');
6+
const net = require('node:net');
7+
8+
// Regression test for https://github.com/nodejs/node/issues/45150
9+
// Writing an odd-length hex string to a stream that batches writes via
10+
// Writev (e.g. HTTP requests that are automatically corked) used to
11+
// fatal-assert in StringBytes::StorageSize. The trailing incomplete nibble
12+
// should be silently dropped, consistent with non-Writev paths.
13+
14+
// Test 1: HTTP POST with a single odd-length hex write.
15+
// "1" has no complete bytes in hex encoding, so the request body is empty.
16+
{
17+
const server = http.createServer(common.mustCall((req, res) => {
18+
const chunks = [];
19+
req.on('data', (chunk) => chunks.push(chunk));
20+
req.on('end', common.mustCall(() => {
21+
assert.strictEqual(Buffer.concat(chunks).length, 0);
22+
res.end();
23+
server.close();
24+
}));
25+
}));
26+
27+
server.listen(0, common.mustCall(() => {
28+
const req = http.request({
29+
port: server.address().port,
30+
method: 'POST',
31+
}, common.mustCall((res) => {
32+
res.resume();
33+
}));
34+
req.write('1', 'hex');
35+
req.end();
36+
}));
37+
}
38+
39+
// Test 2: HTTP POST with cork/uncork and mixed odd-length hex writes.
40+
// "ff1" (3 hex chars) decodes to 1 byte (0xff); the trailing "1" nibble is
41+
// dropped. "1" (1 hex char) decodes to 0 bytes.
42+
{
43+
const server = http.createServer(common.mustCall((req, res) => {
44+
const chunks = [];
45+
req.on('data', (chunk) => chunks.push(chunk));
46+
req.on('end', common.mustCall(() => {
47+
assert.deepStrictEqual(Buffer.concat(chunks), Buffer.from([0xff]));
48+
res.end();
49+
server.close();
50+
}));
51+
}));
52+
53+
server.listen(0, common.mustCall(() => {
54+
const req = http.request({
55+
port: server.address().port,
56+
method: 'POST',
57+
}, common.mustCall((res) => {
58+
res.resume();
59+
}));
60+
req.cork();
61+
req.write('ff1', 'hex');
62+
req.write('1', 'hex');
63+
req.uncork();
64+
req.end();
65+
}));
66+
}
67+
68+
// Test 3: net socket with cork/uncork and an odd-length hex write.
69+
// Exercises the Writev path directly at the net layer.
70+
{
71+
const server = net.createServer(common.mustCall((socket) => {
72+
const chunks = [];
73+
socket.on('data', (chunk) => chunks.push(chunk));
74+
socket.on('end', common.mustCall(() => {
75+
assert.deepStrictEqual(Buffer.concat(chunks), Buffer.from([0xff]));
76+
server.close();
77+
}));
78+
socket.resume();
79+
}));
80+
81+
server.listen(0, common.mustCall(() => {
82+
const conn = net.createConnection(server.address().port);
83+
conn.on('connect', common.mustCall(() => {
84+
conn.cork();
85+
conn.write('ff', 'hex');
86+
conn.write('1', 'hex');
87+
conn.uncork();
88+
conn.end();
89+
}));
90+
}));
91+
}
92+
'use strict';
93+
94+
const common = require('../common');
95+
const assert = require('node:assert');
96+
const http = require('node:http');
97+
const net = require('node:net');
98+
99+
// Regression test for https://github.com/nodejs/node/issues/45150
100+
// Writing an odd-length hex string to a stream that batches writes via
101+
// Writev (e.g. HTTP requests that are automatically corked) used to
102+
// fatal-assert in StringBytes::StorageSize. The trailing incomplete nibble
103+
// should be silently dropped, consistent with non-Writev paths.
104+
105+
// Test 1: HTTP POST with a single odd-length hex write.
106+
// "1" has no complete bytes in hex encoding, so the request body is empty.
107+
{
108+
const server = http.createServer(common.mustCall((req, res) => {
109+
const chunks = [];
110+
req.on('data', (chunk) => chunks.push(chunk));
111+
req.on('end', common.mustCall(() => {
112+
assert.strictEqual(Buffer.concat(chunks).length, 0);
113+
res.end();
114+
server.close();
115+
}));
116+
}));
117+
118+
server.listen(0, common.mustCall(() => {
119+
const req = http.request({
120+
port: server.address().port,
121+
method: 'POST',
122+
}, common.mustCall((res) => {
123+
res.resume();
124+
}));
125+
req.write('1', 'hex');
126+
req.end();
127+
}));
128+
}
129+
130+
// Test 2: HTTP POST with cork/uncork and mixed odd-length hex writes.
131+
// "ff1" (3 hex chars) decodes to 1 byte (0xff); the trailing "1" nibble is
132+
// dropped. "1" (1 hex char) decodes to 0 bytes.
133+
{
134+
const server = http.createServer(common.mustCall((req, res) => {
135+
const chunks = [];
136+
req.on('data', (chunk) => chunks.push(chunk));
137+
req.on('end', common.mustCall(() => {
138+
assert.deepStrictEqual(Buffer.concat(chunks), Buffer.from([0xff]));
139+
res.end();
140+
server.close();
141+
}));
142+
}));
143+
144+
server.listen(0, common.mustCall(() => {
145+
const req = http.request({
146+
port: server.address().port,
147+
method: 'POST',
148+
}, common.mustCall((res) => {
149+
res.resume();
150+
}));
151+
req.cork();
152+
req.write('ff1', 'hex');
153+
req.write('1', 'hex');
154+
req.uncork();
155+
req.end();
156+
}));
157+
}
158+
159+
// Test 3: net socket with cork/uncork and an odd-length hex write.
160+
// Exercises the Writev path directly at the net layer.
161+
{
162+
const server = net.createServer(common.mustCall((socket) => {
163+
const chunks = [];
164+
socket.on('data', (chunk) => chunks.push(chunk));
165+
socket.on('end', common.mustCall(() => {
166+
assert.deepStrictEqual(Buffer.concat(chunks), Buffer.from([0xff]));
167+
server.close();
168+
}));
169+
socket.resume();
170+
}));
171+
172+
server.listen(0, common.mustCall(() => {
173+
const conn = net.createConnection(server.address().port);
174+
conn.on('connect', common.mustCall(() => {
175+
conn.cork();
176+
conn.write('ff', 'hex');
177+
conn.write('1', 'hex');
178+
conn.uncork();
179+
conn.end();
180+
}));
181+
}));
182+
}

0 commit comments

Comments
 (0)