Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSLEOFError when serving static MP4 video files greater than a certain size in some browsers using Python >= 3.10.0 with SSL enabled #122254

Closed
peterhorsley opened this issue Jul 25, 2024 · 2 comments
Labels
topic-SSL type-bug An unexpected behavior, bug, or error

Comments

@peterhorsley
Copy link

peterhorsley commented Jul 25, 2024

Bug report

Bug description:

Since Python 3.10.0, I can reproduce a crash in ssl handling when serving static MP4 video files using werkzeug, a popular wsgi server that is a basic wrapper around python's http.server and ssl. I originally reported this against the werkzeug repo, but the owner suggested I report here instead.

When serving static MP4 video files greater than a certain size in some browsers using Python >= 3.10.0 with SSL enabled, an SSLEOFError occurs:

ERROR:werkzeug:Error on request:
Traceback (most recent call last):
File "C:\Users\phorsley.conda\envs\ssl-eof-3.10.0\lib\site-packages\werkzeug\serving.py", line 365, in run_wsgi
execute(self.server.app)
File "C:\Users\phorsley.conda\envs\ssl-eof-3.10.0\lib\site-packages\werkzeug\serving.py", line 329, in execute
write(data)
File "C:\Users\phorsley.conda\envs\ssl-eof-3.10.0\lib\site-packages\werkzeug\serving.py", line 304, in write
self.wfile.write(data)
File "C:\Users\phorsley.conda\envs\ssl-eof-3.10.0\lib\socketserver.py", line 826, in write
self._sock.sendall(b)
File "C:\Users\phorsley.conda\envs\ssl-eof-3.10.0\lib\ssl.py", line 1236, in sendall
v = self.send(byte_view[count:])
File "C:\Users\phorsley.conda\envs\ssl-eof-3.10.0\lib\ssl.py", line 1205, in send
return self._sslobj.write(data)
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:2384)

I'm providing a tiny cut-down example app (including MP4 files) that reproduces the problem, along with additional debug logs, to help narrow down root cause (which I originally thought likely to be in werkzeug's handling of 206 PARTIAL RESPONSEs, however the repo owner believes otherwise).

Example app: ssl-eof.zip

This is the example app code for reference.

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return  "<ul>" + \
            "<li><a href=\"static/1mb.mp4\">1mb mp4 file (works)</a></li>" + \
            "<li><a href=\"static/8mb.mp4\">8mb mp4 file (ssl error)</a></li>" + \
            "</ul>"
            
if __name__ == "__main__":
    app.run(ssl_context='adhoc')

Here's a screen capture of the example app demonstrating the problem:

ssl-eof-chrome-python-3.12.4.mp4

A 1Mb MP4 file does not trigger the error, but an 8Mb MP4 file does.

It reproduces on Windows using Chrome, Brave and Edge, but not Firefox. I believe it does not reproduce on MacOS using Safari or Vivaldi, but does reproduce on Linux, although I have personally only tested on Windows.

Using Flask 3.0.3, Werkzeug 3.0.3 and pyOpenSSL 24.2.1 on Windows 10, I tested the example app with following versions of python and discovered the problem started with Python 3.10.0:

Using Python 3.8.10 (uses OpenSSL 1.1.1w): no error
Using Python 3.9.19 (uses OpenSSL 3.0.14): no error
Using Python 3.10.0 (uses OpenSSL 1.1.1w): error
Using Python 3.10.14 (uses OpenSSL 3.0.14): error
Using Python 3.12.4 (uses OpenSSL 3.0.14): error

I added this log line before the call to self.wfile.write(data) on line 304 of werkzeug's serving.py:

logging.warning(f'writing {len(data)} bytes with status {status_sent} {headers_sent}')

Attached are the console outputs for both Python 3.9.19 and 3.10.0. The only difference is
the presence of the call stack (twice) in 3.10.0.

python-3.10.0-logs.txt

python-3.9.19-logs.txt

From these logs it does appear the problem is related to 206 partial response / range request handling, however I am not sure where to start to identify root cause as I am not an expert on these types of HTTP responses.

I did check the python 3.10.0 release notes and it does appear that something related to ssl was changed, as they mention 'PEP 644 -- Require OpenSSL 1.1.1 or newer'.

Tip - I used miniconda and the following commands to test different versions of python:

conda create --name ssl-eof-3.x.x python=3.x.x
conda activate ssl-eof-3.x.x
pip install flask pyopenssl
python hello.py

Then load https://127.0.0.1:5000/ in a webkit browser (ignore security warning).

Happy to do any further testing / debugging to help narrow this down!

CPython versions tested on:

3.8, 3.9, 3.10, 3.12

Operating systems tested on:

Windows

@keepworking
Copy link
Contributor

keepworking commented Jul 26, 2024

It seems relative with #115627 ,
It could be able to fix after 3.13 version.

In my expectation, that issue works follow like this

  1. client connect to video page
  2. receive "200 OK" resonse, server is still sending a video files
  3. client wanted to receive a video with partial content
  4. client close first connection and request it again, but server still sending video files
    4.1. maybe server will finished to sending 1mb media file in this time
  5. server get broken pipe error
  6. but this python version cannot handle that error
  7. server throw the "ssleof" error

Unless this causes other serious errors, it probably seems okay to ignore.

@peterhorsley
Copy link
Author

Thanks @keepworking and nice work investigating and patching this regression. I'll close this issue and re-test once Python 3.13 is released.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic-SSL type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants