From e085bb9600b190692beb5efc85656ebf127ae08c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 5 Oct 2010 15:45:16 -0700 Subject: [PATCH] generate empty backtraces for EPIPE and ECONNRESET 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. --- ext/kgio/read_write.c | 30 +++++++++++++++++++++++++++--- test/lib_read_write.rb | 16 ++++++++++++---- 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 -- 2.11.4.GIT