Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src/bigint-buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@

#define BIT_MASK(n) (~( ((~0ull) << ((n)-1)) << 1 ))

// The maximum size we'll store on the stack. If we need a larger temporary
// buffer malloc will be called.
#define BUFFER_STACK_SIZE 32
// Number of uint64_t words to allocate on the stack for temporary buffers.
// If the required buffer exceeds this, malloc is used instead.
#define BUFFER_STACK_WORDS 32
#define BUFFER_STACK_BYTES (BUFFER_STACK_WORDS * sizeof(uint64_t))

#if defined(_WIN16) || defined(_WIN32) || defined(_WIN64)
#define bswap64(x) _byteswap_uint64(x)
Expand Down Expand Up @@ -54,9 +55,9 @@ napi_value toBigInt (napi_env env, napi_callback_info info) {
// Buffer is managed by VM, so copy it out (TODO: perhaps we can increase refcount?)
size_t aligned_len = len + overflow_len;
size_t len_in_words = not_64_aligned ? (len >> 3) + 1 : (len >> 3);
bool fits_in_stack = aligned_len <= BUFFER_STACK_SIZE;
bool fits_in_stack = aligned_len <= BUFFER_STACK_BYTES;

uint8_t copy[BUFFER_STACK_SIZE];
uint8_t copy[BUFFER_STACK_BYTES];
uint8_t* bufTemp = fits_in_stack ? copy : malloc(aligned_len);
if (overflow_len > 0) {
memset(bufTemp + len, 0, overflow_len);
Expand Down Expand Up @@ -149,11 +150,11 @@ napi_value fromBigInt (napi_env env, napi_callback_info info) {
if (word_count > word_width) {
word_count = word_width;
}
size_t word_width_bytes = (word_count << 3);
bool fits_in_stack = word_width_bytes <= BUFFER_STACK_SIZE;
size_t buffer_size = byte_width + overflow_len;
bool fits_in_stack = buffer_size <= BUFFER_STACK_BYTES;

uint64_t* conv_buffer = (uint64_t*) raw_buffer;
uint64_t stack_buffer[BUFFER_STACK_SIZE];
uint64_t stack_buffer[BUFFER_STACK_WORDS];
if (not_64_aligned) {
conv_buffer = fits_in_stack ? stack_buffer : malloc(byte_width + overflow_len);
}
Expand Down
78 changes: 78 additions & 0 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,82 @@ describe('Try bigint conversion (big endian)', () => {
0xe0, 0xdd, 0xf0, 0x0d, 0xde, 0xad, 0xbe, 0xef
]));
});
});

describe('Buffer overflow regression (SNYK-JS-BIGINTBUFFER-3364597)', () => {
// Small BigInt into a non-aligned buffer wider than the native stack buffer.
// Before the fix, fromBigInt compared word_count bytes (small) against the
// stack limit, then memset the full byte_width+overflow into the undersized
// stack buffer, causing a stack buffer overflow.

it('toBufferBE with small bigint and non-aligned width > 32 bytes', () => {
const buf = toBufferBE(BigInt(`0xdeadbeef`), 33);
buf.length.should.equal(33);
// First 29 bytes should be zero-padded, last 4 bytes hold the value
for (let i = 0; i < 29; i++) {
buf[i].should.equal(0, `byte ${i} should be 0`);
}
buf[29].should.equal(0xde);
buf[30].should.equal(0xad);
buf[31].should.equal(0xbe);
buf[32].should.equal(0xef);
});

it('toBufferLE with small bigint and non-aligned width > 32 bytes', () => {
const buf = toBufferLE(BigInt(`0xdeadbeef`), 33);
buf.length.should.equal(33);
buf[0].should.equal(0xef);
buf[1].should.equal(0xbe);
buf[2].should.equal(0xad);
buf[3].should.equal(0xde);
for (let i = 4; i < 33; i++) {
buf[i].should.equal(0, `byte ${i} should be 0`);
}
});

it('toBufferBE with small bigint and large non-aligned width (257 bytes)', () => {
const buf = toBufferBE(BigInt(`0x1`), 257);
buf.length.should.equal(257);
for (let i = 0; i < 256; i++) {
buf[i].should.equal(0, `byte ${i} should be 0`);
}
buf[256].should.equal(1);
});

it('toBufferLE with small bigint and large non-aligned width (257 bytes)', () => {
const buf = toBufferLE(BigInt(`0x1`), 257);
buf.length.should.equal(257);
buf[0].should.equal(1);
for (let i = 1; i < 257; i++) {
buf[i].should.equal(0, `byte ${i} should be 0`);
}
});

it('roundtrip BE with non-aligned width (33 bytes)', () => {
const val = BigInt(`0xbadc0ffee0ddf00ddeadbeef`);
const buf = toBufferBE(val, 33);
const result = toBigIntBE(buf);
assertEquals(result, val);
});

it('roundtrip LE with non-aligned width (33 bytes)', () => {
const val = BigInt(`0xbadc0ffee0ddf00ddeadbeef`);
const buf = toBufferLE(val, 33);
const result = toBigIntLE(buf);
assertEquals(result, val);
});

it('roundtrip BE with large non-aligned width (257 bytes)', () => {
const val = BigInt(`0xbadc0ffee0ddf00ddeadbeefbadc0ffee0ddf00ddeadbeef`);
const buf = toBufferBE(val, 257);
const result = toBigIntBE(buf);
assertEquals(result, val);
});

it('roundtrip LE with large non-aligned width (257 bytes)', () => {
const val = BigInt(`0xbadc0ffee0ddf00ddeadbeefbadc0ffee0ddf00ddeadbeef`);
const buf = toBufferLE(val, 257);
const result = toBigIntLE(buf);
assertEquals(result, val);
});
});