Possible out-of-bounds read disclosed over the network in SocketWrap::Send
I found a possible OOB read (CWE-125) in SocketWrap::Send. The method takes a JS Buffer plus
an offset and length, both unsigned integers fully controlled by the caller, and forms the
send pointer as node::Buffer::Data(buffer) + offset, then hands data/length to sendto()
— with no check that offset or offset + length stay within the Buffer's actual size. A caller
supplying an offset/length beyond the Buffer bounds makes the kernel read length bytes
starting past (or before, via wrap) the Buffer's backing store, and those out-of-bounds bytes are
transmitted to the destination address (also caller-supplied), disclosing adjacent heap memory
over the wire. Compare CreateChecksum in the same file, which validates offset < length and
new_length <= length, and SetOption, which checks len > Buffer::Length — Send does neither.
File: src/raw.cc
Function: SocketWrap::Send
buffer = Nan::To<Object>(info[0]).ToLocalChecked();
offset = Nan::To<Uint32>(info[1]).ToLocalChecked()->Value();
length = Nan::To<Uint32>(info[2]).ToLocalChecked()->Value();
data = node::Buffer::Data (buffer) + offset;
...
rc = sendto (socket->poll_fd_, data, length, 0,
(struct sockaddr *) &addr, sizeof (addr));
- Only the argument types are validated (Buffer, Uint32, Uint32, String, Function). The
numeric offset and length values are never bounds-checked against
node::Buffer::Length(buffer).
data = node::Buffer::Data(buffer) + offset; advances the pointer by an unchecked offset,
which may already point past the end of the backing store.
sendto(fd, data, length, ...) reads length bytes from data. With offset/length
chosen beyond the buffer, this reads out-of-bounds heap memory.
- The bytes read are sent to the caller-controlled address (
info[3]), so the over-read is an
information disclosure, not merely a crash.
JS trigger (if applicable):
Note: the OOB is not reachable through the public Socket.prototype.send. That wrapper
(index.js) validates the range before calling the binding:
// index.js — Socket.prototype.send
if (length + offset > buffer.length)
throw new RangeError ("Length and offset must be within the given buffer");
so s.send(buf, 0, 4096, ...) throws in JS and never reaches SocketWrap::Send. The over-read is
only reachable by calling the native binding method directly, bypassing the wrapper:
// Direct-binding path only (bypasses the public index.js validation):
const raw = require('bindings')('raw');
const wrap = new raw.SocketWrap(raw.Protocol.None, raw.AddressFamily.IPv4);
const buf = Buffer.alloc(16);
wrap.send(buf, 0, 4096, sockaddrBuf, () => {}); // offset+length exceed 16-byte buffer -> OOB read
Reachability
Raw-binding reachable only. The public Socket.prototype.send bounds-checks
length + offset > buffer.length and throws, so the disclosure cannot be triggered via the
supported API — only via a direct SocketWrap.send(...) call on the internal binding.
Suggested fix: before computing data, validate the range in native code too, e.g.
if (offset > bufLen || length > bufLen - offset) { throw RangeError; } where
bufLen = node::Buffer::Length(buffer), mirroring the checks in CreateChecksum/SetOption (so
the binding is memory-safe independent of the JS wrapper).
Possible out-of-bounds read disclosed over the network in
SocketWrap::SendI found a possible OOB read (CWE-125) in
SocketWrap::Send. The method takes a JS Buffer plusan
offsetandlength, both unsigned integers fully controlled by the caller, and forms thesend pointer as
node::Buffer::Data(buffer) + offset, then handsdata/lengthtosendto()— with no check that
offsetoroffset + lengthstay within the Buffer's actual size. A callersupplying an
offset/lengthbeyond the Buffer bounds makes the kernel readlengthbytesstarting past (or before, via wrap) the Buffer's backing store, and those out-of-bounds bytes are
transmitted to the destination address (also caller-supplied), disclosing adjacent heap memory
over the wire. Compare
CreateChecksumin the same file, which validatesoffset < lengthandnew_length <= length, andSetOption, which checkslen > Buffer::Length—Senddoes neither.File:
src/raw.ccFunction:
SocketWrap::Sendnumeric
offsetandlengthvalues are never bounds-checked againstnode::Buffer::Length(buffer).data = node::Buffer::Data(buffer) + offset;advances the pointer by an uncheckedoffset,which may already point past the end of the backing store.
sendto(fd, data, length, ...)readslengthbytes fromdata. Withoffset/lengthchosen beyond the buffer, this reads out-of-bounds heap memory.
info[3]), so the over-read is aninformation disclosure, not merely a crash.
JS trigger (if applicable):
Note: the OOB is not reachable through the public
Socket.prototype.send. That wrapper(
index.js) validates the range before calling the binding:so
s.send(buf, 0, 4096, ...)throws in JS and never reachesSocketWrap::Send. The over-read isonly reachable by calling the native binding method directly, bypassing the wrapper:
Reachability
Raw-binding reachable only. The public
Socket.prototype.sendbounds-checkslength + offset > buffer.lengthand throws, so the disclosure cannot be triggered via thesupported API — only via a direct
SocketWrap.send(...)call on the internal binding.Suggested fix: before computing
data, validate the range in native code too, e.g.if (offset > bufLen || length > bufLen - offset) { throw RangeError; }wherebufLen = node::Buffer::Length(buffer), mirroring the checks inCreateChecksum/SetOption(sothe binding is memory-safe independent of the JS wrapper).