Recently Written · git

ai-config

One canonical AI agent config (rules, skills, MCP servers) rendered into Claude Code, Codex, Copilot CLI and Antigravity by acsync

git clone https://github.com/equwal/ai-config

Log | Files | Refs


skills/dynamic-analysis/SKILL.md (6736 bytes)

1 ---
2 name: dynamic-analysis
3 description: Find memory errors, undefined behaviour, data races, and parser crashes using sanitizers (ASan, UBSan, TSan, MSan, Miri) and coverage-guided fuzzing (libFuzzer, cargo-fuzz, Go native fuzzing, AFL++). Use when working in C, C++, Rust unsafe, Go, or Zig; when writing or modifying code that parses untrusted input; when debugging a segfault, heap corruption, use-after-free, flaky crash, or suspected data race; or when adding a new parser, decoder, deserialiser, or protocol handler.
4 ---
5 
6 # Dynamic analysis
7 
8 Static analysis proves absence of some bugs. Sanitizers and fuzzing find the
9 bugs that are actually there. If the code parses input you did not generate,
10 this is not optional.
11 
12 ## Preflight
13 
14 Check availability before writing anything; report clearly if missing.
15 
16 ```bash
17 clang --version; command -v cargo-fuzz; go version; command -v afl-fuzz
18 rustup component list --toolchain nightly 2>/dev/null | grep -i miri
19 ```
20 
21 Sanitizers ship with clang and gcc. libFuzzer ships with clang. `cargo-fuzz`
22 needs `cargo install cargo-fuzz` and a nightly toolchain. Go fuzzing is built
23 into `go test`.
24 
25 ## Sanitizers
26 
27 Rebuild with instrumentation and run the existing test suite. This alone finds
28 real bugs in most untested C/C++ codebases.
29 
30 ```bash
31 # ASan + UBSan together — the default pairing
32 clang -fsanitize=address,undefined -fno-omit-frame-pointer \
33       -fno-sanitize-recover=all -g -O1 ...
34 ```
35 
36 `-fno-sanitize-recover=all` makes UBSan abort instead of printing and
37 continuing. Without it, UBSan findings scroll past in CI and get ignored.
38 
39 **ASan and TSan cannot be combined.** They use incompatible shadow memory
40 layouts. Use separate build directories:
41 
42 ```bash
43 cmake -B build-asan -DCMAKE_C_FLAGS="-fsanitize=address,undefined -g -O1"
44 cmake -B build-tsan -DCMAKE_C_FLAGS="-fsanitize=thread -g -O1"
45 ```
46 
47 | Sanitizer | Flag | Finds | Slowdown |
48 |---|---|---|---|
49 | ASan | `-fsanitize=address` | use-after-free, buffer overflow, double free, leaks | ~2x |
50 | UBSan | `-fsanitize=undefined` | signed overflow, misaligned access, invalid shifts, bad casts | ~1.2x |
51 | TSan | `-fsanitize=thread` | data races, lock-order inversion | ~5–15x |
52 | MSan | `-fsanitize=memory` | reads of uninitialised memory | ~3x |
53 
54 MSan requires **every** linked library to be instrumented, including libc++.
55 Uninstrumented dependencies produce false positives. Skip it unless the whole
56 dependency tree can be rebuilt.
57 
58 Useful runtime options:
59 
60 ```bash
61 export ASAN_OPTIONS=detect_leaks=1:abort_on_error=1:strict_string_checks=1
62 export UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1
63 export TSAN_OPTIONS=halt_on_error=1:second_deadlock_stack=1
64 ```
65 
66 Other languages:
67 
68 - **Rust**: `cargo +nightly miri test` for UB in `unsafe` blocks. Sanitizers via
69   `RUSTFLAGS="-Zsanitizer=address" cargo +nightly test`.
70 - **Go**: `go test -race ./...` — cheap, run it by default in CI.
71 
72 TSan and the Go race detector only find races on code paths that actually
73 execute concurrently during the run. A clean run is weak evidence. Run the
74 concurrent tests repeatedly (`go test -race -count=50 -run TestConcurrent`).
75 
76 ## Fuzzing
77 
78 Fuzz any function that takes bytes, a string, or a file from outside the
79 process. Target the parse/decode boundary, not the whole application.
80 
81 ### libFuzzer (C/C++)
82 
83 ```c
84 // fuzz/fuzz_parse.c
85 #include <stdint.h>
86 #include <stddef.h>
87 #include "parser.h"
88 
89 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
90     if (size > 64 * 1024) return -1;       // reject oversized inputs
91     parser_ctx *ctx = parser_new();
92     parser_feed(ctx, data, size);          // must not crash on any input
93     parser_free(ctx);                      // ASan catches leaks here
94     return 0;
95 }
96 ```
97 
98 ```bash
99 clang -fsanitize=fuzzer,address,undefined -fno-sanitize-recover=all \
100       -g -O1 fuzz/fuzz_parse.c parser.c -o fuzz_parse
101 
102 mkdir -p corpus
103 ./fuzz_parse corpus/ -max_total_time=120 -print_final_stats=1
104 ```
105 
106 Useful flags: `-jobs=$(nproc)` for parallel runs, `-max_len=` to bound input
107 size, `-dict=file.dict` to supply format keywords, `-runs=N` for a bounded
108 deterministic run in CI.
109 
110 ### cargo-fuzz (Rust)
111 
112 ```bash
113 cargo fuzz init
114 cargo fuzz add parse_target
115 cargo fuzz run parse_target -- -max_total_time=120
116 ```
117 
118 ```rust
119 // fuzz_targets/parse_target.rs
120 #![no_main]
121 use libfuzzer_sys::fuzz_target;
122 
123 fuzz_target!(|data: &[u8]| {
124     let _ = my_crate::parse(data);   // must not panic
125 });
126 ```
127 
128 For structured input, use the `arbitrary` crate:
129 `fuzz_target!(|input: MyStruct| { ... })`.
130 
131 ### Go native fuzzing
132 
133 ```go
134 func FuzzParse(f *testing.F) {
135     f.Add([]byte(`{"a":1}`))              // seed corpus
136     f.Fuzz(func(t *testing.T, data []byte) {
137         v, err := Parse(data)
138         if err != nil {
139             return                         // errors are fine; panics are not
140         }
141         out, err := Marshal(v)             // round-trip property
142         if err != nil {
143             t.Fatalf("marshal failed on parsed input: %v", err)
144         }
145         if v2, _ := Parse(out); !reflect.DeepEqual(v, v2) {
146             t.Fatalf("round trip mismatch")
147         }
148     })
149 }
150 ```
151 
152 ```bash
153 go test -fuzz=FuzzParse -fuzztime=120s ./pkg/parser
154 ```
155 
156 Failing inputs are written to `testdata/fuzz/FuzzParse/` and **become
157 permanent regression tests** on subsequent `go test` runs. Commit them.
158 
159 ### Seed corpus
160 
161 Fuzzing without seeds wastes most of its budget rediscovering valid syntax.
162 Seed from existing test fixtures:
163 
164 ```bash
165 mkdir -p corpus && cp tests/fixtures/*.json corpus/
166 ```
167 
168 Minimise the corpus periodically: `./fuzz_target -merge=1 corpus_min/ corpus/`.
169 
170 ## Triage of a crash
171 
172 1. **Reproduce deterministically**: `./fuzz_parse crash-<sha1>` — libFuzzer
173    writes the failing input to a `crash-*` file in the working directory.
174    Go writes to `testdata/fuzz/`.
175 2. **Minimise**: `./fuzz_parse -minimize_crash=1 -runs=10000 crash-<sha1>`.
176    Go: `go test -run=FuzzParse/<hash>`.
177 3. **Commit the minimised input as a unit test before fixing.** It must fail
178    on unfixed code — show that run.
179 4. Read the sanitizer stack trace to identify the mechanism. ASan reports both
180    the faulting access and the allocation/free sites; all three matter.
181 5. Fix, then re-run the fuzzer for at least the original duration to confirm
182    the crash is gone and no new one surfaced.
183 
184 ## CI integration
185 
186 Fuzzing in CI is a bounded regression run, not a discovery campaign:
187 
188 ```bash
189 ./fuzz_parse corpus/ -runs=100000 -max_total_time=60
190 go test -fuzz=FuzzParse -fuzztime=60s ./...
191 ```
192 
193 Long campaigns belong on a schedule (nightly, hours) or on OSS-Fuzz, which is
194 free for open-source projects and runs continuously. Always run the committed
195 corpus as plain tests on every PR — that is the regression suite.