Skip to content

Commit ca44496

Browse files
chore: adding fuzzing tests (#7903)
* chore: adding fuzzing tests * chore: adding fuzzing tests * chore: adding fuzzing tests
1 parent eceb809 commit ca44496

File tree

9 files changed

+213
-0
lines changed

9 files changed

+213
-0
lines changed

.clusterfuzzlite/Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM gcr.io/oss-fuzz-base/base-builder-python
2+
3+
# Copy project source
4+
COPY . $SRC/powertools
5+
6+
WORKDIR $SRC/powertools
7+
8+
# Install project dependencies
9+
RUN pip3 install -e ".[all]"
10+
11+
# Copy build script
12+
COPY .clusterfuzzlite/build.sh $SRC/

.clusterfuzzlite/build.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash -eu
2+
3+
# Build fuzz targets from tests/fuzz/
4+
for fuzzer in $(find $SRC/powertools/tests/fuzz -name 'fuzz_*.py'); do
5+
compile_python_fuzzer "$fuzzer"
6+
done

.clusterfuzzlite/project.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
language: python
2+
main_repo: https://github.com/aws-powertools/powertools-lambda-python
3+
sanitizers:
4+
- address
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: ClusterFuzzLite fuzzing
2+
3+
on:
4+
schedule:
5+
# Run daily at 8 AM UTC
6+
- cron: "0 8 * * *"
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
PR:
14+
runs-on: ubuntu-latest
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.ref }}
17+
cancel-in-progress: true
18+
steps:
19+
- name: Build Fuzzers
20+
id: build
21+
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
22+
with:
23+
language: python
24+
github-token: ${{ secrets.GITHUB_TOKEN }}
25+
sanitizer: address
26+
27+
- name: Run Fuzzers
28+
id: run
29+
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
30+
with:
31+
github-token: ${{ secrets.GITHUB_TOKEN }}
32+
fuzz-seconds: 30
33+
mode: code-change
34+
sanitizer: address

ruff.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,4 @@ runtime-evaluated-base-classes = ["pydantic.BaseModel"]
101101
"examples/*" = ["FA100", "TCH"]
102102
"tests/*" = ["FA100"]
103103
"aws_lambda_powertools/utilities/parser/models/*" = ["FA100"]
104+
"tests/fuzz/*" = ["FA100", "F401", "E402"]

tests/fuzz/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Fuzz testing targets for ClusterFuzzLite."""

tests/fuzz/fuzz_event_sources.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Fuzz target for Event Source Data Classes - SQS, SNS, API Gateway, Kinesis parsing."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
import sys
7+
8+
import atheris
9+
10+
with atheris.instrument_imports():
11+
from aws_lambda_powertools.utilities.data_classes import (
12+
APIGatewayProxyEvent,
13+
KinesisStreamEvent,
14+
SNSEvent,
15+
SQSEvent,
16+
)
17+
18+
19+
def fuzz_sqs_event(data: bytes) -> None:
20+
"""Fuzz SQS event parsing."""
21+
try:
22+
event_dict = json.loads(data.decode("utf-8", errors="ignore"))
23+
SQSEvent(event_dict)
24+
except (json.JSONDecodeError, KeyError, TypeError, ValueError):
25+
pass
26+
except Exception:
27+
pass
28+
29+
30+
def fuzz_sns_event(data: bytes) -> None:
31+
"""Fuzz SNS event parsing."""
32+
try:
33+
event_dict = json.loads(data.decode("utf-8", errors="ignore"))
34+
SNSEvent(event_dict)
35+
except (json.JSONDecodeError, KeyError, TypeError, ValueError):
36+
pass
37+
except Exception:
38+
pass
39+
40+
41+
def fuzz_api_gateway_event(data: bytes) -> None:
42+
"""Fuzz API Gateway event parsing."""
43+
try:
44+
event_dict = json.loads(data.decode("utf-8", errors="ignore"))
45+
APIGatewayProxyEvent(event_dict)
46+
except (json.JSONDecodeError, KeyError, TypeError, ValueError):
47+
pass
48+
except Exception:
49+
pass
50+
51+
52+
def fuzz_kinesis_event(data: bytes) -> None:
53+
"""Fuzz Kinesis event parsing."""
54+
try:
55+
event_dict = json.loads(data.decode("utf-8", errors="ignore"))
56+
KinesisStreamEvent(event_dict)
57+
except (json.JSONDecodeError, KeyError, TypeError, ValueError):
58+
pass
59+
except Exception:
60+
pass
61+
62+
63+
def fuzz_all_events(data: bytes) -> None:
64+
"""Fuzz all event sources."""
65+
fuzz_sqs_event(data)
66+
fuzz_sns_event(data)
67+
fuzz_api_gateway_event(data)
68+
fuzz_kinesis_event(data)
69+
70+
71+
def main() -> None:
72+
atheris.Setup(sys.argv, fuzz_all_events)
73+
atheris.Fuzz()
74+
75+
76+
if __name__ == "__main__":
77+
main()

tests/fuzz/fuzz_parser.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Fuzz target for Parser - Pydantic event validation."""
2+
3+
from __future__ import annotations
4+
5+
import sys
6+
7+
import atheris
8+
9+
with atheris.instrument_imports():
10+
from pydantic import BaseModel, ValidationError
11+
12+
from aws_lambda_powertools.utilities.parser import parse
13+
14+
15+
class SimpleModel(BaseModel):
16+
name: str
17+
value: int
18+
19+
20+
def fuzz_parser(data: bytes) -> None:
21+
"""Fuzz the parser with arbitrary JSON-like data."""
22+
try:
23+
parse(event=data.decode("utf-8", errors="ignore"), model=SimpleModel)
24+
except (ValidationError, ValueError, TypeError, KeyError):
25+
pass
26+
except Exception:
27+
pass
28+
29+
30+
def main() -> None:
31+
atheris.Setup(sys.argv, fuzz_parser)
32+
atheris.Fuzz()
33+
34+
35+
if __name__ == "__main__":
36+
main()

tests/fuzz/fuzz_validation.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Fuzz target for Validation - JSON Schema validation."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
import sys
7+
8+
import atheris
9+
10+
with atheris.instrument_imports():
11+
from aws_lambda_powertools.utilities.validation import validate
12+
from aws_lambda_powertools.utilities.validation.exceptions import SchemaValidationError
13+
14+
SIMPLE_SCHEMA = {
15+
"$schema": "http://json-schema.org/draft-07/schema#",
16+
"type": "object",
17+
"properties": {
18+
"name": {"type": "string"},
19+
"age": {"type": "integer"},
20+
},
21+
"required": ["name"],
22+
}
23+
24+
25+
def fuzz_validation(data: bytes) -> None:
26+
"""Fuzz JSON Schema validation."""
27+
try:
28+
event = json.loads(data.decode("utf-8", errors="ignore"))
29+
validate(event=event, schema=SIMPLE_SCHEMA)
30+
except (json.JSONDecodeError, SchemaValidationError, TypeError, ValueError):
31+
pass
32+
except Exception:
33+
pass
34+
35+
36+
def main() -> None:
37+
atheris.Setup(sys.argv, fuzz_validation)
38+
atheris.Fuzz()
39+
40+
41+
if __name__ == "__main__":
42+
main()

0 commit comments

Comments
 (0)