Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ammaraskar opened this issue Mar 5, 2025 · 0 comments
Assignees
Labels
enhancement New feature or request

Comments

@ammaraskar
Copy link

ammaraskar commented Mar 5, 2025

Currently in libfuzzer, the hooks for strncmp, __sanitizer_weak_hook_strncmp calls into:

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

and then AddValueForMemcmp in turn does these two calls:

  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)


In comparison, the hook for strncmp in libafl-targets does:

__libafl_targets_cmplog_routines_len(k, s1 as *const u8, s2 as *const u8, actual_len);

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

MEMCPY(libafl_cmplog_map_ptr->vals.routines[k][hits].v0, ptr1, len);
MEMCPY(libafl_cmplog_map_ptr->vals.routines[k][hits].v1, ptr2, len);


This prevents libafl-libfuzzer from being able to solve things like this little harness (full harness attached harness.cpp.txt):

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.

@ammaraskar ammaraskar added the enhancement New feature or request label Mar 5, 2025
@addisoncrump addisoncrump self-assigned this Mar 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants