Skip to content
Open
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
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ endif()
# add C library
add_subdirectory("power_grid_model_c")

# fuzz harnesses (opt-in). Kept independent of PGM_ENABLE_DEV_BUILD so building
# a harness only needs the C API target above — it does not pull in doctest or
# the rest of the test tree. Defaults to the AFL++ engine; see tests/fuzzer.
option(PGM_ENABLE_FUZZER "Build fuzz harnesses (AFL++ by default; see tests/fuzzer)" OFF)
if(PGM_ENABLE_FUZZER)
add_subdirectory("tests/fuzzer")
endif()

# dev build
if(${PGM_ENABLE_DEV_BUILD})
include(CTest)
Expand Down
60 changes: 60 additions & 0 deletions tests/fuzzer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>
#
# SPDX-License-Identifier: MPL-2.0

# Fuzz harnesses for the Power Grid Model C API.
#
# These targets are only configured when PGM_ENABLE_FUZZER=ON. The default
# fuzzing engine is AFL++ (build with the afl-clang-fast toolchain); libFuzzer
# is also supported via -DPGM_FUZZER_ENGINE=libfuzzer.

set(PGM_FUZZER_ENGINE "afl" CACHE STRING
"Fuzzing engine for the harnesses: afl (default) or libfuzzer")
set_property(CACHE PGM_FUZZER_ENGINE PROPERTY STRINGS afl libfuzzer)

add_executable(pgm_fuzz_deserializer_json harness_deserializer_json.c)

# power_grid_model_c is defined as SHARED and exports its public include dir,
# so linking against it is enough to resolve <power_grid_model_c.h>.
target_link_libraries(pgm_fuzz_deserializer_json PRIVATE power_grid_model_c)

set_target_properties(pgm_fuzz_deserializer_json PROPERTIES LINKER_LANGUAGE CXX)

if(PGM_FUZZER_ENGINE STREQUAL "afl")
# AFL++: instrumentation comes from the compiler itself, so this requires
# configuring with the AFL++ toolchain, e.g.
# -DCMAKE_C_COMPILER=afl-clang-fast -DCMAKE_CXX_COMPILER=afl-clang-fast++
if(NOT CMAKE_C_COMPILER MATCHES "afl-clang")
message(FATAL_ERROR
"PGM_FUZZER_ENGINE=afl requires the AFL++ compiler. Configure with "
"-DCMAKE_C_COMPILER=afl-clang-fast -DCMAKE_CXX_COMPILER=afl-clang-fast++.")
endif()

# aflpp_driver supplies an AFL fork-server main() for LLVMFuzzerTestOneInput
# harnesses; it ships with AFL++ as libAFLDriver.a. Override the location
# with -DPGM_AFL_DRIVER=/path/to/libAFLDriver.a or by setting AFL_PATH.
find_library(PGM_AFL_DRIVER
NAMES AFLDriver
PATHS /usr/local/lib/afl /usr/lib/afl ENV AFL_PATH
PATH_SUFFIXES lib afl "")
if(NOT PGM_AFL_DRIVER)
message(FATAL_ERROR
"Could not find libAFLDriver.a (AFL++ aflpp_driver). Set AFL_PATH or "
"pass -DPGM_AFL_DRIVER=/path/to/libAFLDriver.a.")
endif()
target_link_libraries(pgm_fuzz_deserializer_json PRIVATE "${PGM_AFL_DRIVER}")

elseif(PGM_FUZZER_ENGINE STREQUAL "libfuzzer")
if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
message(FATAL_ERROR
"PGM_FUZZER_ENGINE=libfuzzer requires a Clang toolchain "
"(-fsanitize=fuzzer).")
endif()
# Coverage instrumentation + the libFuzzer runtime (provides main()).
target_compile_options(pgm_fuzz_deserializer_json PRIVATE -fsanitize=fuzzer-no-link)
target_link_options(pgm_fuzz_deserializer_json PRIVATE -fsanitize=fuzzer)

else()
message(FATAL_ERROR
"Unknown PGM_FUZZER_ENGINE '${PGM_FUZZER_ENGINE}' (use afl or libfuzzer).")
endif()
65 changes: 65 additions & 0 deletions tests/fuzzer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<!--
SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>

SPDX-License-Identifier: MPL-2.0
-->

# Fuzz harnesses

Coverage-guided fuzz harnesses for the Power Grid Model C API. They exercise the
untrusted-input entry points of the library and run under **AFL++** by default;
libFuzzer is also supported (both drive the same harness sources).

| Harness | Entry point | What it drives |
|---------|-------------|----------------|
| `harness_deserializer_json.c` | `PGM_create_deserializer_from_binary_buffer(..., PGM_json)` | JSON deserialization → dataset inspection → buffer alloc/parse → buffer get/set round-trips → `PGM_create_model` → symmetric / asymmetric power flow + short-circuit `PGM_calculate` |

Supporting material:

- `corpus_json/` — seed corpus (valid grids, edge cases, and malformed
documents) for `harness_deserializer_json`.
- `pgm.dict` — AFL++/libFuzzer dictionary of the PGM JSON envelope keys,
component names, attribute names, and common numeric literals.

## Building and running with AFL++ (default)

Requires the AFL++ toolchain (`afl-clang-fast` / `afl-clang-fast++`), which
instruments both the harness and the C API library it links against.

```bash
export AFL_USE_ASAN=1 # optional: also build with AddressSanitizer
cmake -S . -B build -G Ninja \
-DCMAKE_C_COMPILER=afl-clang-fast -DCMAKE_CXX_COMPILER=afl-clang-fast++ \
-DPGM_ENABLE_FUZZER=ON
cmake --build build --target pgm_fuzz_deserializer_json

afl-fuzz -i tests/fuzzer/corpus_json -o fuzz_out \
-x tests/fuzzer/pgm.dict -- ./build/bin/pgm_fuzz_deserializer_json @@
```

If `libAFLDriver.a` is not on a standard path, point the build at it with
`-DPGM_AFL_DRIVER=/path/to/libAFLDriver.a` or by exporting `AFL_PATH`.

## Building and running with libFuzzer (alternative)

Requires a Clang toolchain (this path uses `-fsanitize=fuzzer`).

```bash
cmake -S . -B build-libfuzzer -G Ninja \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DPGM_ENABLE_FUZZER=ON -DPGM_FUZZER_ENGINE=libfuzzer \
-DCMAKE_C_FLAGS="-fsanitize=address,undefined" \
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined"
cmake --build build-libfuzzer --target pgm_fuzz_deserializer_json

./build-libfuzzer/bin/pgm_fuzz_deserializer_json \
-dict=tests/fuzzer/pgm.dict tests/fuzzer/corpus_json
```

The harnesses expose the standard libFuzzer entry points
(`LLVMFuzzerTestOneInput` / `LLVMFuzzerInitialize`), which is what lets AFL++
(via `aflpp_driver`) and libFuzzer share the same sources with no changes. The
[OSS-Fuzz](https://github.com/google/oss-fuzz) `power-grid-model` project
compiles these same sources against `$LIB_FUZZING_ENGINE` — building for `afl`
and `libfuzzer` — and ships `corpus_json/` and `pgm.dict` as the seed corpus and
dictionary.
26 changes: 26 additions & 0 deletions tests/fuzzer/corpus_json/01_minimal_node_source.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"version": "1.0",
"type": "input",
"is_batch": false,
"attributes": {},
"data": {
"node": [
{
"id": 0,
"u_rated": 10000.0
}
],
"source": [
{
"id": 1,
"node": 0,
"status": 1,
"u_ref": 1.0,
"u_ref_angle": 0.0,
"sk": 1000000.0,
"rx_ratio": 0.1,
"z01_ratio": 1.0
}
]
}
}
36 changes: 36 additions & 0 deletions tests/fuzzer/corpus_json/02_minimal_with_load.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": "1.0",
"type": "input",
"is_batch": false,
"attributes": {},
"data": {
"node": [
{
"id": 0,
"u_rated": 400.0
}
],
"source": [
{
"id": 1,
"node": 0,
"status": 1,
"u_ref": 1.0,
"u_ref_angle": 0.0,
"sk": 5000000.0,
"rx_ratio": 0.1,
"z01_ratio": 1.0
}
],
"sym_load": [
{
"id": 2,
"node": 0,
"status": 1,
"type": 0,
"p_specified": 10000.0,
"q_specified": 5000.0
}
]
}
}
36 changes: 36 additions & 0 deletions tests/fuzzer/corpus_json/03_load_type_const_current.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": "1.0",
"type": "input",
"is_batch": false,
"attributes": {},
"data": {
"node": [
{
"id": 0,
"u_rated": 400.0
}
],
"source": [
{
"id": 1,
"node": 0,
"status": 1,
"u_ref": 1.0,
"u_ref_angle": 0.0,
"sk": 1000000.0,
"rx_ratio": 0.1,
"z01_ratio": 1.0
}
],
"sym_load": [
{
"id": 2,
"node": 0,
"status": 1,
"type": 1,
"p_specified": 10000.0,
"q_specified": 5000.0
}
]
}
}
36 changes: 36 additions & 0 deletions tests/fuzzer/corpus_json/03_load_type_const_impedance.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": "1.0",
"type": "input",
"is_batch": false,
"attributes": {},
"data": {
"node": [
{
"id": 0,
"u_rated": 400.0
}
],
"source": [
{
"id": 1,
"node": 0,
"status": 1,
"u_ref": 1.0,
"u_ref_angle": 0.0,
"sk": 1000000.0,
"rx_ratio": 0.1,
"z01_ratio": 1.0
}
],
"sym_load": [
{
"id": 2,
"node": 0,
"status": 1,
"type": 2,
"p_specified": 10000.0,
"q_specified": 5000.0
}
]
}
}
36 changes: 36 additions & 0 deletions tests/fuzzer/corpus_json/03_load_type_const_power.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": "1.0",
"type": "input",
"is_batch": false,
"attributes": {},
"data": {
"node": [
{
"id": 0,
"u_rated": 400.0
}
],
"source": [
{
"id": 1,
"node": 0,
"status": 1,
"u_ref": 1.0,
"u_ref_angle": 0.0,
"sk": 1000000.0,
"rx_ratio": 0.1,
"z01_ratio": 1.0
}
],
"sym_load": [
{
"id": 2,
"node": 0,
"status": 1,
"type": 0,
"p_specified": 10000.0,
"q_specified": 5000.0
}
]
}
}
36 changes: 36 additions & 0 deletions tests/fuzzer/corpus_json/04_voltage_ehv_380k.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": "1.0",
"type": "input",
"is_batch": false,
"attributes": {},
"data": {
"node": [
{
"id": 0,
"u_rated": 380000.0
}
],
"source": [
{
"id": 1,
"node": 0,
"status": 1,
"u_ref": 1.0,
"u_ref_angle": 0.0,
"sk": 1000000000.0,
"rx_ratio": 0.1,
"z01_ratio": 1.0
}
],
"sym_load": [
{
"id": 2,
"node": 0,
"status": 1,
"type": 0,
"p_specified": 10000.0,
"q_specified": 5000.0
}
]
}
}
36 changes: 36 additions & 0 deletions tests/fuzzer/corpus_json/04_voltage_hv_110k.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": "1.0",
"type": "input",
"is_batch": false,
"attributes": {},
"data": {
"node": [
{
"id": 0,
"u_rated": 110000.0
}
],
"source": [
{
"id": 1,
"node": 0,
"status": 1,
"u_ref": 1.0,
"u_ref_angle": 0.0,
"sk": 1000000000.0,
"rx_ratio": 0.1,
"z01_ratio": 1.0
}
],
"sym_load": [
{
"id": 2,
"node": 0,
"status": 1,
"type": 0,
"p_specified": 10000.0,
"q_specified": 5000.0
}
]
}
}
Loading