gunicorn icon indicating copy to clipboard operation
gunicorn copied to clipboard

Gunicorn generates exception for malformed Client chunked request

Open lxylxy123456 opened this issue 2 years ago • 0 comments

Description

The server is a simple Flask application using werkzeug and gunicorn. I am using telnet for the client. When the client sends a malformed request, Werkzeug does not return 400. Instead, it generates an exception and causes an 500 to be returned.

Note: a similar bug for werkzeug is reported in https://github.com/pallets/werkzeug/issues/2469

Reproducing

  • Install Flask and gunicorn using python3 -m pip install flask gunicorn
  • Copy the following to myproject.py:
    from flask import Flask, request
    app = Flask(__name__)
    
    @app.route("/", methods=['POST'])
    def hello():
        return "Hello %s" % ('file' in request.files)
    
    if __name__ == "__main__":
        app.run()
    
  • Copy the following to wsgi.py:
    from myproject import app
    
    if __name__ == "__main__":
        app.run()
    
  • Run python3 -m gunicorn wsgi:app to start gunicorn
  • Run telnet 127.0.0.1 8000, copy the following content to telnet, then press Enter a few times
    POST / HTTP/1.1
    Host: localhost:8000
    User-Agent: curl/7.79.1
    Accept: */*
    Transfer-Encoding: chunked
    Content-Type: multipart/form-data; boundary=----abcd
    Expect: 100-continue
    
    ----abcd
    
  • See 500 from telnet
  • See traceback in Flask below
[2022-07-25 22:45:00,850] ERROR in app: Exception on / [POST]
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/gunicorn/http/body.py", line 90, in parse_chunk_size
    chunk_size = int(chunk_size, 16)
ValueError: invalid literal for int() with base 16: b'----abcd'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/flask/app.py", line 2073, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.10/dist-packages/flask/app.py", line 1519, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.10/dist-packages/flask/app.py", line 1517, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.10/dist-packages/flask/app.py", line 1503, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/myproject.py", line 6, in hello
    return "Hello %s" % ('file' in request.files)
  File "/usr/local/lib/python3.10/dist-packages/werkzeug/utils.py", line 109, in __get__
    value = self.fget(obj)  # type: ignore
  File "/usr/local/lib/python3.10/dist-packages/werkzeug/wrappers/request.py", line 480, in files
    self._load_form_data()
  File "/usr/local/lib/python3.10/dist-packages/flask/wrappers.py", line 112, in _load_form_data
    super()._load_form_data()
  File "/usr/local/lib/python3.10/dist-packages/werkzeug/wrappers/request.py", line 266, in _load_form_data
    data = parser.parse(
  File "/usr/local/lib/python3.10/dist-packages/werkzeug/formparser.py", line 263, in parse
    return parse_func(self, stream, mimetype, content_length, options)
  File "/usr/local/lib/python3.10/dist-packages/werkzeug/formparser.py", line 140, in wrapper
    return f(self, stream, *args, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/werkzeug/formparser.py", line 290, in _parse_multipart
    form, files = parser.parse(stream, boundary, content_length)
  File "/usr/local/lib/python3.10/dist-packages/werkzeug/formparser.py", line 418, in parse
    for data in iterator:
  File "/usr/local/lib/python3.10/dist-packages/werkzeug/wsgi.py", line 691, in _make_chunk_iter
    item = _read(buffer_size)
  File "/usr/local/lib/python3.10/dist-packages/gunicorn/http/body.py", line 215, in read
    data = self.reader.read(1024)
  File "/usr/local/lib/python3.10/dist-packages/gunicorn/http/body.py", line 30, in read
    self.buf.write(next(self.parser))
  File "/usr/local/lib/python3.10/dist-packages/gunicorn/http/body.py", line 58, in parse_chunked
    (size, rest) = self.parse_chunk_size(unreader)
  File "/usr/local/lib/python3.10/dist-packages/gunicorn/http/body.py", line 92, in parse_chunk_size
    raise InvalidChunkSize(chunk_size)
gunicorn.http.errors.InvalidChunkSize: Invalid chunk size: b'----abcd'

Expected behavior

400 should be returned to the client, because the client sends a bad request.

Environment

  • Python version: 3.10.4
  • Gunicorn version: 20.1.0
  • All packages installed by pip: Jinja2-3.1.2 MarkupSafe-2.1.1 Werkzeug-2.2.0 click-8.1.3 flask-2.1.3 itsdangerous-2.1.2 gunicorn-20.1.0

lxylxy123456 avatar Jul 25 '22 22:07 lxylxy123456