Skip to content

Commit afe9691

Browse files
authored
Merge branch 'main' into fix/signature-deduplication
2 parents f8b8363 + 278f5ad commit afe9691

14 files changed

Lines changed: 291 additions & 7 deletions

.clusterfuzzlite/Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM gcr.io/oss-fuzz-base/base-builder-python
2+
3+
RUN apt-get update && apt-get install -y --no-install-recommends \
4+
protobuf-compiler \
5+
&& rm -rf /var/lib/apt/lists/*
6+
7+
COPY . $SRC/hiero-sdk-python
8+
WORKDIR $SRC/hiero-sdk-python
9+
COPY .clusterfuzzlite/build.sh $SRC/

.clusterfuzzlite/build.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash -eu
2+
3+
pip3 install grpcio-tools
4+
5+
python3 generate_proto.py
6+
7+
pip3 install .
8+
9+
pip3 install atheris pyinstaller
10+
11+
find "$SRC/hiero-sdk-python/.clusterfuzzlite" -name '*_fuzzer.py' -print0 | while IFS= read -r -d '' fuzzer; do
12+
fuzzer_basename=$(basename -s .py "$fuzzer")
13+
fuzzer_package="${fuzzer_basename}.pkg"
14+
15+
# Bundle the fuzzer and all its dependencies into a single portable package.
16+
pyinstaller --distpath "$OUT" --onefile --name "$fuzzer_package" "$fuzzer"
17+
18+
# Write a thin shell wrapper that ClusterFuzzLite uses to invoke the fuzzer.
19+
# The wrapper preloads the sanitizer library and sets ASAN_OPTIONS.
20+
# LD_PRELOAD is required here because the SDK uses C extensions (grpcio,
21+
# cryptography, protobuf) that must be covered by the sanitizer.
22+
cat > "$OUT/$fuzzer_basename" << EOF
23+
#!/bin/sh
24+
# LLVMFuzzerTestOneInput for fuzzer detection.
25+
this_dir=\$(dirname "\$0")
26+
LD_PRELOAD=\$this_dir/sanitizer_with_fuzzer.so \\
27+
PYCRYPTODOME_DISABLE_DEEPBIND=1 \\
28+
ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$this_dir/llvm-symbolizer:detect_leaks=0 \\
29+
"\$this_dir/$fuzzer_package" "\$@"
30+
EOF
31+
chmod +x "$OUT/$fuzzer_basename"
32+
done
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Atheris fuzz target: ContractFunctionParameters ABI encoding."""
2+
3+
from __future__ import annotations
4+
5+
import sys
6+
7+
import atheris
8+
9+
10+
with atheris.instrument_imports():
11+
from eth_abi.exceptions import EncodingTypeError, ValueOutOfBounds
12+
13+
from hiero_sdk_python import ContractFunctionParameters
14+
15+
16+
def TestOneInput(data: bytes) -> None:
17+
"""Feed arbitrary bytes into ContractFunctionParameters encoding paths."""
18+
fdp = atheris.FuzzedDataProvider(data)
19+
choice = fdp.ConsumeIntInRange(0, 9)
20+
params = ContractFunctionParameters()
21+
if choice == 0:
22+
params.add_bool(fdp.ConsumeBool())
23+
elif choice == 1:
24+
# add_address expects a 20-byte hex string or bytes
25+
params.add_address(fdp.ConsumeBytes(20).hex())
26+
elif choice == 2:
27+
params.add_string(fdp.ConsumeUnicodeNoSurrogates(256))
28+
elif choice == 3:
29+
params.add_bytes(fdp.ConsumeBytes(256))
30+
elif choice == 4:
31+
params.add_bytes32(fdp.ConsumeBytes(32))
32+
elif choice == 5:
33+
count = fdp.ConsumeIntInRange(0, 8)
34+
params.add_bool_array([fdp.ConsumeBool() for _ in range(count)])
35+
elif choice == 6:
36+
count = fdp.ConsumeIntInRange(0, 8)
37+
params.add_string_array([fdp.ConsumeUnicodeNoSurrogates(64) for _ in range(count)])
38+
elif choice == 7:
39+
count = fdp.ConsumeIntInRange(0, 8)
40+
params.add_bytes_array([fdp.ConsumeBytes(32) for _ in range(count)])
41+
elif choice == 8:
42+
count = fdp.ConsumeIntInRange(0, 8)
43+
params.add_address_array([fdp.ConsumeBytes(20).hex() for _ in range(count)])
44+
else:
45+
count = fdp.ConsumeIntInRange(0, 8)
46+
params.add_bytes32_array([fdp.ConsumeBytes(32) for _ in range(count)])
47+
try:
48+
params.to_bytes()
49+
except (TypeError, ValueError, EncodingTypeError, ValueOutOfBounds):
50+
# Malformed fuzz input can trigger expected validation/ABI encoding failures.
51+
pass
52+
53+
54+
atheris.Setup(sys.argv, TestOneInput)
55+
atheris.Fuzz()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Atheris fuzz target: entity ID string parsing."""
2+
3+
from __future__ import annotations
4+
5+
import sys
6+
7+
import atheris
8+
9+
10+
with atheris.instrument_imports():
11+
from hiero_sdk_python import AccountId, ContractId, FileId, TokenId, TopicId
12+
13+
_CLASSES = (AccountId, TokenId, ContractId, FileId, TopicId)
14+
15+
16+
def _try_parse_entity_id(entity_id_class, text: str) -> None:
17+
try:
18+
entity_id_class.from_string(text)
19+
except (TypeError, ValueError, IndexError, OverflowError):
20+
# Expected errors from malformed string input (parsing, indexing, conversions)
21+
pass
22+
23+
24+
def TestOneInput(data: bytes) -> None:
25+
"""Feed arbitrary strings into entity ID parsers."""
26+
fdp = atheris.FuzzedDataProvider(data)
27+
text = fdp.ConsumeUnicodeNoSurrogates(256)
28+
29+
for cls in _CLASSES:
30+
_try_parse_entity_id(cls, text)
31+
32+
33+
atheris.Setup(sys.argv, TestOneInput)
34+
atheris.Fuzz()

.clusterfuzzlite/keys_fuzzer.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Atheris fuzz target: PrivateKey and PublicKey parsing."""
2+
3+
from __future__ import annotations
4+
5+
import sys
6+
import warnings
7+
8+
import atheris
9+
10+
11+
with atheris.instrument_imports():
12+
from hiero_sdk_python import PrivateKey, PublicKey
13+
14+
15+
def _quiet(fn, *args):
16+
"""Call *fn* with *args*, suppressing UserWarning."""
17+
with warnings.catch_warnings():
18+
warnings.simplefilter("ignore", UserWarning)
19+
return fn(*args)
20+
21+
22+
def TestOneInput(data: bytes) -> None:
23+
"""Feed arbitrary bytes/strings into key parsers."""
24+
fdp = atheris.FuzzedDataProvider(data)
25+
choice = fdp.ConsumeIntInRange(0, 3)
26+
27+
try:
28+
if choice == 0:
29+
text = fdp.ConsumeUnicodeNoSurrogates(256)
30+
_quiet(PrivateKey.from_string, text)
31+
elif choice == 1:
32+
raw = fdp.ConsumeBytes(128)
33+
_quiet(PrivateKey.from_bytes, raw)
34+
elif choice == 2:
35+
text = fdp.ConsumeUnicodeNoSurrogates(256)
36+
_quiet(PublicKey.from_string, text)
37+
else:
38+
raw = fdp.ConsumeBytes(128)
39+
_quiet(PublicKey.from_bytes, raw)
40+
except ValueError:
41+
pass
42+
43+
44+
atheris.Setup(sys.argv, TestOneInput)
45+
atheris.Fuzz()

.clusterfuzzlite/project.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
language: python
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Atheris fuzz target: Transaction.from_bytes deserialization."""
2+
3+
from __future__ import annotations
4+
5+
import sys
6+
7+
import atheris
8+
9+
10+
with atheris.instrument_imports():
11+
from hiero_sdk_python import Transaction
12+
13+
14+
def TestOneInput(data: bytes) -> None:
15+
"""Feed arbitrary bytes into Transaction.from_bytes and re-serialise."""
16+
try:
17+
tx = Transaction.from_bytes(data)
18+
tx.to_bytes()
19+
except Exception:
20+
# Protobuf deserialization and transaction parsing can raise many
21+
# exception types (DecodeError, ValueError, TypeError, KeyError, etc.);
22+
# only unhandled exceptions that escape are reported as fuzzer crashes.
23+
pass
24+
25+
26+
atheris.Setup(sys.argv, TestOneInput)
27+
atheris.Fuzz()

.codacy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
exclude_paths:
33
- "tck/**"
4+
- ".clusterfuzzlite/**"
45

56
engines:
67
bandit:

.github/ISSUE_TEMPLATE/00-good-first-issue-candidate.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ body:
8282
- The complete steps to implement the solution
8383
- What the final result or output should look like
8484
- Links to relevant documentation or code (if helpful)
85-
For good first issues, please keep this as guided and clear as possible.
85+
For Good First Issues, please keep this as guided and clear as possible.
8686
value: |
8787
<!-- Identify what to change in which files -->
8888
@@ -95,7 +95,7 @@ body:
9595
attributes:
9696
label: 🧠 Good First Issue Developers — Prerequisites & Expectations
9797
description: |
98-
Adapt as needed. Concrete requirements before claiming this good first issue. Adapt as needed.
98+
Adapt as needed. Concrete requirements before claiming this Good First Issue. Adapt as needed.
9999
value: |
100100
> [!IMPORTANT]
101101
> **This issue does not require prior domain knowledge.**

.github/ISSUE_TEMPLATE/01-good-first-issue.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ body:
6969
- The complete steps to implement the solution
7070
- What the final result or output should look like
7171
- Links to relevant documentation or code (if helpful)
72-
For good first issues, please keep this as guided and clear as possible.
72+
For Good First Issues, please keep this as guided and clear as possible.
7373
value: |
7474
<!-- Identify what to change in which files -->
7575
@@ -82,7 +82,7 @@ body:
8282
attributes:
8383
label: 🧠 Good First Issue Developers — Prerequisites & Expectations
8484
description: |
85-
Adapt as needed. Concrete requirements before claiming this good first issue. Adapt as needed.
85+
Adapt as needed. Concrete requirements before claiming this Good First Issue. Adapt as needed.
8686
value: |
8787
> [!IMPORTANT]
8888
> **This issue does not require prior domain knowledge.**

0 commit comments

Comments
 (0)