-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Add fuzzing to CI #12887
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
Open
Dreamsorcerer
wants to merge
24
commits into
master
Choose a base branch
from
fuzz
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add fuzzing to CI #12887
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
cda204a
Add fuzzing to CI
Dreamsorcerer 791fb23
Track upstream
Dreamsorcerer f2bf4db
Create bug
Dreamsorcerer d841c79
Apply suggestion from @Dreamsorcerer
Dreamsorcerer dee8c7b
Create http_parser.py
Dreamsorcerer ac99f7f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] f1c49ab
Create http_payload_parser.py
Dreamsorcerer be6b0fe
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 09e782a
Create multipart.py
Dreamsorcerer 4e4515f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 74b62f4
Create payload_url.py
Dreamsorcerer 6d46483
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 9af2d98
Create web_request.py
Dreamsorcerer e3b8698
Update .mypy.ini
Dreamsorcerer 6df241f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 4263424
Update http_payload_parser.py
Dreamsorcerer af37f57
Update http_parser.py
Dreamsorcerer 88c72f5
Apply suggestions from code review
Dreamsorcerer 791e31c
Apply suggestions from code review
Dreamsorcerer 8e2070c
Update lint.in
Dreamsorcerer 3bd9887
Update lint.txt
Dreamsorcerer a97ecd9
Apply suggestions from code review
Dreamsorcerer cfe8f4d
Update multipart.py
Dreamsorcerer 3fe6800
Update http_payload_parser.py
Dreamsorcerer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| name: CIFuzz | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - 'master' | ||
| - '[0-9].[0-9]+' # matches to backport branches, e.g. 3.6 | ||
| pull_request: | ||
| branches: | ||
| - 'master' | ||
| - '[0-9].[0-9]+' | ||
|
|
||
| permissions: {} | ||
|
|
||
| jobs: | ||
| Fuzzing: | ||
| name: Fuzzing (${{ matrix.sanitizer }}) | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read # For build_fuzzers to check out the code | ||
| issues: write # To create a new issue in the last step | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| sanitizer: [address, undefined] | ||
| steps: | ||
| - name: Build Fuzzers | ||
| id: build | ||
| uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master | ||
| with: | ||
| oss-fuzz-project-name: 'aiohttp' | ||
| language: python | ||
| sanitizer: ${{ matrix.sanitizer }} | ||
| - name: Run Fuzzers | ||
| uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master | ||
| with: | ||
| oss-fuzz-project-name: 'aiohttp' | ||
| language: python | ||
| fuzz-seconds: 600 | ||
| sanitizer: ${{ matrix.sanitizer }} | ||
| - name: Upload Crash | ||
| uses: actions/upload-artifact@v4 | ||
| if: failure() && steps.build.outcome == 'success' | ||
| with: | ||
| name: ${{ matrix.sanitizer }}-artifacts | ||
| path: ./out/artifacts | ||
| - name: Open issue on post-merge crash | ||
| if: >- | ||
| failure() | ||
| && steps.build.outcome == 'success' | ||
| && github.event_name == 'push' | ||
| uses: actions/github-script@v9 | ||
| env: | ||
| SANITIZER: ${{ matrix.sanitizer }} | ||
| with: | ||
| script: | | ||
| const branch = context.ref.replace('refs/heads/', ''); | ||
| const shortSha = context.sha.substring(0, 7); | ||
| const sanitizer = process.env.SANITIZER; | ||
| const runUrl = | ||
| `${context.serverUrl}/${context.repo.owner}` + | ||
| `/${context.repo.repo}/actions/runs/${context.runId}`; | ||
| await github.rest.issues.create({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| title: `CIFuzz: ${sanitizer} crash on ${branch} @ ${shortSha}`, | ||
| body: [ | ||
| `CIFuzz found a crash on \`${branch}\` at ${context.sha}.`, | ||
| ``, | ||
| `- Sanitizer: \`${sanitizer}\``, | ||
| `- Run: ${runUrl}`, | ||
| `- Download \`${sanitizer}-artifacts\` from the run page for the reproducer.`, | ||
| ].join('\n'), | ||
| type: 'Bug', | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| #!/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(): | ||
| from aiohttp.base_protocol import BaseProtocol | ||
| from aiohttp.http_exceptions import BadHttpMessage | ||
| from aiohttp.http_parser import HttpRequestParserC, HttpRequestParserPy | ||
|
|
||
| AIOHTTP_VAL = 0 | ||
| HttpRequestParser = HttpRequestParserC if AIOHTTP_VAL == 0 else HttpRequestParserPy | ||
| LOOP = mock.create_autospec(asyncio.AbstractEventLoop, spec_set=True, instance=True) | ||
| PROTOCOL = BaseProtocol(LOOP) | ||
|
|
||
|
|
||
| @atheris.instrument_func | ||
| def TestOneInput(data: bytes) -> None: | ||
| 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) | ||
| atheris.Fuzz() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
Dreamsorcerer marked this conversation as resolved.
|
||
| from contextlib import suppress | ||
| from unittest import mock | ||
|
|
||
| import atheris # noqa: I900 | ||
|
|
||
| with atheris.instrument_imports(): | ||
| from aiohttp import StreamReader | ||
| from aiohttp.base_protocol import BaseProtocol | ||
|
Dreamsorcerer marked this conversation as resolved.
|
||
| 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 | ||
| def TestOneInput(data: bytes) -> None: | ||
| 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) | ||
| atheris.Fuzz() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| #!/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 | ||
|
Dreamsorcerer marked this conversation as resolved.
|
||
| from contextlib import suppress | ||
|
|
||
| import atheris # noqa: I900 | ||
|
|
||
| with atheris.instrument_imports(): | ||
| from aiohttp import BodyPartReader | ||
| 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) -> bytes: | ||
| return self.content.readline() | ||
|
|
||
| def unread_data(self, data: bytes) -> None: | ||
| self.content = io.BytesIO(data + self.content.read()) | ||
|
|
||
|
|
||
| @atheris.instrument_func | ||
| async def fuzz_bodypart_reader(data: bytes) -> None: | ||
| fdp = atheris.FuzzedDataProvider(data) | ||
| obj = BodyPartReader( | ||
| b"--:", | ||
| HeadersDictProxy({CONTENT_TYPE: fdp.ConsumeUnicode(30)}), | ||
| FuzzStream(fdp.ConsumeBytes(atheris.ALL_REMAINING)), | ||
| ) | ||
| if not obj.at_eof(): | ||
| await obj.form() | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we get the coverage from the fuzzing run? I'm not sure this is covering enough of the multipart code. |
||
|
|
||
|
|
||
| @atheris.instrument_func | ||
| def TestOneInput(data: bytes) -> None: | ||
| with suppress(ValueError): | ||
| asyncio.run(fuzz_bodypart_reader(data)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| atheris.Setup(sys.argv, TestOneInput) | ||
| atheris.Fuzz() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(): | ||
| from yarl import URL | ||
|
|
||
| from aiohttp.payload import StringPayload | ||
|
|
||
|
|
||
| @atheris.instrument_func | ||
| def TestOneInput(data: bytes) -> None: | ||
| fdp = atheris.FuzzedDataProvider(data) | ||
| 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) | ||
| atheris.Fuzz() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(): | ||
| from multidict import CIMultiDict | ||
| from yarl import URL | ||
|
|
||
| from aiohttp.test_utils import make_mocked_request | ||
|
|
||
|
|
||
| @atheris.instrument_func | ||
| async def fuzz_run_one_async(data: bytes) -> None: | ||
| fdp = atheris.FuzzedDataProvider(data) | ||
| 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 | ||
| def TestOneInput(data: bytes) -> None: | ||
| asyncio.run(fuzz_run_one_async(data)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) | ||
| atheris.Fuzz() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unclear how this variable is supposed to change.