AFLplusplus / AFLplusplus/LibAFL

[libafl-targets] Value profile guidance for strcmp/memcmp interceptor and friends

Open
#3,042 0 comments 1 reaction 1 assignee Claimed by @addisoncrump View on GitHub
enhancement
Dominant language
Rust
Stars
2.6k
Forks
481
Avg merge
2d 30m
Merged PRs (30d)
16

Description

Currently in libfuzzer, the hooks for strncmp, [`__sanitizer_weak_hook_strncmp`](https://github.com/llvm/llvm-project/blob/74df2032d467618a9aab085120539e306f21bcc0/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L628-L639) calls into:

```c
fuzzer::TPC.AddValueForMemcmp(caller_pc, s1, s2, n, /*StopAtZero*/true);
```

and then `AddValueForMemcmp` in turn does [these two calls](https://github.com/llvm/llvm-project/blob/74df2032d467618a9aab085120539e306f21bcc0/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L376-L377):

```c
ValueProfileMap.AddValue(Idx);
TORCW.Insert(Idx ^ Hash, Word(B1, Len), Word(B2, Len));
```

`TORCW` is the comparison logging mechanism but notice it also has `ValueProfileMap` guidance here.

(same code in [AFL++ here](https://github.com/AFLplusplus/AFLplusplus/blob/f590973387ee04d6c7ef016d5111313f9f4945b8/custom_mutators/libfuzzer/FuzzerTracePC.cpp#L432-L434))

---

In comparison, the hook for strncmp in `libafl-targets` does: https://github.com/AFLplusplus/LibAFL/blob/89342b22c2011f455566d1b5516177b9857dd3f2/libafl_targets/src/sancov_cmp.rs#L92

which in turn only adds the value to the cmplog map:

https://github.com/AFLplusplus/LibAFL/blob/89342b22c2011f455566d1b5516177b9857dd3f2/libafl_targets/src/cmplog.h#L190-L191

---

This prevents libafl-libfuzzer from being able to solve things like this little harness (full harness attached [harness.cpp.txt](https://github.com/user-attachments/files/19095581/harness.cpp.txt)):

```c
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
if (Size == 0) return 0;
char* encoded = (char*) malloc(Size * 3);
if (encoded == NULL) return 0;

int b64_length = b64_encode((unsigned char*) Data, Size, (unsigned char*) encoded);
// "Hello " in base64 so it's not visible to cmplog
if (strncmp(encoded, "SGVsbG8g", 8) == 0) {
// "World" in base64 so it's not visible to cmplog
if (b64_length > 8 && strncmp(encoded + b64_length - 8, "V29ybGQ=", 8) == 0) {
abort();
}
}

free(encoded);
return 0;
}
```

libfuzzer with value profiles can solve this rather easily because it breaks the strncmp into a brute-force of the base64 output:

```
$ time ./libfuzzer -use_value_profile=1
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 3161432840
INFO: Loaded 1 modules (16 inline 8-bit counters): 16 [0x55a96f6e0fe1, 0x55a96f6e0ff1),
INFO: Loaded 1 PC tables (16 PCs): 16 [0x55a96f6e0ff8,0x55a96f6e10f8),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 7 ft: 24 corp: 1/1b exec/s: 0 rss: 26Mb
...
0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64,
Hello World
artifact_prefix='./'; Test unit written to ./crash-0a4d55a8d778e5022fab701977c5d840bbc486d0
Base64: SGVsbG8gV29ybGQ=

real 0m20.850s
user 0m20.556s
sys 0m0.068s
...
```

but using libafl's libfuzzer shim for the same thing:
```
$ time ./libafl-libfuzzer -use_value_profile=1
WARNING: cowardly refusing to use grimoire since we cannot determine if the input is primarily text; set -grimoire=1 or provide a corpus directory.
[UserStats #0] (GLOBAL) run time: 0h-0m-0s, clients: 1, corpus: 0, objectives: 0, executions: 0, exec/sec: 0.000, edges: 43.750%
(CLIENT) corpus: 0, objectives: 0, executions: 0, exec/sec: 0.000, edges: 7/16 (43%)
[UserStats #0] (GLOBAL) run time: 0h-0m-0s, clients: 1, corpus: 0, objectives: 0, executions: 0, exec/sec: 0.000, edges: 43.750%, size_edges: 43.750%
...
[Client Heartbeat #0] (GLOBAL) run time: 0h-4m-30s, clients: 1, corpus: 10, objectives: 0, executions: 2015729, exec/sec: 7.459k, cmps: 0.011%, edges: 68.750%, size_edges: 68.750%, stability: 100.000%
(CLIENT) corpus: 10, objectives: 0, executions: 2015729, exec/sec: 7.459k, cmps: 7/65536 (0%), edges: 11/16 (68%), size_edges: 11/16 (68%), stability: 11/11 (100%)
[Client Heartbeat #0] (GLOBAL) run time: 0h-4m-45s, clients: 1, corpus: 10, objectives: 0, executions: 2126692, exec/sec: 7.455k, cmps: 0.011%, edges: 68.750%, size_edges: 68.750%, stability: 100.000%
(CLIENT) corpus: 10, objectives: 0, executions: 2126692, exec/sec: 7.455k, cmps: 7/65536 (0%), edges: 11/16 (68%), size_edges: 11/16 (68%), stability: 11/11 (100%)
[Client Heartbeat #0] (GLOBAL) run time: 0h-5m-0s, clients: 1, corpus: 10, objectives: 0, executions: 2240750, exec/sec: 7.463k, cmps: 0.011%, edges: 68.750%, size_edges: 68.750%, stability: 100.000%
(CLIENT) corpus: 10, objectives: 0, executions: 2240750, exec/sec: 7.463k, cmps: 7/65536 (0%), edges: 11/16 (68%), size_edges: 11/16 (68%), stability: 11/11 (100%)
^C

real 5m2.676s
user 4m59.504s
sys 0m3.055s
```
Notice there isn't an increase in `cmps`.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.