Skip to content

Commit

Permalink
Detect ignored exceptions
Browse files Browse the repository at this point in the history
Close #43
  • Loading branch information
senier committed Mar 17, 2024
1 parent 7b3d94f commit 4f66e19
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Catch and report internal worker errors
- Implement adaptive random fuzzing parameter selection (#24)
- Splicing mutation (#23)
- Detect ignored exceptions (#43)

### Fixed

Expand Down
47 changes: 38 additions & 9 deletions cobrafuzz/fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import traceback
from dataclasses import dataclass
from pathlib import Path
from types import TracebackType
from typing import Callable, Optional, Union, cast

import dill as pickle # type: ignore[import-untyped]
Expand Down Expand Up @@ -57,19 +58,19 @@ class Error(Report):
message: str


def covered(e: Exception) -> set[tuple[Optional[str], Optional[int], str, int]]:
"""Construct coverage information from exception."""
def covered(t: Optional[TracebackType]) -> set[tuple[Optional[str], Optional[int], str, int]]:
"""Construct coverage information from exception traceback."""

prev_line: Optional[int] = None
prev_file: Optional[str] = None
tb = e.__traceback__
covered = set()
result = set()
tb: Optional[TracebackType] = t
while tb:
covered.add((prev_file, prev_line, tb.tb_frame.f_code.co_filename, tb.tb_lineno))
result.add((prev_file, prev_line, tb.tb_frame.f_code.co_filename, tb.tb_lineno))
prev_line = tb.tb_lineno
prev_file = tb.tb_frame.f_code.co_filename
tb = tb.tb_next
return covered
return result


def worker( # noqa: PLR0913
Expand Down Expand Up @@ -163,18 +164,46 @@ def _worker_run(
) -> StatusBase:
tracer.reset()

unraisable_covered: Optional[set[tuple[Optional[str], Optional[int], str, int]]] = None
unraisable_message: Optional[str] = None

data = state.get_input()

def unraisablehook(unraisable: sys.UnraisableHookArgs) -> None:
message = (
"\n".join(map(str, unraisable.exc_value.args))
if unraisable.exc_value
else "Unraisable exception"
)
nonlocal unraisable_covered, unraisable_message
unraisable_covered = covered(unraisable.exc_traceback)
unraisable_message = message
del unraisable

_old_unraisablehook = sys.unraisablehook
sys.unraisablehook = unraisablehook

try:
target(bytes(data))
except Exception as e: # noqa: BLE001
return Error(
wid=wid,
runs=runs,
data=data,
covered=covered(e),
covered=covered(e.__traceback__),
message=f"{traceback.format_exc()}",
)
finally:
sys.unraisablehook = _old_unraisablehook

if unraisable_message:
return Error(
wid=wid,
runs=runs,
data=data,
covered=unraisable_covered or set(),
message=unraisable_message,
)

new_path = state.store_coverage(data=tracer.get_covered())

Expand Down Expand Up @@ -299,7 +328,7 @@ def _load_crashes(self, regression: bool) -> None:
target(f.read())
except Exception as e: # noqa: BLE001
if regression:
changed = local_state.store_coverage(covered(e))
changed = local_state.store_coverage(covered(e.__traceback__))
if changed:
logging.info(
"\n========================================================================\n"
Expand All @@ -308,7 +337,7 @@ def _load_crashes(self, regression: bool) -> None:
traceback.format_exc(),
)
else:
self._state.store_coverage(covered(e))
self._state.store_coverage(covered(e.__traceback__))
else:
if regression:
logging.info("No error when testing %s", error_file)
Expand Down
2 changes: 1 addition & 1 deletion tests/performance/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def bench_paths(args: argparse.Namespace) -> None:
increased = st.store_coverage(tracer.get_covered())
except Exception as e: # noqa: BLE001
traceback.print_exc()
st.store_coverage(fuzzer.covered(e))
st.store_coverage(fuzzer.covered(e.__traceback__))
increased = True

st.update(success=increased)
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/test_fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,3 +650,26 @@ def target(_: bytes) -> None:
assert all(
w[0].terminated and w[0].joined and w[0].timeout == 1 and w[1].canceled for w in workers
)


def test_worker_run_ignored_exception() -> None:
class Error:
def __del__(self) -> None:
raise DoneError("Exception in __del__")

def target(_: bytes) -> None:
Error()

result = fuzzer._worker_run( # noqa: SLF001
wid=1,
target=target,
state=DummyState(data=b"deadbeef"),
runs=1,
)

assert isinstance(result, fuzzer.Error)
assert result.wid == 1
assert result.runs == 1
assert result.data == b"deadbeef"
assert result.message == "Exception in __del__"
assert result.covered

0 comments on commit 4f66e19

Please sign in to comment.