From 87a7a11aee98547732b5894bc6eaec684af674db Mon Sep 17 00:00:00 2001 From: Robert Scott Date: Sat, 26 Jun 2021 15:57:51 +0100 Subject: [PATCH] python3Packages.fastapi: add patch for CVE-2021-32677 --- .../fastapi/0.55.1-CVE-2021-32677.patch | 151 ++++++++++++++++++ .../python-modules/fastapi/default.nix | 4 + 2 files changed, 155 insertions(+) create mode 100644 pkgs/development/python-modules/fastapi/0.55.1-CVE-2021-32677.patch diff --git a/pkgs/development/python-modules/fastapi/0.55.1-CVE-2021-32677.patch b/pkgs/development/python-modules/fastapi/0.55.1-CVE-2021-32677.patch new file mode 100644 index 000000000000..1a17cdf05498 --- /dev/null +++ b/pkgs/development/python-modules/fastapi/0.55.1-CVE-2021-32677.patch @@ -0,0 +1,151 @@ +Based on upstream https://github.com/tiangolo/fastapi/commit/fa7e3c996edf2d5482fff8f9d890ac2390dede4d.patch +modified by ris to apply to 0.55.1 +diff --git a/fastapi/routing.py b/fastapi/routing.py +index ac5e19d998..9b51f03cac 100644 +--- a/fastapi/routing.py ++++ b/fastapi/routing.py +@@ -1,3 +1,4 @@ + import asyncio ++import email.message + import inspect + import json +@@ -36,6 +37,7 @@ + ) + from pydantic import BaseModel + from pydantic.error_wrappers import ErrorWrapper, ValidationError ++from pydantic.fields import Undefined + from starlette import routing + from starlette.concurrency import run_in_threadpool + from starlette.exceptions import HTTPException +@@ -174,7 +175,7 @@ def get_request_handler( + + async def app(request: Request) -> Response: + try: +- body = None ++ body: Any = None + if body_field: + if is_body_form: + body = await request.form() +@@ -181,6 +183,18 @@ def get_request_handler( + else: + body_bytes = await request.body() + if body_bytes: +- body = await request.json() ++ json_body: Any = Undefined ++ content_type_value = request.headers.get("content-type") ++ if content_type_value: ++ message = email.message.Message() ++ message["content-type"] = content_type_value ++ if message.get_content_maintype() == "application": ++ subtype = message.get_content_subtype() ++ if subtype == "json" or subtype.endswith("+json"): ++ json_body = await request.json() ++ if json_body != Undefined: ++ body = json_body ++ else: ++ body = body_bytes + except Exception as e: + logger.error(f"Error getting request body: {e}") +diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py +index 38c6dbe876..c90240ae4c 100644 +--- a/tests/test_tutorial/test_body/test_tutorial001.py ++++ b/tests/test_tutorial/test_body/test_tutorial001.py +@@ -0,4 +0,6 @@ ++from unittest.mock import patch ++ + import pytest + from fastapi.testclient import TestClient + + from body.tutorial001 import app +@@ -173,6 +173,76 @@ def test_post_body(path, body, expected_status, expected_response): + + + def test_post_broken_body(): +- response = client.post("/items/", data={"name": "Foo", "price": 50.5}) +- assert response.status_code == 400, response.text +- assert response.json() == {"detail": "There was an error parsing the body"} ++ response = client.post( ++ "/items/", ++ headers={"content-type": "application/json"}, ++ data="{some broken json}", ++ ) ++ assert response.status_code == 400, response.text ++ assert response.json() == {"detail": "There was an error parsing the body"} ++ ++ ++def test_post_form_for_json(): ++ response = client.post("/items/", data={"name": "Foo", "price": 50.5}) ++ assert response.status_code == 422, response.text ++ assert response.json() == { ++ "detail": [ ++ { ++ "loc": ["body", "item"], ++ "msg": "value is not a valid dict", ++ "type": "type_error.dict", ++ } ++ ] ++ } ++ ++ ++def test_explicit_content_type(): ++ response = client.post( ++ "/items/", ++ data='{"name": "Foo", "price": 50.5}', ++ headers={"Content-Type": "application/json"}, ++ ) ++ assert response.status_code == 200, response.text ++ ++ ++def test_geo_json(): ++ response = client.post( ++ "/items/", ++ data='{"name": "Foo", "price": 50.5}', ++ headers={"Content-Type": "application/geo+json"}, ++ ) ++ assert response.status_code == 200, response.text ++ ++ ++def test_wrong_headers(): ++ data = '{"name": "Foo", "price": 50.5}' ++ invalid_dict = { ++ "detail": [ ++ { ++ "loc": ["body", "item"], ++ "msg": "value is not a valid dict", ++ "type": "type_error.dict", ++ } ++ ] ++ } ++ ++ response = client.post("/items/", data=data, headers={"Content-Type": "text/plain"}) ++ assert response.status_code == 422, response.text ++ assert response.json() == invalid_dict ++ ++ response = client.post( ++ "/items/", data=data, headers={"Content-Type": "application/geo+json-seq"} ++ ) ++ assert response.status_code == 422, response.text ++ assert response.json() == invalid_dict ++ response = client.post( ++ "/items/", data=data, headers={"Content-Type": "application/not-really-json"} ++ ) ++ assert response.status_code == 422, response.text ++ assert response.json() == invalid_dict ++ ++ ++def test_other_exceptions(): ++ with patch("json.loads", side_effect=Exception): ++ response = client.post("/items/", json={"test": "test2"}) ++ assert response.status_code == 400, response.text +diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py +index cc85a8a82a..3eb5822e28 100644 +--- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py ++++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py +@@ -25,6 +25,7 @@ def test_gzip_request(compress): + if compress: + data = gzip.compress(data) + headers["Content-Encoding"] = "gzip" ++ headers["Content-Type"] = "application/json" + response = client.post("/sum", data=data, headers=headers) + assert response.json() == {"sum": n} + diff --git a/pkgs/development/python-modules/fastapi/default.nix b/pkgs/development/python-modules/fastapi/default.nix index 32ebd6a0307d..d84261a10900 100644 --- a/pkgs/development/python-modules/fastapi/default.nix +++ b/pkgs/development/python-modules/fastapi/default.nix @@ -27,6 +27,10 @@ buildPythonPackage rec { sha256 = "1515nhwari48v0angyl5z3cfpvwn4al2nvqh0cjd9xgxzvm310s8"; }; + patches = [ + ./0.55.1-CVE-2021-32677.patch + ]; + postPatch = '' substituteInPlace pyproject.toml \ --replace "starlette ==0.13.2" "starlette"