Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .mypy.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[mypy]
files = aiohttp, docs/code, examples, tests
files = aiohttp, docs/code, examples, fuzzers, tests
check_untyped_defs = True
follow_imports_for_stubs = True
disallow_any_decorated = True
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ include .coveragerc-cython.toml
graft aiohttp
graft docs
graft examples
graft fuzzers
graft tests
graft tools
graft requirements
Expand Down
43 changes: 43 additions & 0 deletions fuzzers/http_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/python3

# Copyright 2022-2025 Google LLC, 2026 aio-libs contributors
#
# 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 asyncio
import sys
from contextlib import suppress
from unittest import mock

import atheris # noqa: I900

with atheris.instrument_imports(): # type: ignore[attr-defined]
from aiohttp.base_protocol import BaseProtocol
from aiohttp.http_exceptions import BadHttpMessage
from aiohttp.http_parser import HttpRequestParser

LOOP = mock.create_autospec(asyncio.AbstractEventLoop, spec_set=True, instance=True)
PROTOCOL = BaseProtocol(LOOP)


@atheris.instrument_func # type: ignore[attr-defined]
def TestOneInput(data: bytes) -> None: # type: ignore[misc]
parser = HttpRequestParser(PROTOCOL, LOOP, 32768)
with suppress(BadHttpMessage):
parser.feed_data(data)
parser.feed_eof()


if __name__ == "__main__":
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) # type: ignore[attr-defined]
atheris.Fuzz() # type: ignore[attr-defined]
44 changes: 44 additions & 0 deletions fuzzers/http_payload_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/python3

# Copyright 2022-2025 Google LLC, 2026 aio-libs contributors
#
# 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 asyncio
import sys
from contextlib import suppress
from unittest import mock

import atheris # noqa: I900

with atheris.instrument_imports(): # type: ignore[attr-defined]
from aiohttp import StreamReader
from aiohttp.base_protocol import BaseProtocol
from aiohttp.http_exceptions import BadHttpMessage
from aiohttp.http_parser import HeadersParser, HttpPayloadParser

LOOP = mock.create_autospec(asyncio.AbstractEventLoop, spec_set=True, instance=True)
PROTOCOL = BaseProtocol(LOOP)


@atheris.instrument_func # type: ignore[attr-defined]
def TestOneInput(data: bytes) -> None: # type: ignore[misc]
out = StreamReader(PROTOCOL, 2**16, loop=LOOP)
parser = HttpPayloadParser(out, headers_parser=HeadersParser())
with suppress(BadHttpMessage):
parser.feed_data(data)


if __name__ == "__main__":
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) # type: ignore[attr-defined]
atheris.Fuzz() # type: ignore[attr-defined]
69 changes: 69 additions & 0 deletions fuzzers/multipart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/python3

# Copyright 2022-2025 Google LLC, 2026 aio-libs contributors
#
# 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 asyncio
import io
import sys
from contextlib import suppress

import atheris # noqa: I900

with atheris.instrument_imports(): # type: ignore[attr-defined]
from multidict import CIMultiDict

from aiohttp import BodyPartReader, StreamReader
from aiohttp.hdrs import CONTENT_TYPE
from aiohttp.helpers import HeadersDictProxy


class FuzzStream(StreamReader):
def __init__(self, content: bytes):
self.content = io.BytesIO(content)

async def read(self, size: int | None = None) -> bytes:
return self.content.read(size)

def at_eof(self) -> bool:
return self.content.tell() == len(self.content.getbuffer())

async def readline(self, *, max_line_length: int | None = None) -> bytes:
return self.content.readline()

def unread_data(self, data: bytes) -> None:
self.content = io.BytesIO(data + self.content.read())


@atheris.instrument_func # type: ignore[attr-defined]
async def fuzz_bodypart_reader(data: bytes) -> None: # type: ignore[misc]
fdp = atheris.FuzzedDataProvider(data) # type: ignore[attr-defined]
obj = BodyPartReader(
b"--:",
HeadersDictProxy(CIMultiDict({CONTENT_TYPE: fdp.ConsumeUnicode(30)})),
FuzzStream(fdp.ConsumeBytes(atheris.ALL_REMAINING)), # type: ignore[attr-defined]
)
if not obj.at_eof():
await obj.form()


@atheris.instrument_func # type: ignore[attr-defined]
def TestOneInput(data: bytes) -> None: # type: ignore[misc]
with suppress(ValueError):
asyncio.run(fuzz_bodypart_reader(data))


if __name__ == "__main__":
atheris.Setup(sys.argv, TestOneInput) # type: ignore[attr-defined]
atheris.Fuzz() # type: ignore[attr-defined]
3 changes: 3 additions & 0 deletions fuzzers/no_extensions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This lists fuzzers which should be executed a second time with AIOHTTP_NO_EXTENSIONS

http_parser.py
41 changes: 41 additions & 0 deletions fuzzers/payload_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/python3

# Copyright 2022-2025 Google LLC, 2026 aio-libs contributors
#
# 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 sys
from contextlib import suppress

import atheris # noqa: I900

with atheris.instrument_imports(): # type: ignore[attr-defined]
from yarl import URL

from aiohttp.payload import StringPayload


@atheris.instrument_func # type: ignore[attr-defined]
def TestOneInput(data: bytes) -> None: # type: ignore[misc]
fdp = atheris.FuzzedDataProvider(data) # type: ignore[attr-defined]
original = fdp.ConsumeString(sys.maxsize)

with suppress(UnicodeEncodeError):
StringPayload(original)
with suppress(ValueError):
URL(original)


if __name__ == "__main__":
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) # type: ignore[attr-defined]
atheris.Fuzz() # type: ignore[attr-defined]
54 changes: 54 additions & 0 deletions fuzzers/web_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/python3

# Copyright 2022-2025 Google LLC, 2026 aio-libs contributors
#
# 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 asyncio
import sys

import atheris # noqa: I900

with atheris.instrument_imports(): # type: ignore[attr-defined]
from multidict import CIMultiDict
from yarl import URL

from aiohttp.test_utils import make_mocked_request


@atheris.instrument_func # type: ignore[attr-defined]
async def fuzz_run_one_async(data: bytes) -> None: # type: ignore[misc]
fdp = atheris.FuzzedDataProvider(data) # type: ignore[attr-defined]
url_s = fdp.ConsumeString(fdp.ConsumeIntInRange(0, 512))
try:
URL(url_s)
except ValueError:
return

headers = CIMultiDict(
{fdp.ConsumeString(20): fdp.ConsumeString(fdp.ConsumeIntInRange(0, 512))}
)
req = make_mocked_request("GET", url_s, headers=headers)

req.forwarded
await req.post()


@atheris.instrument_func # type: ignore[attr-defined]
def TestOneInput(data: bytes) -> None: # type: ignore[misc]
asyncio.run(fuzz_run_one_async(data))


if __name__ == "__main__":
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) # type: ignore[attr-defined]
atheris.Fuzz() # type: ignore[attr-defined]
1 change: 1 addition & 0 deletions requirements/lint.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
aiodns
aiofastnet >= 0.19.0
atheris
backports.zstd; implementation_name == "cpython" and python_version < "3.14"
blockbuster
freezegun
Expand Down
2 changes: 2 additions & 0 deletions requirements/lint.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ async-timeout==5.0.1
# via
# aiohttp
# valkey
atheris==3.0.0
# via -r requirements/lint.in
attrs==26.1.0
# via aiohttp
backports-asyncio-runner==1.2.0
Expand Down
Loading