# Fuzz Testing for afpd

This directory contains a [libFuzzer](https://llvm.org/docs/LibFuzzer.html)-based
fuzz target that exercises AFP file and directory operations (createdir, createfile,
delete, getfiledirparms, enumerate) with fuzzer-generated names.

## Prerequisites

- Clang (libFuzzer is built into clang)
- The same build dependencies as netatalk itself (libevent, libgcrypt, etc.)
- `uuidgen` (from uuid-runtime) for generating `test.conf`

## Local Development

### Build

Generate the test configuration, then configure and build:

```sh
cd test/afpd && bash test.sh && cd ../..
CC=clang meson setup build -Dwith-fuzzing=true -Dwith-dtrace=false
ninja -C build test/afpd/afpd_fuzz
```

By default, when no external fuzzing engine is specified, the build uses
`-fsanitize=fuzzer,address` for both compilation and linking. This enables
libFuzzer and AddressSanitizer together.

### Run

Run the fuzzer with the seed corpus:

```sh
cd test/afpd
../../build/test/afpd/afpd_fuzz fuzz_seed_corpus/
```

To run for a fixed duration (e.g. 60 seconds):

```sh
../../build/test/afpd/afpd_fuzz fuzz_seed_corpus/ -max_total_time=60
```

The target already caps at 230 bytes internally,
but you can also set a maximum input length from the fuzzer:

```sh
../../build/test/afpd/afpd_fuzz fuzz_seed_corpus/ -max_len=100
```

Crash reproducers are written to the current directory by default. To replay a
specific crash input:

```sh
../../build/test/afpd/afpd_fuzz crash-<hash>
```

### Useful libFuzzer Options

| Option | Description |
|---|---|
| `-max_total_time=N` | Stop after N seconds |
| `-max_len=N` | Maximum input size in bytes |
| `-jobs=N` | Run N fuzzing jobs in parallel |
| `-workers=N` | Number of worker processes |
| `-dict=FILE` | Use a fuzzer dictionary |
| `-seed=N` | Use a specific random seed for reproducibility |
| `-print_final_stats=1` | Print statistics on exit |
| `-help=1` | Show all available options |

## CI with ClusterFuzzLite

We have a GitHub Actions workflow that runs [ClusterFuzzLite](https://google.github.io/clusterfuzzlite/)
on pushes and pull requests.
It builds the fuzz target inside a container and runs it in `code-change` mode with AddressSanitizer.

### Testing the ClusterFuzzLite Build Locally

Build the container:

```sh
podman build -t netatalk-fuzz -f .clusterfuzzlite/Dockerfile .
```

Run the build script inside the container (approximating the OSS-Fuzz
environment):

```sh
podman run --rm netatalk-fuzz /bin/bash /src/build.sh
```

## Using an External Fuzzing Engine

For integration with OSS-Fuzz or other fuzzing infrastructure that provides its
own fuzzing engine library, pass the library path at configure time:

```sh
meson setup build \
    -Dwith-fuzzing=true \
    -Dwith-fuzzing-engine=/path/to/libFuzzingEngine.a \
    -Dwith-dtrace=false
```

When a fuzzing engine is specified, the build does not add `-fsanitize=fuzzer`
to compiler or linker flags, since the external engine provides the `main()`
entry point. Sanitizer flags are expected to come from the environment (e.g.
via `$CFLAGS` and `$LDFLAGS` set by OSS-Fuzz).

## Seed Corpus

The `fuzz_seed_corpus/` directory contains initial inputs to bootstrap the
fuzzer:

| File | Content | Purpose |
|---|---|---|
| `seed_simple` | `testfile` | Basic ASCII filename |
| `seed_slash` | `dir1/file1` | Tests AFP path separator conversion |
| `seed_nulls` | `\x00\x00` | Tests null byte handling |
| `seed_long` | 200 bytes of `A` | Tests long name handling |
| `seed_utf8` | Multi-byte UTF-8 | Tests Unicode name handling |

## What the Fuzzer Tests

The fuzz target (`fuzz.c`) performs one-time initialization identical to the
integration test harness (`test.c`): it parses `test.conf`, initializes CNID
and dircache, and opens the test volume. On each fuzzer iteration it uses the
fuzzer-generated bytes as a file/directory name and calls:

1. `createdir` - create a directory with the fuzzed name
2. `getfiledirparms` - query parameters for the created directory
3. `delete` - remove the directory
4. `createfile` - create a file with the fuzzed name
5. `getfiledirparms` - query parameters for the created file
6. `delete` - remove the file
7. `enumerate` - list the volume root

Return values are ignored; the fuzzer is looking for crashes, memory errors,
and undefined behavior rather than testing correctness.
