Skip to content

Commit c14e98b

Browse files
committed
dgram: copy the list in send
This commit fix a possible crash situation in dgram send(). A crash is possible if an array is passed, and then altered after the send call, as the call to libuv is wrapped in process.nextTick(). It also avoid sending an empty array to libuv by allocating an empty buffer. It also does some cleanup inside send() to increase readability. It removes test flakyness by use common.mustCall and common.platformTimeout. Fixes situations were some events were not asserted to be emitted. Fixes: #6616 PR-URL: #6804 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]>
1 parent f9ea52e commit c14e98b

7 files changed

+105
-38
lines changed

lib/dgram.js

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -263,16 +263,20 @@ function sliceBuffer(buffer, offset, length) {
263263
}
264264

265265

266-
function fixBuffer(buffer) {
267-
for (var i = 0, l = buffer.length; i < l; i++) {
268-
var buf = buffer[i];
266+
function fixBufferList(list) {
267+
const newlist = new Array(list.length);
268+
269+
for (var i = 0, l = list.length; i < l; i++) {
270+
var buf = list[i];
269271
if (typeof buf === 'string')
270-
buffer[i] = Buffer.from(buf);
272+
newlist[i] = Buffer.from(buf);
271273
else if (!(buf instanceof Buffer))
272-
return false;
274+
return null;
275+
else
276+
newlist[i] = buf;
273277
}
274278

275-
return true;
279+
return newlist;
276280
}
277281

278282

@@ -306,7 +310,8 @@ Socket.prototype.send = function(buffer,
306310
port,
307311
address,
308312
callback) {
309-
var self = this;
313+
const self = this;
314+
let list;
310315

311316
if (address || (port && typeof port !== 'function')) {
312317
buffer = sliceBuffer(buffer, offset, length);
@@ -318,13 +323,13 @@ Socket.prototype.send = function(buffer,
318323

319324
if (!Array.isArray(buffer)) {
320325
if (typeof buffer === 'string') {
321-
buffer = [ Buffer.from(buffer) ];
326+
list = [ Buffer.from(buffer) ];
322327
} else if (!(buffer instanceof Buffer)) {
323328
throw new TypeError('First argument must be a buffer or a string');
324329
} else {
325-
buffer = [ buffer ];
330+
list = [ buffer ];
326331
}
327-
} else if (!fixBuffer(buffer)) {
332+
} else if (!(list = fixBufferList(buffer))) {
328333
throw new TypeError('Buffer list arguments must be buffers or strings');
329334
}
330335

@@ -342,20 +347,23 @@ Socket.prototype.send = function(buffer,
342347
if (self._bindState == BIND_STATE_UNBOUND)
343348
self.bind({port: 0, exclusive: true}, null);
344349

350+
if (list.length === 0)
351+
list.push(Buffer.allocUnsafe(0));
352+
345353
// If the socket hasn't been bound yet, push the outbound packet onto the
346354
// send queue and send after binding is complete.
347355
if (self._bindState != BIND_STATE_BOUND) {
348-
enqueue(self, [buffer, port, address, callback]);
356+
enqueue(self, [list, port, address, callback]);
349357
return;
350358
}
351359

352360
self._handle.lookup(address, function afterDns(ex, ip) {
353-
doSend(ex, self, ip, buffer, address, port, callback);
361+
doSend(ex, self, ip, list, address, port, callback);
354362
});
355363
};
356364

357365

358-
function doSend(ex, self, ip, buffer, address, port, callback) {
366+
function doSend(ex, self, ip, list, address, port, callback) {
359367
if (ex) {
360368
if (typeof callback === 'function') {
361369
callback(ex);
@@ -369,16 +377,16 @@ function doSend(ex, self, ip, buffer, address, port, callback) {
369377
}
370378

371379
var req = new SendWrap();
372-
req.buffer = buffer; // Keep reference alive.
380+
req.list = list; // Keep reference alive.
373381
req.address = address;
374382
req.port = port;
375383
if (callback) {
376384
req.callback = callback;
377385
req.oncomplete = afterSend;
378386
}
379387
var err = self._handle.send(req,
380-
buffer,
381-
buffer.length,
388+
list,
389+
list.length,
382390
port,
383391
ip,
384392
!!callback);

src/udp_wrap.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ void UDPWrap::DoSend(const FunctionCallbackInfo<Value>& args, int family) {
257257
args.Holder(),
258258
args.GetReturnValue().Set(UV_EBADF));
259259

260-
// send(req, buffer, port, address, hasCallback)
260+
// send(req, list, port, address, hasCallback)
261261
CHECK(args[0]->IsObject());
262262
CHECK(args[1]->IsArray());
263263
CHECK(args[2]->IsUint32());
Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
'use strict';
2-
var common = require('../common');
3-
var assert = require('assert');
42

5-
var dgram = require('dgram');
6-
var client, timer, buf, len, offset;
3+
const common = require('../common');
4+
const assert = require('assert');
75

6+
const dgram = require('dgram');
7+
const client = dgram.createSocket('udp4');
88

9-
client = dgram.createSocket('udp4');
10-
11-
buf = Buffer.allocUnsafe(256);
12-
offset = 20;
13-
14-
len = buf.length - offset;
9+
const timer = setTimeout(function() {
10+
throw new Error('Timeout');
11+
}, common.platformTimeout(200));
1512

13+
const buf = Buffer.allocUnsafe(256);
14+
const offset = 20;
15+
const len = buf.length - offset;
1616

17-
client.send(buf, offset, len, common.PORT, '127.0.0.1', function(err, bytes) {
17+
const messageSent = common.mustCall(function messageSent(err, bytes) {
1818
assert.notEqual(bytes, buf.length);
1919
assert.equal(bytes, buf.length - offset);
2020
clearTimeout(timer);
2121
client.close();
2222
});
2323

24-
timer = setTimeout(function() {
25-
throw new Error('Timeout');
26-
}, 200);
24+
client.send(buf, offset, len, common.PORT, '127.0.0.1', messageSent);

test/parallel/test-dgram-send-callback-multi-buffer.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,22 @@ const timer = setTimeout(function() {
1010
throw new Error('Timeout');
1111
}, common.platformTimeout(200));
1212

13-
const onMessage = common.mustCall(function(err, bytes) {
13+
const messageSent = common.mustCall(function messageSent(err, bytes) {
1414
assert.equal(bytes, buf1.length + buf2.length);
1515
clearTimeout(timer);
16-
client.close();
1716
});
1817

1918
const buf1 = Buffer.alloc(256, 'x');
2019
const buf2 = Buffer.alloc(256, 'y');
2120

2221
client.on('listening', function() {
23-
client.send([buf1, buf2], common.PORT, common.localhostIPv4, onMessage);
22+
client.send([buf1, buf2], common.PORT, common.localhostIPv4, messageSent);
2423
});
2524

26-
client.on('message', function(buf, info) {
25+
client.on('message', common.mustCall(function onMessage(buf, info) {
2726
const expected = Buffer.concat([buf1, buf2]);
2827
assert.ok(buf.equals(expected), 'message was received correctly');
2928
client.close();
30-
});
29+
}));
3130

3231
client.bind(common.PORT);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const dgram = require('dgram');
6+
7+
if (process.platform === 'darwin') {
8+
common.skip('because of 17894467 Apple bug');
9+
return;
10+
}
11+
12+
const client = dgram.createSocket('udp4');
13+
14+
const timer = setTimeout(function() {
15+
throw new Error('Timeout');
16+
}, common.platformTimeout(200));
17+
18+
client.on('message', common.mustCall(function onMessage(buf, info) {
19+
const expected = Buffer.alloc(0);
20+
assert.ok(buf.equals(expected), 'message was received correctly');
21+
clearTimeout(timer);
22+
client.close();
23+
}));
24+
25+
client.on('listening', function() {
26+
client.send([], common.PORT, common.localhostIPv4);
27+
});
28+
29+
client.bind(common.PORT);

test/parallel/test-dgram-send-empty-buffer.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ const client = dgram.createSocket('udp4');
1111

1212
client.bind(common.PORT);
1313

14-
client.on('message', function(buffer, bytes) {
14+
client.on('message', common.mustCall(function onMessage(buffer, bytes) {
1515
clearTimeout(timer);
1616
client.close();
17-
});
17+
}));
1818

1919
const buf = Buffer.alloc(0);
2020
client.send(buf, 0, 0, common.PORT, '127.0.0.1', function(err, len) { });
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const dgram = require('dgram');
6+
7+
const client = dgram.createSocket('udp4');
8+
9+
const timer = setTimeout(function() {
10+
throw new Error('Timeout');
11+
}, common.platformTimeout(200));
12+
13+
const onMessage = common.mustCall(function(err, bytes) {
14+
assert.equal(bytes, buf1.length + buf2.length);
15+
clearTimeout(timer);
16+
});
17+
18+
const buf1 = Buffer.alloc(256, 'x');
19+
const buf2 = Buffer.alloc(256, 'y');
20+
21+
client.on('listening', function() {
22+
const toSend = [buf1, buf2];
23+
client.send(toSend, common.PORT, common.localhostIPv4, onMessage);
24+
toSend.splice(0, 2);
25+
});
26+
27+
client.on('message', common.mustCall(function onMessage(buf, info) {
28+
const expected = Buffer.concat([buf1, buf2]);
29+
assert.ok(buf.equals(expected), 'message was received correctly');
30+
client.close();
31+
}));
32+
33+
client.bind(common.PORT);

0 commit comments

Comments
 (0)