diff options
author | Aldo Cortesi <aldo@nullcube.com> | 2014-10-26 17:58:36 +1300 |
---|---|---|
committer | Aldo Cortesi <aldo@nullcube.com> | 2014-10-26 17:58:36 +1300 |
commit | 16654ad6a4ba4f12287d5707dafe3794b6e33fb8 (patch) | |
tree | c4bb04d3f8070272589775fdc53fc0f40ce63340 | |
parent | 7aee9a7c311e755147b398b8ba0b44aaec40eaf7 (diff) | |
download | mitmproxy-16654ad6a4ba4f12287d5707dafe3794b6e33fb8.tar.gz mitmproxy-16654ad6a4ba4f12287d5707dafe3794b6e33fb8.tar.bz2 mitmproxy-16654ad6a4ba4f12287d5707dafe3794b6e33fb8.zip |
Fix crash while streaming
Found using fuzzing. Reproduction with pathoc, given "mitmproxy -s" and
pathod running on 9999:
get:'http://localhost:9999/p/':s'200:b\'foo\':h\'Content-Length\'=\'3\'':i58,'\x1a':r
return flow.FlowMaster.run(self)
File "/Users/aldo/mitmproxy/mitmproxy/libmproxy/controller.py", line 111, in run
self.tick(self.masterq, 0.01)
File "/Users/aldo/mitmproxy/mitmproxy/libmproxy/flow.py", line 613, in tick
return controller.Master.tick(self, q, timeout)
File "/Users/aldo/mitmproxy/mitmproxy/libmproxy/controller.py", line 101, in tick
self.handle(*msg)
File "/Users/aldo/mitmproxy/mitmproxy/libmproxy/controller.py", line 118, in handle
m(obj)
File "/Users/aldo/mitmproxy/mitmproxy/libmproxy/flow.py", line 738, in handle_responseheaders
self.stream_large_bodies.run(f, False)
File "/Users/aldo/mitmproxy/mitmproxy/libmproxy/flow.py", line 155, in run
r.headers, is_request, flow.request.method, code
File "/Users/aldo/mitmproxy/mitmproxy/netlib/http.py", line 401, in expected_http_body_size
raise HttpError(400 if is_request else 502, "Invalid content-length header: %s" % headers["content-length"])
netlib.http.HttpError: Invalid content-length header: ['\x1a3']
-rw-r--r-- | libmproxy/cmdline.py | 27 | ||||
-rw-r--r-- | libmproxy/flow.py | 8 | ||||
-rw-r--r-- | libmproxy/protocol/http.py | 40 | ||||
-rw-r--r-- | test/fuzzing/straight_stream | 4 |
4 files changed, 53 insertions, 26 deletions
diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py index 83eab7ee..4a3b5a48 100644 --- a/libmproxy/cmdline.py +++ b/libmproxy/cmdline.py @@ -248,14 +248,18 @@ def common_options(parser): "--stream", action="store", dest="stream_large_bodies", default=None, metavar="SIZE", - help="Stream data to the client if response body exceeds the given threshold. " - "If streamed, the body will not be stored in any way. Understands k/m/g suffixes, i.e. 3m for 3 megabytes." + help=""" + Stream data to the client if response body exceeds the given threshold. + If streamed, the body will not be stored in any way. Understands k/m/g + suffixes, i.e. 3m for 3 megabytes. + """ ) group = parser.add_argument_group("Proxy Options") - # We could make a mutually exclusive group out of -R, -U, -T, but we don't do that because - # - --upstream-server should be in that group as well, but it's already in a different group. - # - our own error messages are more helpful + # We could make a mutually exclusive group out of -R, -U, -T, but we don't + # do that because - --upstream-server should be in that group as well, but + # it's already in a different group. - our own error messages are more + # helpful group.add_argument( "-b", action="store", type=str, dest="addr", default='', @@ -265,11 +269,14 @@ def common_options(parser): "-I", "--ignore", action="append", type=str, dest="ignore_hosts", default=[], metavar="HOST", - help="Ignore host and forward all traffic without processing it. " - "In transparent mode, it is recommended to use an IP address (range), not the hostname. " - "In regular mode, only SSL traffic is ignored and the hostname should be used. " - "The supplied value is interpreted as a regular expression and matched on the ip or the hostname. " - "Can be passed multiple times. " + help=""" + Ignore host and forward all traffic without processing it. In + transparent mode, it is recommended to use an IP address (range), + not the hostname. In regular mode, only SSL traffic is ignored and + the hostname should be used. The supplied value is interpreted as a + regular expression and matched on the ip or the hostname. Can be + passed multiple times. + """ ) group.add_argument( "--tcp", diff --git a/libmproxy/flow.py b/libmproxy/flow.py index 6a24cc63..6136ec1c 100644 --- a/libmproxy/flow.py +++ b/libmproxy/flow.py @@ -738,8 +738,12 @@ class FlowMaster(controller.Master): def handle_responseheaders(self, f): self.run_script_hook("responseheaders", f) - if self.stream_large_bodies: - self.stream_large_bodies.run(f, False) + try: + if self.stream_large_bodies: + self.stream_large_bodies.run(f, False) + except netlib.http.HttpError: + f.reply(protocol.KILL) + return f.reply() return f diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py index 9542cc81..e81c7640 100644 --- a/libmproxy/protocol/http.py +++ b/libmproxy/protocol/http.py @@ -18,6 +18,10 @@ HDR_FORM_URLENCODED = "application/x-www-form-urlencoded" CONTENT_MISSING = 0 +class KillSignal(Exception): + pass + + def get_line(fp): """ Get a line, possibly preceded by a blank. @@ -1001,19 +1005,21 @@ class HTTPHandler(ProtocolHandler): # call the appropriate script hook - this is an opportunity for an # inline script to set flow.stream = True - self.c.channel.ask("responseheaders", flow) - - # now get the rest of the request body, if body still needs to be read - # but not streaming this response - if flow.response.stream: - flow.response.content = CONTENT_MISSING + flow = self.c.channel.ask("responseheaders", flow) + if flow == KILL: + raise KillSignal else: - flow.response.content = http.read_http_body( - self.c.server_conn.rfile, flow.response.headers, - self.c.config.body_size_limit, - flow.request.method, flow.response.code, False - ) - flow.response.timestamp_end = utils.timestamp() + # now get the rest of the request body, if body still needs to be + # read but not streaming this response + if flow.response.stream: + flow.response.content = CONTENT_MISSING + else: + flow.response.content = http.read_http_body( + self.c.server_conn.rfile, flow.response.headers, + self.c.config.body_size_limit, + flow.request.method, flow.response.code, False + ) + flow.response.timestamp_end = utils.timestamp() def handle_flow(self): flow = HTTPFlow(self.c.client_conn, self.c.server_conn, self.live) @@ -1092,8 +1098,16 @@ class HTTPHandler(ProtocolHandler): flow.live.restore_server() return True # Next flow please. - except (HttpAuthenticationError, http.HttpError, proxy.ProxyError, tcp.NetLibError), e: + except ( + HttpAuthenticationError, + http.HttpError, + proxy.ProxyError, + tcp.NetLibError, + ), e: self.handle_error(e, flow) + except KillSignal: + self.c.log("Connection killed", "info") + flow.live = None finally: flow.live = None # Connection is not live anymore. return False diff --git a/test/fuzzing/straight_stream b/test/fuzzing/straight_stream index 64feae45..a716a085 100644 --- a/test/fuzzing/straight_stream +++ b/test/fuzzing/straight_stream @@ -1,4 +1,6 @@ mitmdump: $MITMDUMP -q --stream 1 pathod: $PATHOD -q -pathoc: sleep 2 && $PATHOC $FUZZ_SETTINGS localhost:8080 ./straight_stream_patterns
\ No newline at end of file +#pathoc: sleep 2 && $PATHOC $FUZZ_SETTINGS localhost:8080 ./straight_stream_patterns +pathoc: sleep 2 && $PATHOC $FUZZ_SETTINGS localhost:8080 /tmp/err + |