Node.js memory corruption from JavaScript as a feature

Update 26 Sept 2016: a fix is being prepared at

As I was casually browsing the NodeJS 6.6.0 source code I stumbled upon this suspect piece of code.


 816 template <typename T, enum Endianness endianness>
 817 void WriteFloatGeneric(const FunctionCallbackInfo<Value>& args) {
 818   Environment* env = Environment::GetCurrent(args);
 820   bool should_assert = args.Length() < 4;
 822   if (should_assert) {
 823     THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
 824   }
 826   Local<Uint8Array> ts_obj = args[0].As<Uint8Array>();
 827   ArrayBuffer::Contents ts_obj_c = ts_obj->Buffer()->GetContents();
 828   const size_t ts_obj_offset = ts_obj->ByteOffset();
 829   const size_t ts_obj_length = ts_obj->ByteLength();
 830   char* const ts_obj_data =
 831       static_cast<char*>(ts_obj_c.Data()) + ts_obj_offset;
 832   if (ts_obj_length > 0)
 833     CHECK_NE(ts_obj_data, nullptr);
 835   T val = args[1]->NumberValue(env->context()).FromMaybe(0);
 836   size_t offset = args[2]->IntegerValue(env->context()).FromMaybe(0);
 838   size_t memcpy_num = sizeof(T);
 840   if (should_assert) {
 841     CHECK_NOT_OOB(offset + memcpy_num >= memcpy_num);
 842     CHECK_NOT_OOB(offset + memcpy_num <= ts_obj_length);
 843   }
 845   if (offset + memcpy_num > ts_obj_length)
 846     memcpy_num = ts_obj_length - offset;
 848   union NoAlias {
 849     T val;
 850     char bytes[sizeof(T)];
 851   };
 853   union NoAlias na = { val };
 854   char* ptr = static_cast<char*>(ts_obj_data) + offset;
 855   if (endianness != GetEndianness())
 856     Swizzle(na.bytes, sizeof(na.bytes));
 857   memcpy(ptr, na.bytes, memcpy_num);
 858 }

As you can see, should_assert is set to false when there is a 4th parameter.

This is what the documentation says about it:

buf.writeFloatBE(value, offset[, noAssert])
buf.writeFloatLE(value, offset[, noAssert])
Added in: v0.11.15

    value <Number> Number to be written to buf
    offset <Integer> Where to start writing. Must satisfy: 0 <= offset <= buf.length - 4
    noAssert <Boolean> Skip value and offset validation? Default: false
    Return: <Integer> offset plus the number of bytes written

Writes value to buf at the specified offset with specified endian format (writeFloatBE() writes big endian, writeFloatLE() writes little endian). value should be a valid 32-bit float. Behavior is undefined when value is anything other than a 32-bit float.

Setting noAssert to true allows the encoded form of value to extend beyond the end of buf, but the result should be considered undefined behavior.

So it’s not a bug but a feature..

Let’s try it on 64 bit:

node-v6.6.0$ ./node -e 'new Buffer(10).writeFloatBE(1, 0xFFFFFFFFFFFFFFFF-3000, 1);'
Segmentation fault


Disclaimer: I never use NodeJS and I know next to nothing about it. Maybe there is a good use for this “feature” (but what?), but other popular high-level languages have a zero-tolerance policy with regards to raw memory corruption from scripts (see Python, Ruby, Perl, PHP vulnerabilities etc in the Internet Bug Bounty program).

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.