Skip to content

Commit

Permalink
Attempted fix for urllib3 crashes (#10022)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sg3-141-592 authored Apr 5, 2023
1 parent cea7e49 commit d90c296
Showing 1 changed file with 58 additions and 51 deletions.
109 changes: 58 additions & 51 deletions projects/urllib3/fuzz_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,11 @@

import atheris
from http.server import BaseHTTPRequestHandler, HTTPServer
import os
import random
import sys
import threading
import time
import urllib3

timeout = urllib3.util.Timeout(connect=1.0, read=1.0)
urllib_pool = urllib3.PoolManager(timeout=timeout)

PORT = 8011

GLOBAL_RESPONSE_MESSAGE = ""
Expand Down Expand Up @@ -69,67 +64,79 @@ def log_request(self, code="-", size="-"):
return


def run_webserver():
with HTTPServer(("", PORT), handler) as server:
server.serve_forever()
class StoppableHTTPServer(HTTPServer):
def run(self):
try:
self.serve_forever()
finally:
self.server_close()


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


def TestOneInput(input_bytes):
global GLOBAL_RESPONSE_MESSAGE, GLOBAL_RESPONSE_CODE, GLOBAL_CONTENT_ENCODING
fdp = atheris.FuzzedDataProvider(input_bytes)
global GLOBAL_RESPONSE_MESSAGE, GLOBAL_RESPONSE_CODE, GLOBAL_CONTENT_ENCODING, PORT

# Fuzz Http Response
GLOBAL_RESPONSE_MESSAGE = fdp.ConsumeUnicodeNoSurrogates(sys.maxsize)
GLOBAL_RESPONSE_CODE = fdp.ConsumeIntInRange(200, 599)
GLOBAL_CONTENT_ENCODING = fdp.PickValueInList(CONTENT_ENCODING_TYPES)

# Fuzz Http Request
requestType = fdp.PickValueInList(REQUEST_METHODS)
# Optionally provide request headers
requestHeaders = urllib3._collections.HTTPHeaderDict({})
for i in range(0, fdp.ConsumeIntInRange(0, 10)):
requestHeaders.add(
fdp.ConsumeString(sys.maxsize), fdp.ConsumeString(sys.maxsize)
)
requestHeaders = None if fdp.ConsumeBool() else requestHeaders

# Optionally generate form data for request
formData = {}
for i in range(0, fdp.ConsumeIntInRange(0, 100)):
formData[fdp.ConsumeString(sys.maxsize)] = fdp.ConsumeString(sys.maxsize)
formData = None if fdp.ConsumeBool() else formData

# Optionally generate request body
requestBody = None if fdp.ConsumeBool() else fdp.ConsumeString(sys.maxsize)

r = urllib_pool.request(
requestType,
f"http://localhost:{PORT}/",
headers=requestHeaders,
fields=formData,
body=requestBody
)
r.status
r.data
r.headers
timeout = urllib3.util.Timeout(connect=1.0, read=1.0)
urllib_pool = urllib3.PoolManager(timeout=timeout)

def main():
# Try and get an open port to run our test web server
for attempt in range(10):
try:
PORT = random.randint(8000,9999)
x = threading.Thread(target=run_webserver, daemon=True)
x.start()
PORT = random.randint(8000, 9999)
server = StoppableHTTPServer(("127.0.0.1", PORT), handler)
t1 = threading.Thread(None, server.run)
t1.start()
break
except OSError as e:
pass
except OSError:
pass

time.sleep(0.5) # Short delay to start test server
fdp = atheris.FuzzedDataProvider(input_bytes)

BATCH_SIZE = 100
for iteration in range(BATCH_SIZE):
# Fuzz Http Response
GLOBAL_RESPONSE_MESSAGE = fdp.ConsumeUnicodeNoSurrogates(sys.maxsize)
GLOBAL_RESPONSE_CODE = fdp.ConsumeIntInRange(200, 599)
GLOBAL_CONTENT_ENCODING = fdp.PickValueInList(CONTENT_ENCODING_TYPES)

# Fuzz Http Request
requestType = fdp.PickValueInList(REQUEST_METHODS)
# Optionally provide request headers
requestHeaders = urllib3._collections.HTTPHeaderDict({})
for i in range(0, fdp.ConsumeIntInRange(0, 10)):
requestHeaders.add(
fdp.ConsumeString(sys.maxsize), fdp.ConsumeString(sys.maxsize)
)
requestHeaders = None if fdp.ConsumeBool() else requestHeaders

# Optionally generate form data for request
formData = {}
for i in range(0, fdp.ConsumeIntInRange(0, 100)):
formData[fdp.ConsumeString(sys.maxsize)] = fdp.ConsumeString(sys.maxsize)
formData = None if fdp.ConsumeBool() else formData

# Optionally generate request body
requestBody = None if fdp.ConsumeBool() else fdp.ConsumeString(sys.maxsize)

r = urllib_pool.request(
requestType,
f"http://localhost:{PORT}/",
headers=requestHeaders,
fields=formData,
body=requestBody,
)
r.status
r.data
r.headers

server.shutdown()
t1.join()


def main():
atheris.instrument_all()
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
Expand Down

0 comments on commit d90c296

Please sign in to comment.