Skip to content

Commit d90c296

Browse files
authored
Attempted fix for urllib3 crashes (#10022)
We've still got an issue with crashes on the urllib3 requests test that uses the mock HTTP server. Fix #9958 to handle port mapping errors didn't resolve it. I got a feeling there's an ordering issue. Looking at the error logs [https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=56500#c2](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=56500#c2) there appears to be an issue where we're throwing exceptions before the coverage completes. ``` === Uncaught Python exception: === --   | MaxRetryError: HTTPConnectionPool(host='localhost', port=8011): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f4cdf33d1f0>: Failed to establish a new connection: [Errno 101] Network is unreachable'))   | Traceback (most recent call last):   | File "fuzz_requests.py", line 109, in TestOneInput   | File "urllib3/_request_methods.py", line 118, in request   | File "urllib3/_request_methods.py", line 217, in request_encode_body   | File "urllib3/poolmanager.py", line 433, in urlopen   | File "urllib3/connectionpool.py", line 874, in urlopen   | File "urllib3/connectionpool.py", line 874, in urlopen   | File "urllib3/connectionpool.py", line 874, in urlopen   | File "urllib3/connectionpool.py", line 844, in urlopen   | File "urllib3/util/retry.py", line 505, in increment   | MaxRetryError: HTTPConnectionPool(host='localhost', port=8011): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f4cdf33d1f0>: Failed to establish a new connection: [Errno 101] Network is unreachable'))   |     | INFO: Instrumenting 3854 functions...   | INFO: Instrumentation complete.   | ==10674== ERROR: libFuzzer: fuzz target exited   | #0 0x7f4ce0bac694 in __sanitizer_print_stack_trace /src/llvm-project/compiler-rt/lib/ubsan/ubsan_diag_standalone.cpp:31:3   | #1 0x7f4ce0b2df48 in fuzzer::PrintStackTrace() /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerUtil.cpp:210:5   | #2 0x7f4ce0b12cdc in fuzzer::Fuzzer::ExitCallback() /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:250:3   | #3 0x7f4ce09068a6 in __run_exit_handlers /build/glibc-SzIz7B/glibc-2.31/stdlib/exit.c:108:8   | #4 0x7f4ce0906a5f in exit /build/glibc-SzIz7B/glibc-2.31/stdlib/exit.c:139:3   | #5 0x7f4ce03b2c78 in libpython3.8.so.1.0   | #6 0x7f4ce03b76cf in libpython3.8.so.1.0   | #7 0x403ad2 in fuzz_requests.pkg   | #8 0x403e67 in fuzz_requests.pkg   | #9 0x7f4ce08e4082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/libc-start.c:308:16   | #10 0x40249d in fuzz_requests.pkg   |     | SUMMARY: libFuzzer: fuzz target exited ``` This is an attempted fix inspired by the requests [fuzz_server.py](https://github.com/google/oss-fuzz/blob/master/projects/requests/fuzz_server.py) where the lifecycle of the test thread is managed within the server. Since the web server is created at the start of `TestOneInput` I don't expect there to be any timing issues or thread initialisation issues.
1 parent cea7e49 commit d90c296

File tree

1 file changed

+58
-51
lines changed

1 file changed

+58
-51
lines changed

projects/urllib3/fuzz_requests.py

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,11 @@
1616

1717
import atheris
1818
from http.server import BaseHTTPRequestHandler, HTTPServer
19-
import os
2019
import random
2120
import sys
2221
import threading
23-
import time
2422
import urllib3
2523

26-
timeout = urllib3.util.Timeout(connect=1.0, read=1.0)
27-
urllib_pool = urllib3.PoolManager(timeout=timeout)
28-
2924
PORT = 8011
3025

3126
GLOBAL_RESPONSE_MESSAGE = ""
@@ -69,67 +64,79 @@ def log_request(self, code="-", size="-"):
6964
return
7065

7166

72-
def run_webserver():
73-
with HTTPServer(("", PORT), handler) as server:
74-
server.serve_forever()
67+
class StoppableHTTPServer(HTTPServer):
68+
def run(self):
69+
try:
70+
self.serve_forever()
71+
finally:
72+
self.server_close()
7573

7674

7775
REQUEST_METHODS = ["POST", "GET", "HEAD", "PUT", "DELETE", "OPTIONS", "PATCH"]
7876
CONTENT_ENCODING_TYPES = [None, "gzip", "deflate"]
7977

8078

8179
def TestOneInput(input_bytes):
82-
global GLOBAL_RESPONSE_MESSAGE, GLOBAL_RESPONSE_CODE, GLOBAL_CONTENT_ENCODING
83-
fdp = atheris.FuzzedDataProvider(input_bytes)
80+
global GLOBAL_RESPONSE_MESSAGE, GLOBAL_RESPONSE_CODE, GLOBAL_CONTENT_ENCODING, PORT
8481

85-
# Fuzz Http Response
86-
GLOBAL_RESPONSE_MESSAGE = fdp.ConsumeUnicodeNoSurrogates(sys.maxsize)
87-
GLOBAL_RESPONSE_CODE = fdp.ConsumeIntInRange(200, 599)
88-
GLOBAL_CONTENT_ENCODING = fdp.PickValueInList(CONTENT_ENCODING_TYPES)
89-
90-
# Fuzz Http Request
91-
requestType = fdp.PickValueInList(REQUEST_METHODS)
92-
# Optionally provide request headers
93-
requestHeaders = urllib3._collections.HTTPHeaderDict({})
94-
for i in range(0, fdp.ConsumeIntInRange(0, 10)):
95-
requestHeaders.add(
96-
fdp.ConsumeString(sys.maxsize), fdp.ConsumeString(sys.maxsize)
97-
)
98-
requestHeaders = None if fdp.ConsumeBool() else requestHeaders
99-
100-
# Optionally generate form data for request
101-
formData = {}
102-
for i in range(0, fdp.ConsumeIntInRange(0, 100)):
103-
formData[fdp.ConsumeString(sys.maxsize)] = fdp.ConsumeString(sys.maxsize)
104-
formData = None if fdp.ConsumeBool() else formData
105-
106-
# Optionally generate request body
107-
requestBody = None if fdp.ConsumeBool() else fdp.ConsumeString(sys.maxsize)
108-
109-
r = urllib_pool.request(
110-
requestType,
111-
f"http://localhost:{PORT}/",
112-
headers=requestHeaders,
113-
fields=formData,
114-
body=requestBody
115-
)
116-
r.status
117-
r.data
118-
r.headers
82+
timeout = urllib3.util.Timeout(connect=1.0, read=1.0)
83+
urllib_pool = urllib3.PoolManager(timeout=timeout)
11984

120-
def main():
12185
# Try and get an open port to run our test web server
12286
for attempt in range(10):
12387
try:
124-
PORT = random.randint(8000,9999)
125-
x = threading.Thread(target=run_webserver, daemon=True)
126-
x.start()
88+
PORT = random.randint(8000, 9999)
89+
server = StoppableHTTPServer(("127.0.0.1", PORT), handler)
90+
t1 = threading.Thread(None, server.run)
91+
t1.start()
12792
break
128-
except OSError as e:
129-
pass
93+
except OSError:
94+
pass
13095

131-
time.sleep(0.5) # Short delay to start test server
96+
fdp = atheris.FuzzedDataProvider(input_bytes)
13297

98+
BATCH_SIZE = 100
99+
for iteration in range(BATCH_SIZE):
100+
# Fuzz Http Response
101+
GLOBAL_RESPONSE_MESSAGE = fdp.ConsumeUnicodeNoSurrogates(sys.maxsize)
102+
GLOBAL_RESPONSE_CODE = fdp.ConsumeIntInRange(200, 599)
103+
GLOBAL_CONTENT_ENCODING = fdp.PickValueInList(CONTENT_ENCODING_TYPES)
104+
105+
# Fuzz Http Request
106+
requestType = fdp.PickValueInList(REQUEST_METHODS)
107+
# Optionally provide request headers
108+
requestHeaders = urllib3._collections.HTTPHeaderDict({})
109+
for i in range(0, fdp.ConsumeIntInRange(0, 10)):
110+
requestHeaders.add(
111+
fdp.ConsumeString(sys.maxsize), fdp.ConsumeString(sys.maxsize)
112+
)
113+
requestHeaders = None if fdp.ConsumeBool() else requestHeaders
114+
115+
# Optionally generate form data for request
116+
formData = {}
117+
for i in range(0, fdp.ConsumeIntInRange(0, 100)):
118+
formData[fdp.ConsumeString(sys.maxsize)] = fdp.ConsumeString(sys.maxsize)
119+
formData = None if fdp.ConsumeBool() else formData
120+
121+
# Optionally generate request body
122+
requestBody = None if fdp.ConsumeBool() else fdp.ConsumeString(sys.maxsize)
123+
124+
r = urllib_pool.request(
125+
requestType,
126+
f"http://localhost:{PORT}/",
127+
headers=requestHeaders,
128+
fields=formData,
129+
body=requestBody,
130+
)
131+
r.status
132+
r.data
133+
r.headers
134+
135+
server.shutdown()
136+
t1.join()
137+
138+
139+
def main():
133140
atheris.instrument_all()
134141
atheris.Setup(sys.argv, TestOneInput)
135142
atheris.Fuzz()

0 commit comments

Comments
 (0)