Update 26 Sept 2016: a fix is being prepared at https://github.com/nodejs/node/issues/8724
As I was casually browsing the NodeJS 6.6.0 source code I stumbled upon this suspect piece of code.
src/node_buffer.cc
:
816 template <typename T, enum Endianness endianness> 817 void WriteFloatGeneric(const FunctionCallbackInfo<Value>& args) { 818 Environment* env = Environment::GetCurrent(args); 819 820 bool should_assert = args.Length() < 4; 821 822 if (should_assert) { 823 THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]); 824 } 825 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); 834 835 T val = args[1]->NumberValue(env->context()).FromMaybe(0); 836 size_t offset = args[2]->IntegerValue(env->context()).FromMaybe(0); 837 838 size_t memcpy_num = sizeof(T); 839 840 if (should_assert) { 841 CHECK_NOT_OOB(offset + memcpy_num >= memcpy_num); 842 CHECK_NOT_OOB(offset + memcpy_num <= ts_obj_length); 843 } 844 845 if (offset + memcpy_num > ts_obj_length) 846 memcpy_num = ts_obj_length - offset; 847 848 union NoAlias { 849 T val; 850 char bytes[sizeof(T)]; 851 }; 852 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:
https://nodejs.org/api/buffer.html#buffer_buf_writefloatbe_value_offset_noassert
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
Groovy!
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).