about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@yhbt.net>2010-10-05 15:45:16 -0700
committerEric Wong <e@yhbt.net>2010-10-05 15:53:29 -0700
commite085bb9600b190692beb5efc85656ebf127ae08c (patch)
treee8e54ec583fa85e73a421718d58bf079ec555f60
parentb168cc894037620cab82fa82f3ab37a3aab81570 (diff)
downloadkgio-e085bb9600b190692beb5efc85656ebf127ae08c.tar.gz
Malicious clients may disconnect during big writes to cause
EPIPE and ECONNRESET exceptions.  Generating backtraces can be
expensive with Ruby, so mitigate the DoS vector by lowering the
cost of generating an exception.
-rw-r--r--ext/kgio/read_write.c30
-rw-r--r--test/lib_read_write.rb16
2 files changed, 39 insertions, 7 deletions
diff --git a/ext/kgio/read_write.c b/ext/kgio/read_write.c
index 7466c91..57fee4e 100644
--- a/ext/kgio/read_write.c
+++ b/ext/kgio/read_write.c
@@ -1,5 +1,6 @@
 #include "kgio.h"
 static VALUE mKgio_WaitReadable, mKgio_WaitWritable;
+static VALUE eErrno_EPIPE, eErrno_ECONNRESET;
 
 /*
  * we know MSG_DONTWAIT works properly on all stream sockets under Linux
@@ -10,17 +11,37 @@ static VALUE mKgio_WaitReadable, mKgio_WaitWritable;
 #  define USE_MSG_DONTWAIT
 #endif
 
+NORETURN(static void raise_empty_bt(VALUE, const char *));
 NORETURN(static void my_eof_error(void));
+NORETURN(static void wr_sys_fail(const char *));
 
-static void my_eof_error(void)
+static void raise_empty_bt(VALUE err, const char *msg)
 {
-        VALUE exc = rb_exc_new2(rb_eEOFError, "");
+        VALUE exc = rb_exc_new2(err, msg);
         VALUE bt = rb_ary_new();
 
         rb_funcall(exc, rb_intern("set_backtrace"), 1, bt);
         rb_exc_raise(exc);
 }
 
+static void my_eof_error(void)
+{
+        raise_empty_bt(rb_eEOFError, "");
+}
+
+static void wr_sys_fail(const char *msg)
+{
+        switch (errno) {
+        case EPIPE:
+                errno = 0;
+                raise_empty_bt(eErrno_EPIPE, msg);
+        case ECONNRESET:
+                errno = 0;
+                raise_empty_bt(eErrno_ECONNRESET, msg);
+        }
+        rb_sys_fail(msg);
+}
+
 static void prepare_read(struct io_args *a, int argc, VALUE *argv, VALUE io)
 {
         VALUE length;
@@ -218,7 +239,7 @@ done:
                         }
                         return 0;
                 }
-                rb_sys_fail(msg);
+                wr_sys_fail(msg);
         } else {
                 assert(n >= 0 && n < a->len && "write/send syscall broken?");
                 a->ptr += n;
@@ -360,4 +381,7 @@ void init_kgio_read_write(VALUE mKgio)
          * Kgio::LOCALHOST constant for UNIX domain sockets.
          */
         rb_define_attr(mSocketMethods, "kgio_addr", 1, 1);
+
+        eErrno_EPIPE = rb_const_get(rb_mErrno, rb_intern("EPIPE"));
+        eErrno_ECONNRESET = rb_const_get(rb_mErrno, rb_intern("ECONNRESET"));
 }
diff --git a/test/lib_read_write.rb b/test/lib_read_write.rb
index bb5ec42..146c222 100644
--- a/test/lib_read_write.rb
+++ b/test/lib_read_write.rb
@@ -38,16 +38,24 @@ module LibReadWriteTest
 
   def test_write_closed
     @rd.close
-    assert_raises(Errno::EPIPE, Errno::ECONNRESET) {
+    begin
       loop { @wr.kgio_write "HI" }
-    }
+    rescue Errno::EPIPE, Errno::ECONNRESET => e
+      assert_equal [], e.backtrace
+      return
+    end
+    assert false, "should never get here (line:#{__LINE__})"
   end
 
   def test_trywrite_closed
     @rd.close
-    assert_raises(Errno::EPIPE, Errno::ECONNRESET) {
+    begin
       loop { @wr.kgio_trywrite "HI" }
-    }
+    rescue Errno::EPIPE, Errno::ECONNRESET => e
+      assert_equal [], e.backtrace
+      return
+    end
+    assert false, "should never get here (line:#{__LINE__})"
   end
 
   def test_write_conv