buffer: segfault writing values with noAssert=true #8724

Open
bnoordhuis opened this Issue Sep 22, 2016 · 4 comments

Projects

None yet

4 participants

@bnoordhuis
Member

Reported by @guidovranken. Test case:

$ gdb --args ./out/Release/node -e 'new Buffer(10).writeFloatBE(1, 0xFFFFFFFF-1000, 1);'
Reading symbols from ./out/Release/node...done.
(gdb) run
# <elided>
Thread 1 "node" received signal SIGSEGV, Segmentation fault.
0x00007ffff6be36be in __memcpy_sse2_unaligned () from /lib64/libc.so.6
Missing separate debuginfos, use: dnf debuginfo-install libgcc-6.1.1-3.fc24.x86_64 libstdc++-6.1.1-3.fc24.x86_64
(gdb) backtrace 5
#0  0x00007ffff6be36be in __memcpy_sse2_unaligned () from /lib64/libc.so.6
#1  0x00000000012cf00c in void node::Buffer::WriteFloatGeneric<float, (node::Endianness)1>(v8::FunctionCallbackInfo<v8::Value> const&) ()
#2  0x0000000000a129dd in v8::internal::FunctionCallbackArguments::Call(void (*)(v8::FunctionCallbackInfo<v8::Value> const&)) ()
#3  0x0000000000a882b8 in v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Ha
ndle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) ()
#4  0x0000000000a88fcd in v8::internal::Builtin_HandleApiCall(int, v8::internal::Object**, v8::internal::Isolate*) ()

The documentation says this:

`offset` {Integer} Where to start writing. Must satisfy: `0 <= offset <= buf.length - 4`
`noAssert` {Boolean} Skip `value` and `offset` validation? **Default:** `false`

IOW, it's technically allowed for node.js to crash but whether that's actually a good idea is something reasonable people can disagree on. Anyone have opinions on either:

  1. Removing noAssert; i.e., always checking the inputs, or
  2. Skipping out-of-bounds reads and writes?
@bnoordhuis bnoordhuis added the buffer label Sep 22, 2016
@addaleax
Member

Skipping out-of-bounds reads and writes?

If it doesn’t benchmark horribly, I think we should do that.

@rvagg
Member
rvagg commented Sep 23, 2016

any idea what the cost of permanently turning on noAssert are? that seems like reasonable behaviour to me but I don't really know the implications.

@bnoordhuis
Member

It performs about the same. This is master with noAssert=true:

 Performance counter stats for 'out/Release/node -e var b = Buffer.alloc(8); for (var i = 0; i < 5e7; ++i) b.writeDoubleLE(1.0, 0, true)' (5 runs):

       2744.897728      task-clock:u (msec)       #    1.000 CPUs utilized            ( +-  1.04% )
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
              2503      page-faults:u             #    0.912 K/sec                    ( +-  0.01% )

       2.745411566 seconds time elapsed                                          ( +-  1.05% )

Master with noAssert=false. Omitting noAssert benchmarks the same:

 Performance counter stats for 'out/Release/node -e var b = Buffer.alloc(8); for (var i = 0; i < 5e7; ++i) b.writeDoubleLE(1.0, 0, false)' (5 runs):

       3019.762714      task-clock:u (msec)       #    1.000 CPUs utilized            ( +-  1.01% )
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
              2504      page-faults:u             #    0.829 K/sec                    ( +-  0.03% )

       3.020504516 seconds time elapsed                                          ( +-  1.02% )

And this is with noAssert removed, see patch below:

 Performance counter stats for 'out/Release/node -e var b = Buffer.alloc(8); for (var i = 0; i < 5e7; ++i) b.writeDoubleLE(1.0, 0)' (5 runs):

       2750.038828      task-clock:u (msec)       #    1.000 CPUs utilized            ( +-  0.84% )
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
              2506      page-faults:u             #    0.911 K/sec                    ( +-  0.02% )

       2.751283658 seconds time elapsed                                          ( +-  0.85% )

Patch:

diff --git a/lib/buffer.js b/lib/buffer.js
index 876bdbe..d3e48a1 100644
--- a/lib/buffer.js
+++ b/lib/buffer.js
@@ -1241,13 +1241,8 @@ Buffer.prototype.writeInt32BE = function(value, offset, noAssert) {
 };


-Buffer.prototype.writeFloatLE = function writeFloatLE(val, offset, noAssert) {
-  val = +val;
-  offset = offset >>> 0;
-  if (!noAssert)
-    binding.writeFloatLE(this, val, offset);
-  else
-    binding.writeFloatLE(this, val, offset, true);
+Buffer.prototype.writeFloatLE = function writeFloatLE(val, offset) {
+  binding.writeFloatLE(this, +val, offset >>> 0);
   return offset + 4;
 };

diff --git a/src/node_buffer.cc b/src/node_buffer.cc
index 4baa8d9..027b963 100644
--- a/src/node_buffer.cc
+++ b/src/node_buffer.cc
@@ -817,12 +817,7 @@ template <typename T, enum Endianness endianness>
 void WriteFloatGeneric(const FunctionCallbackInfo<Value>& args) {
   Environment* env = Environment::GetCurrent(args);

-  bool should_assert = args.Length() < 4;
-
-  if (should_assert) {
-    THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
-  }
-
+  THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
   Local<Uint8Array> ts_obj = args[0].As<Uint8Array>();
   ArrayBuffer::Contents ts_obj_c = ts_obj->Buffer()->GetContents();
   const size_t ts_obj_offset = ts_obj->ByteOffset();
@@ -837,10 +832,8 @@ void WriteFloatGeneric(const FunctionCallbackInfo<Value>& args) {

   size_t memcpy_num = sizeof(T);

-  if (should_assert) {
-    CHECK_NOT_OOB(offset + memcpy_num >= memcpy_num);
-    CHECK_NOT_OOB(offset + memcpy_num <= ts_obj_length);
-  }
+  CHECK_NOT_OOB(offset + memcpy_num >= memcpy_num);
+  CHECK_NOT_OOB(offset + memcpy_num <= ts_obj_length);

   if (offset + memcpy_num > ts_obj_length)
     memcpy_num = ts_obj_length - offset;
@seishun
Member
seishun commented Feb 10, 2017

This is just about writing floats and doubles, right? noAssert=false causes a significant performance drop for ints: #11245

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment