diff options
-rw-r--r-- | lib/yahns/proxy_http_response.rb | 35 | ||||
-rw-r--r-- | test/test_proxy_pass.rb | 10 |
2 files changed, 42 insertions, 3 deletions
diff --git a/lib/yahns/proxy_http_response.rb b/lib/yahns/proxy_http_response.rb index af8d8cc..4801008 100644 --- a/lib/yahns/proxy_http_response.rb +++ b/lib/yahns/proxy_http_response.rb @@ -227,11 +227,40 @@ module Yahns::HttpResponse # :nodoc: proxy_busy_mod_done(wbuf.wbuf_persist) # returns nil end + def proxy_wait_next(qflags) + # We must allocate a new, empty request object here to avoid a TOCTTOU + # in the following timeline + # + # original thread: | another thread + # HttpClient#yahns_step | + # r = k.app.call(env = @hs.env) # socket hijacked into epoll queue + # <thread is scheduled away> | epoll_wait readiness + # | ReqRes#yahns_step + # | proxy dispatch ... + # | proxy_busy_mod_done + # ************************** DANGER BELOW ******************************** + # | HttpClient#yahns_step + # | # clears env + # sees empty env: | + # return :ignore if env.include?('rack.hijack_io') | + # + # In other words, we cannot touch the original env seen by the + # original thread since it must see the 'rack.hijack_io' value + # because both are operating in the same Yahns::HttpClient object. + # This will happen regardless of GVL existence + hs = Unicorn::HttpRequest.new + hs.buf.replace(@hs.buf) + @hs = hs + + # n.b. we may not touch anything in this object once we call queue_mod, + # another thread is likely to take it! + Thread.current[:yahns_queue].queue_mod(self, qflags) + end + def proxy_busy_mod_done(alive) - q = Thread.current[:yahns_queue] case http_response_done(alive) - when :wait_readable then q.queue_mod(self, Yahns::Queue::QEV_RD) - when :wait_writable then q.queue_mod(self, Yahns::Queue::QEV_WR) + when :wait_readable then proxy_wait_next(Yahns::Queue::QEV_RD) + when :wait_writable then proxy_wait_next(Yahns::Queue::QEV_WR) when :close then Thread.current[:yahns_fdmap].sync_close(self) end diff --git a/test/test_proxy_pass.rb b/test/test_proxy_pass.rb index 840ad1c..a27a45e 100644 --- a/test/test_proxy_pass.rb +++ b/test/test_proxy_pass.rb @@ -437,6 +437,16 @@ class TestProxyPass < Testcase end re = /^Date:[^\r\n]+/ assert_equal after_up.sub(re, ''), r1.sub(re, '') + + pl.write "GET / HTTP/1.1\r\nHost: example.com" + sleep 0.1 # hope epoll wakes up and reads in this time + pl.write "\r\n\r\nGET / HTTP/1.1\r\nHost: example.com\r\n\r\n" + burst = pl.readpartial(666) + until burst.scan(/^hi$/).size == 2 + burst << pl.readpartial(666) + end + assert_equal 2, burst.scan(/^hi$/).size + assert_match %r{\r\n\r\nhi\n\z}, burst end r1 = r1.split("\r\n").reject { |x| x =~ /^Date: / } r2 = r2.split("\r\n").reject { |x| x =~ /^Date: / } |