diff --git a/projects/flask/Dockerfile b/projects/flask/Dockerfile index 507a8d112..9a6bf6132 100644 --- a/projects/flask/Dockerfile +++ b/projects/flask/Dockerfile @@ -16,5 +16,6 @@ FROM gcr.io/oss-fuzz-base/base-builder-python RUN git clone https://github.com/pallets/flask +RUN git clone https://github.com/corydolphin/flask-cors RUN git clone --depth=1 https://github.com/google/fuzzing/ -COPY build.sh fuzz_* $SRC/ +COPY build.sh *.py $SRC/ diff --git a/projects/flask/build.sh b/projects/flask/build.sh index 1ff0e3359..27572bc94 100644 --- a/projects/flask/build.sh +++ b/projects/flask/build.sh @@ -14,11 +14,23 @@ # limitations under the License. # ################################################################################ -cd flask + +# Build flask +cd $SRC/flask python3 -m pip install importlib_metadata pip3 install -r ./requirements/tests-pallets-min.in pip3 install . +# Build flask-cors +cd $SRC/flask-cors +pip3 install requests +pip3 install . + +# Build flask-cors fuzzers +cd $SRC/flask-cors +compile_python_fuzzer $SRC/cors_fuzz_flask.py + +# Build flask fuzzers # Build fuzzers in $OUT. for fuzzer in $(find $SRC -name 'fuzz_*.py'); do compile_python_fuzzer $fuzzer diff --git a/projects/flask/cors_fuzz_flask.py b/projects/flask/cors_fuzz_flask.py new file mode 100644 index 000000000..26c830995 --- /dev/null +++ b/projects/flask/cors_fuzz_flask.py @@ -0,0 +1,88 @@ +#!/usr/bin/python3 +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import requests +import threading +import time +import atheris + +with atheris.instrument_imports(): + from flask import Flask + from flask_cors import CORS + from flask import request + +app = Flask(__name__) +CORS(app) +output = "" + +@app.errorhandler(500) +def internal_error(error): + print( + "Catching exception error from flask. The exception is likely " + "printed already right above this message in the log." + ) + return str(error), 500 + +@app.route("/") +def fuzz_echo(): + global output + return output + +def shutdown_server(): + func = request.environ.get('werkzeug.server.shutdown') + if func is None: + raise RuntimeError('Not running with the Werkzeug Server') + func() + +# We use this to force a shutdown of the app. This is to +# have a clean exit when a crash is found. +@app.route('/shutdown') +def shutdown(): + shutdown_server() + return "Server shutdown" + +class ServerThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + + def run(self): + global app + app.run() + +def TestOneInput(data): + global output + output = data + + try: + r = requests.get('http://127.0.0.1:5000', timeout=0.5) + if r.status_code == 500: + raise Exception(r.text) + except requests.exceptions.ConnectionError: + None + except Exception as e: + # Every other exception is raised, but we need to shutdown + # the server before raising it. + requests.get('http://127.0.0.1:5000/shutdown', timeout=1.02) + raise e + +def main(): + t1 = ServerThread() + t1.start() + atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) + atheris.Fuzz() + t1.join() + +if __name__ == "__main__": + main()