Summary:
napi_get_value_string_latin1
, napi_get_value_string_utf8
, napi_get_value_string_utf16
are vulnerable to buffer overflows, partially due to an integer underflow.
Description:
napi_get_value_string_latin1
, napi_get_value_string_utf8
, and napi_get_value_string_utf16
behave like this:
NULL
, return.min(string_length, bufsize - 1)
bytes to the output buffer. Note that bufsize
is an unsigned type, so this leads to an integer underflow for bufsize == 0
. Since this is a size_t
, the underflow will cause the entire string to be written to memory, no matter how long the string is.buf[copied]
, where copied
is the number of bytes previously written. Even if step 2 hadn’t written out of bounds, this would (for bufsize == 0
).Napi::Value Test(const Napi::CallbackInfo& info) {
char buf[1];
// This should be a valid call, e.g., due to a malloc(0).
napi_get_value_string_latin1(info.Env(), info[0], buf, 0, nullptr);
return info.Env().Undefined();
}
const binding = require('bindings')('validation');
console.log(binding.test('this could be code that might later be executed'));
Running the above script corrupts the call stack:
tniessen@local-vm:~/validation-fails$ node .
*** stack smashing detected***: <unknown> terminated
Aborted (core dumped)
The best outcome is a crash, but a very likely outcome is data corruption. If the attacker can control the string’s contents, they can even insert code into the process heap, or modify the call stack. Depending on the architecture and application, this can lead to various issues, up to remote code execution.
It is perfectly valid to pass in a non-NULL pointer for buf
while specifying bufsize == 0
. For example, malloc(0)
is not guaranteed to return NULL
. A npm package might correctly work on one machine based on the assumption that malloc(0) == NULL
, but might create severe security issues on a different host. Passing a non-NULL pointer is also not ruled out by the documentation of N-API, so it is not valid to assume that buf
will always be NULL
if bufsize == 0
.
npm packages and other applications that use N-API may involuntarily open up severe security issues, that might even be exploitable remotely. Even if buf
is a valid pointer, passing bufsize == 0
allows to write outside of the boundaries of that buffer.
Step 2 of the description allows an attacker to precisely define what is written to memory by passing in a custom string. Depending on whether the pointer points to heap or stack, possible results include data corruption, crashes (and thus DoS), and possibly even remote code execution, either by writing instructions to heap memory or by corrupting the stack.
Many attacks are likely caught by kernel and hardware protection mechanisms, but that depends on the specific hardware, kernel, and application, and memory layout. Even if they are caught, the entire process will crash (which is still good compared to other outcomes).