From 160b1dbedc2d052fc9e006abcc410d45a62744bf Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Fri, 5 Dec 2025 13:47:00 +0200 Subject: [PATCH 1/3] fix: fuzzing updated --- fuzz/FUZZING.md | 126 +++++++++++++++++++++++++++-- fuzz/dict/resp.dict | 98 ++++++++++++++++++++++ fuzz/run_fuzzer.sh | 9 ++- fuzz/seeds/resp/bitfield.resp | 11 +++ fuzz/seeds/resp/blpop.resp | 7 ++ fuzz/seeds/resp/eval.resp | 6 ++ fuzz/seeds/resp/hset.resp | 9 +++ fuzz/seeds/resp/json.resp | 9 +++ fuzz/seeds/resp/pipeline.resp | 25 ++++++ fuzz/seeds/resp/sadd.resp | 7 ++ fuzz/seeds/resp/watch_multi.resp | 18 +++++ fuzz/seeds/resp/xadd.resp | 11 +++ fuzz/seeds/resp/xread.resp | 13 +++ fuzz/seeds/resp/zadd.resp | 9 +++ fuzz/seeds/resp/zrangebyscore.resp | 11 +++ src/server/dfly_main.cc | 4 +- 16 files changed, 361 insertions(+), 12 deletions(-) create mode 100644 fuzz/seeds/resp/bitfield.resp create mode 100644 fuzz/seeds/resp/blpop.resp create mode 100644 fuzz/seeds/resp/eval.resp create mode 100644 fuzz/seeds/resp/hset.resp create mode 100644 fuzz/seeds/resp/json.resp create mode 100644 fuzz/seeds/resp/pipeline.resp create mode 100644 fuzz/seeds/resp/sadd.resp create mode 100644 fuzz/seeds/resp/watch_multi.resp create mode 100644 fuzz/seeds/resp/xadd.resp create mode 100644 fuzz/seeds/resp/xread.resp create mode 100644 fuzz/seeds/resp/zadd.resp create mode 100644 fuzz/seeds/resp/zrangebyscore.resp diff --git a/fuzz/FUZZING.md b/fuzz/FUZZING.md index 2591a1e46c47..0b4181f1740a 100644 --- a/fuzz/FUZZING.md +++ b/fuzz/FUZZING.md @@ -2,14 +2,20 @@ ## Install AFL++ +For effective fuzzing with crash replay support, AFL++ must be built from source with `AFL_PERSISTENT_RECORD` enabled. + ```bash -# From package manager (Ubuntu/Debian) +# Install dependencies sudo apt update -sudo apt install afl++ +sudo apt install llvm-18-dev clang-18 lld-18 gcc-13-plugin-dev -# Or build from source -git clone https://github.com/AFLplusplus/AFLplusplus +# Build AFL++ with AFL_PERSISTENT_RECORD support +git clone --depth=1 --branch v4.34c https://github.com/AFLplusplus/AFLplusplus.git cd AFLplusplus + +# Enable AFL_PERSISTENT_RECORD (required for stateful crash replay) +sed -i 's|// #define AFL_PERSISTENT_RECORD|#define AFL_PERSISTENT_RECORD|' include/config.h + make distrib sudo make install ``` @@ -22,22 +28,126 @@ sudo afl-system-config Sets core_pattern and CPU governors for optimal AFL++ performance. -## Build +## Build Dragonfly ```bash cmake -B build-dbg -DUSE_AFL=ON -DCMAKE_BUILD_TYPE=Debug -GNinja ninja -C build-dbg dragonfly ``` -## Run +## Run Fuzzer ```bash cd fuzz ./run_fuzzer.sh ``` -## Replay Crash on production dragonfly: +## AFL_PERSISTENT_RECORD (Stateful Crash Replay) + +Dragonfly uses AFL++ persistent mode for performance. This means multiple fuzzing iterations run within the same process, and the server accumulates state between iterations. + +**Problem:** When a crash occurs, AFL++ only saves the last input. But the crash may depend on state accumulated from previous inputs. + +**Solution:** `AFL_PERSISTENT_RECORD` saves the last N inputs before a crash, enabling replay of the full sequence. + +### Enable Recording + +```bash +# Set number of inputs to record before crash (e.g., last 100 inputs) +AFL_PERSISTENT_RECORD=100 ./run_fuzzer.sh +``` + +When a crash occurs, AFL++ saves files in the crashes directory: +``` +crashes/RECORD:000000,cnt:000000 (input N-99) +crashes/RECORD:000000,cnt:000001 (input N-98) +... +crashes/RECORD:000000,cnt:000099 (crashing input) +``` + +### Replay Recorded Crash + +```bash +# Set directory containing RECORD files +export AFL_PERSISTENT_DIR=./artifacts/resp/default/crashes + +# Replay specific record (e.g., record 000000) +AFL_PERSISTENT_REPLAY=000000 ./build-dbg/dragonfly --port=6379 +``` + +This replays all recorded inputs in sequence, reproducing the exact state that led to the crash. + +### Manual Replay (Alternative) + +If AFL_PERSISTENT_REPLAY doesn't work, replay manually: + ```bash -./dragonfly --port=6379 & +# Start dragonfly +./build-dbg/dragonfly --port=6379 & + +# Send each recorded input in order +for f in $(ls crashes/RECORD:000000,cnt:* | sort); do + nc localhost 6379 < "$f" +done +``` + +## Replay Simple Crash + +For crashes that don't depend on accumulated state: + +```bash +./build-dbg/dragonfly --port=6379 & nc localhost 6379 < artifacts/resp/default/crashes/id:000000,... ``` + +## Fuzzer Configuration + +The `run_fuzzer.sh` script configures: + +- **Timeout:** 500ms per input (adjust with `TIMEOUT` variable) +- **CMPLOG:** `-l 2` for automatic command discovery +- **Memory limit:** 4GB per instance +- **Disabled commands:** SHUTDOWN, DEBUG, FLUSHALL, FLUSHDB + +### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `AFL_PROACTOR_THREADS` | Dragonfly worker threads | 2 | +| `AFL_PERSISTENT_RECORD` | Inputs to save before crash | disabled | +| `BUILD_DIR` | Build directory path | `build-dbg` | +| `OUTPUT_DIR` | Fuzzer output directory | `artifacts/resp` | +| `CORPUS_DIR` | Corpus directory | `corpus/resp` | + +## Debugging Crashes + +1. **With ASAN (recommended for memory bugs):** + ```bash + # Build separate ASAN binary (not for fuzzing, only for replay) + cmake -B build-asan -DWITH_ASAN=ON -DCMAKE_BUILD_TYPE=Debug -GNinja + ninja -C build-asan dragonfly + + # Replay crash with ASAN + ./build-asan/dragonfly --port=6379 & + nc localhost 6379 < crashes/id:000000,... + ``` + +2. **With GDB:** + ```bash + gdb --args ./build-dbg/dragonfly --port=6379 + (gdb) run + # In another terminal: nc localhost 6379 < crash_file + ``` + +## Parallel Fuzzing + +Run multiple fuzzer instances for faster coverage: + +```bash +# Terminal 1 - Main instance +AFL_FINAL_SYNC=1 afl-fuzz -M main -o output ... -- ./dragonfly ... + +# Terminal 2-N - Secondary instances +afl-fuzz -S secondary1 -o output ... -- ./dragonfly ... +afl-fuzz -S secondary2 -o output ... -- ./dragonfly ... +``` diff --git a/fuzz/dict/resp.dict b/fuzz/dict/resp.dict index 9be1ed48507c..9053788486fe 100644 --- a/fuzz/dict/resp.dict +++ b/fuzz/dict/resp.dict @@ -194,3 +194,101 @@ "*3\x0d\x0a$3\x0d\x0aSET\x0d\x0a$3\x0d\x0akey\x0d\x0a$5\x0d\x0avalue\x0d\x0a" "*2\x0d\x0a$3\x0d\x0aDEL\x0d\x0a$3\x0d\x0akey\x0d\x0a" "*2\x0d\x0a$6\x0d\x0aEXISTS\x0d\x0a$3\x0d\x0akey\x0d\x0a" + +# Scripting commands +"EVAL" +"EVALSHA" +"EVAL_RO" +"EVALSHA_RO" +"SCRIPT" + +# Bitfield commands +"BITFIELD" +"BITFIELD_RO" +"BITOP" +"BITCOUNT" +"BITPOS" +"GETBIT" +"SETBIT" + +# More sorted set operations +"ZINTER" +"ZUNION" +"ZINTERSTORE" +"ZUNIONSTORE" +"ZPOPMIN" +"ZPOPMAX" +"BZPOPMIN" +"BZPOPMAX" +"ZMPOP" + +# More blocking commands +"BLMOVE" +"BLMPOP" +"BRPOPLPUSH" + +# Edge case numbers +"9223372036854775807" +"-9223372036854775808" +"2147483647" +"-2147483648" +"0.0" +"-0.0" +"inf" +"-inf" +"+inf" +"nan" + +# Stream IDs and patterns +"0-0" +"0-*" +"$" +">" +"*" +"MAXLEN" +"MINID" + +# JSON paths +"$.." +"$[*]" +"$[-1]" +"$.name" +"$..name" + +# RESP protocol edge cases +"*-1\x0d\x0a" +"$-1\x0d\x0a" +"*0\x0d\x0a" +"$0\x0d\x0a\x0d\x0a" + +# Lua scripting patterns +"return redis.call" +"redis.pcall" +"KEYS[1]" +"ARGV[1]" + +# Bitfield subcommands +"OVERFLOW" +"WRAP" +"SAT" +"FAIL" + +# Aggregate options +"AGGREGATE" +"SUM" +"MIN" +"MAX" +"WEIGHTS" + +# Binary edge cases +"\x00" +"\xff" +"\x00\x00\x00\x00" + +# Consumer group commands +"CREATE" +"DESTROY" +"SETID" +"DELCONSUMER" +"CONSUMERS" +"GROUPS" diff --git a/fuzz/run_fuzzer.sh b/fuzz/run_fuzzer.sh index 1b9e0736035b..e0961b9a7ed7 100755 --- a/fuzz/run_fuzzer.sh +++ b/fuzz/run_fuzzer.sh @@ -17,7 +17,7 @@ OUTPUT_DIR="${OUTPUT_DIR:-$FUZZ_DIR/artifacts/$TARGET}" CORPUS_DIR="${CORPUS_DIR:-$FUZZ_DIR/corpus/$TARGET}" SEEDS_DIR="${SEEDS_DIR:-$FUZZ_DIR/seeds/$TARGET}" DICT_FILE="${DICT_FILE:-$FUZZ_DIR/dict/$TARGET.dict}" -TIMEOUT="10000+" +TIMEOUT="500" FUZZ_TARGET="$BUILD_DIR/dragonfly" AFL_PROACTOR_THREADS="${AFL_PROACTOR_THREADS:-2}" @@ -79,9 +79,10 @@ run_fuzzer() { AFL_CMD=( afl-fuzz -D + -l 2 -o "${OUTPUT_DIR}" -t "${TIMEOUT}" - -m none + -m 4096 -i "${CORPUS_DIR}" ) @@ -99,6 +100,10 @@ run_fuzzer() { --bind=:: --dbfilename="" --omit_basic_usage + --rename_command=SHUTDOWN= + --rename_command=DEBUG= + --rename_command=FLUSHALL= + --rename_command=FLUSHDB= ) print_info "Running: ${AFL_CMD[*]}" diff --git a/fuzz/seeds/resp/bitfield.resp b/fuzz/seeds/resp/bitfield.resp new file mode 100644 index 000000000000..26ba00ddc17e --- /dev/null +++ b/fuzz/seeds/resp/bitfield.resp @@ -0,0 +1,11 @@ +*6 +$8 +BITFIELD +$3 +key +$3 +GET +$2 +u8 +$1 +0 diff --git a/fuzz/seeds/resp/blpop.resp b/fuzz/seeds/resp/blpop.resp new file mode 100644 index 000000000000..72eeb8c08561 --- /dev/null +++ b/fuzz/seeds/resp/blpop.resp @@ -0,0 +1,7 @@ +*3 +$5 +BLPOP +$4 +list +$1 +1 diff --git a/fuzz/seeds/resp/eval.resp b/fuzz/seeds/resp/eval.resp new file mode 100644 index 000000000000..cd710e7294ff --- /dev/null +++ b/fuzz/seeds/resp/eval.resp @@ -0,0 +1,6 @@ +*3 +$4 +EVAL +$26 +return redis.call("PING") +$0 diff --git a/fuzz/seeds/resp/hset.resp b/fuzz/seeds/resp/hset.resp new file mode 100644 index 000000000000..e308dc72d8fc --- /dev/null +++ b/fuzz/seeds/resp/hset.resp @@ -0,0 +1,9 @@ +*4 +$4 +HSET +$4 +hash +$5 +field +$5 +value diff --git a/fuzz/seeds/resp/json.resp b/fuzz/seeds/resp/json.resp new file mode 100644 index 000000000000..de9db788c1fb --- /dev/null +++ b/fuzz/seeds/resp/json.resp @@ -0,0 +1,9 @@ +*4 +$8 +JSON.SET +$3 +doc +$1 +$ +$15 +{"name":"test"} diff --git a/fuzz/seeds/resp/pipeline.resp b/fuzz/seeds/resp/pipeline.resp new file mode 100644 index 000000000000..e61eb7f3b473 --- /dev/null +++ b/fuzz/seeds/resp/pipeline.resp @@ -0,0 +1,25 @@ +*1 +$4 +PING +*3 +$3 +SET +$1 +a +$1 +1 +*2 +$4 +INCR +$1 +a +*2 +$3 +GET +$1 +a +*2 +$3 +DEL +$1 +a diff --git a/fuzz/seeds/resp/sadd.resp b/fuzz/seeds/resp/sadd.resp new file mode 100644 index 000000000000..8a5fc2edd27e --- /dev/null +++ b/fuzz/seeds/resp/sadd.resp @@ -0,0 +1,7 @@ +*3 +$4 +SADD +$3 +set +$6 +member diff --git a/fuzz/seeds/resp/watch_multi.resp b/fuzz/seeds/resp/watch_multi.resp new file mode 100644 index 000000000000..72090da68f5a --- /dev/null +++ b/fuzz/seeds/resp/watch_multi.resp @@ -0,0 +1,18 @@ +*2 +$5 +WATCH +$1 +k +*1 +$5 +MULTI +*3 +$3 +SET +$1 +k +$1 +1 +*1 +$4 +EXEC diff --git a/fuzz/seeds/resp/xadd.resp b/fuzz/seeds/resp/xadd.resp new file mode 100644 index 000000000000..4544e72896aa --- /dev/null +++ b/fuzz/seeds/resp/xadd.resp @@ -0,0 +1,11 @@ +*5 +$4 +XADD +$6 +stream +$1 +* +$5 +field +$5 +value diff --git a/fuzz/seeds/resp/xread.resp b/fuzz/seeds/resp/xread.resp new file mode 100644 index 000000000000..4855962ccda2 --- /dev/null +++ b/fuzz/seeds/resp/xread.resp @@ -0,0 +1,13 @@ +*5 +$5 +XREAD +$5 +COUNT +$1 +1 +$7 +STREAMS +$6 +stream +$1 +0 diff --git a/fuzz/seeds/resp/zadd.resp b/fuzz/seeds/resp/zadd.resp new file mode 100644 index 000000000000..c42cf8c6d4da --- /dev/null +++ b/fuzz/seeds/resp/zadd.resp @@ -0,0 +1,9 @@ +*5 +$4 +ZADD +$4 +zset +$1 +1 +$6 +member diff --git a/fuzz/seeds/resp/zrangebyscore.resp b/fuzz/seeds/resp/zrangebyscore.resp new file mode 100644 index 000000000000..b62f988616e9 --- /dev/null +++ b/fuzz/seeds/resp/zrangebyscore.resp @@ -0,0 +1,11 @@ +*5 +$13 +ZRANGEBYSCORE +$4 +zset +$4 +-inf +$4 ++inf +$10 +WITHSCORES diff --git a/src/server/dfly_main.cc b/src/server/dfly_main.cc index fc2fde0685a9..9afeca0597fb 100644 --- a/src/server/dfly_main.cc +++ b/src/server/dfly_main.cc @@ -720,8 +720,8 @@ ssize_t ReadFuzzInput(char* buffer, size_t buffer_size) { void SendFuzzInputToServer(uint16_t port, const char* data, ssize_t len) { int s = socket(AF_INET, SOCK_STREAM, 0); if (s >= 0) { - // Set short timeout to prevent hanging on slow responses - struct timeval tv = {.tv_sec = 0, .tv_usec = 100000}; // 100ms + // Set timeout for command processing (2 seconds for complex commands) + struct timeval tv = {.tv_sec = 2, .tv_usec = 0}; // 2s setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); From 9248862ef3413c13263c57b5e1b83bdb146d6c82 Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Fri, 5 Dec 2025 14:06:36 +0200 Subject: [PATCH 2/3] fix: hangs fixed --- fuzz/FUZZING.md | 52 -------------------------------------- fuzz/dict/resp.dict | 18 ------------- fuzz/run_fuzzer.sh | 3 +++ fuzz/seeds/resp/blpop.resp | 7 ----- 4 files changed, 3 insertions(+), 77 deletions(-) delete mode 100644 fuzz/seeds/resp/blpop.resp diff --git a/fuzz/FUZZING.md b/fuzz/FUZZING.md index 0b4181f1740a..7ddc44d69af5 100644 --- a/fuzz/FUZZING.md +++ b/fuzz/FUZZING.md @@ -99,55 +99,3 @@ For crashes that don't depend on accumulated state: ./build-dbg/dragonfly --port=6379 & nc localhost 6379 < artifacts/resp/default/crashes/id:000000,... ``` - -## Fuzzer Configuration - -The `run_fuzzer.sh` script configures: - -- **Timeout:** 500ms per input (adjust with `TIMEOUT` variable) -- **CMPLOG:** `-l 2` for automatic command discovery -- **Memory limit:** 4GB per instance -- **Disabled commands:** SHUTDOWN, DEBUG, FLUSHALL, FLUSHDB - -### Environment Variables - -| Variable | Description | Default | -|----------|-------------|---------| -| `AFL_PROACTOR_THREADS` | Dragonfly worker threads | 2 | -| `AFL_PERSISTENT_RECORD` | Inputs to save before crash | disabled | -| `BUILD_DIR` | Build directory path | `build-dbg` | -| `OUTPUT_DIR` | Fuzzer output directory | `artifacts/resp` | -| `CORPUS_DIR` | Corpus directory | `corpus/resp` | - -## Debugging Crashes - -1. **With ASAN (recommended for memory bugs):** - ```bash - # Build separate ASAN binary (not for fuzzing, only for replay) - cmake -B build-asan -DWITH_ASAN=ON -DCMAKE_BUILD_TYPE=Debug -GNinja - ninja -C build-asan dragonfly - - # Replay crash with ASAN - ./build-asan/dragonfly --port=6379 & - nc localhost 6379 < crashes/id:000000,... - ``` - -2. **With GDB:** - ```bash - gdb --args ./build-dbg/dragonfly --port=6379 - (gdb) run - # In another terminal: nc localhost 6379 < crash_file - ``` - -## Parallel Fuzzing - -Run multiple fuzzer instances for faster coverage: - -```bash -# Terminal 1 - Main instance -AFL_FINAL_SYNC=1 afl-fuzz -M main -o output ... -- ./dragonfly ... - -# Terminal 2-N - Secondary instances -afl-fuzz -S secondary1 -o output ... -- ./dragonfly ... -afl-fuzz -S secondary2 -o output ... -- ./dragonfly ... -``` diff --git a/fuzz/dict/resp.dict b/fuzz/dict/resp.dict index 9053788486fe..c0a17ebcb8aa 100644 --- a/fuzz/dict/resp.dict +++ b/fuzz/dict/resp.dict @@ -34,8 +34,6 @@ "LINDEX" "LSET" "LTRIM" -"BLPOP" -"BRPOP" # Hash operations "HSET" @@ -186,7 +184,6 @@ "LIMIT" "COUNT" "MATCH" -"BLOCK" # Common RESP command patterns "*1\x0d\x0a$4\x0d\x0aPING\x0d\x0a" @@ -218,15 +215,8 @@ "ZUNIONSTORE" "ZPOPMIN" "ZPOPMAX" -"BZPOPMIN" -"BZPOPMAX" "ZMPOP" -# More blocking commands -"BLMOVE" -"BLMPOP" -"BRPOPLPUSH" - # Edge case numbers "9223372036854775807" "-9223372036854775808" @@ -284,11 +274,3 @@ "\x00" "\xff" "\x00\x00\x00\x00" - -# Consumer group commands -"CREATE" -"DESTROY" -"SETID" -"DELCONSUMER" -"CONSUMERS" -"GROUPS" diff --git a/fuzz/run_fuzzer.sh b/fuzz/run_fuzzer.sh index e0961b9a7ed7..b3d21c9524d0 100755 --- a/fuzz/run_fuzzer.sh +++ b/fuzz/run_fuzzer.sh @@ -112,6 +112,9 @@ run_fuzzer() { cd "${OUTPUT_DIR}" # Run AFL++ - fuzzing integrated in dragonfly via USE_AFL + # AFL_HANG_TMOUT: Only consider it a hang if no response for 60 seconds + # This prevents false positives from slow but legitimate operations + export AFL_HANG_TMOUT=60000 exec "${AFL_CMD[@]}" } diff --git a/fuzz/seeds/resp/blpop.resp b/fuzz/seeds/resp/blpop.resp deleted file mode 100644 index 72eeb8c08561..000000000000 --- a/fuzz/seeds/resp/blpop.resp +++ /dev/null @@ -1,7 +0,0 @@ -*3 -$5 -BLPOP -$4 -list -$1 -1 From 17d7c5374c24e704aea72ded6ee2aabfb1869e20 Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Fri, 5 Dec 2025 17:45:01 +0200 Subject: [PATCH 3/3] fix: github action was updated --- .github/actions/fuzzing/action.yml | 9 +++------ .github/workflows/fuzz-long.yml | 2 +- .github/workflows/fuzz-smoke.yml | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/actions/fuzzing/action.yml b/.github/actions/fuzzing/action.yml index c8731f0f4c65..a852f29cc966 100644 --- a/.github/actions/fuzzing/action.yml +++ b/.github/actions/fuzzing/action.yml @@ -23,14 +23,10 @@ inputs: runs: using: "composite" steps: - - name: Install AFL++ + - name: Verify AFL++ installation shell: bash run: | - echo "Installing AFL++..." - apt-get update -qq - apt-get install -y -qq afl++ lld-17 > /dev/null - - echo "AFL++ installed successfully" + echo "Verifying AFL++ installation..." afl-fuzz -h | head -5 || true # Verify AFL++ compilers are available @@ -99,6 +95,7 @@ runs: AFL_TESTCACHE_SIZE: ${{ inputs.mode == 'smoke' && '50' || '500' }} AFL_SKIP_CPUFREQ: ${{ inputs.mode == 'long' && '1' || '' }} AFL_FAST_CAL: ${{ inputs.mode == 'long' && '1' || '' }} + AFL_PERSISTENT_RECORD: 1000 - name: Analyze fuzzing results shell: bash diff --git a/.github/workflows/fuzz-long.yml b/.github/workflows/fuzz-long.yml index 50e2a3f7fbfc..786fc33811be 100644 --- a/.github/workflows/fuzz-long.yml +++ b/.github/workflows/fuzz-long.yml @@ -36,7 +36,7 @@ jobs: instance: [0] # Can be expanded to [0, 1, 2, 3] for parallel fuzzing container: - image: ghcr.io/romange/ubuntu-dev:24 + image: ghcr.io/romange/ubuntu-dev:24-afl options: --security-opt seccomp=unconfined --sysctl "net.ipv6.conf.all.disable_ipv6=0" credentials: username: ${{ github.repository_owner }} diff --git a/.github/workflows/fuzz-smoke.yml b/.github/workflows/fuzz-smoke.yml index 60228af44403..211236f4b821 100644 --- a/.github/workflows/fuzz-smoke.yml +++ b/.github/workflows/fuzz-smoke.yml @@ -26,7 +26,7 @@ jobs: timeout-minutes: 45 container: - image: ghcr.io/romange/ubuntu-dev:24 + image: ghcr.io/romange/ubuntu-dev:24-afl options: --security-opt seccomp=unconfined --sysctl "net.ipv6.conf.all.disable_ipv6=0" credentials: username: ${{ github.repository_owner }}