{"id":51122505,"url":"https://github.com/aflplusplus/global-state-detector","last_synced_at":"2026-06-25T04:00:56.873Z","repository":{"id":353948348,"uuid":"1221517703","full_name":"AFLplusplus/global-state-detector","owner":"AFLplusplus","description":"Detects global state in fuzzing targets","archived":false,"fork":false,"pushed_at":"2026-04-26T10:58:16.000Z","size":21,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-26T12:27:09.612Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AFLplusplus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-26T10:19:00.000Z","updated_at":"2026-04-26T11:40:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/AFLplusplus/global-state-detector","commit_stats":null,"previous_names":["aflplusplus/global-state-detector"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/AFLplusplus/global-state-detector","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AFLplusplus%2Fglobal-state-detector","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AFLplusplus%2Fglobal-state-detector/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AFLplusplus%2Fglobal-state-detector/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AFLplusplus%2Fglobal-state-detector/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AFLplusplus","download_url":"https://codeload.github.com/AFLplusplus/global-state-detector/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AFLplusplus%2Fglobal-state-detector/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34758776,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-25T04:00:56.070Z","updated_at":"2026-06-25T04:00:56.864Z","avatar_url":"https://github.com/AFLplusplus.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# global-state-detector\n\n`global-state-detector` is a small C helper for `LLVMFuzzerTestOneInput` and AFL\n`__AFL_LOOP` persistent harnesses that report instability (a value that you can\nsee when using AFL++'s `afl-fuzz` or in LibAFL).\n\nIt discovers persistent writable global state between fuzzer iterations by\nsnapshotting writable ELF `PT_LOAD` segments after target initialization, then\ncompares later memory against that baseline so target globals that drift across\ninputs are visible.\n\nThis is useful when a fuzz target is expected to be deterministic and\niteration-local, but hidden `.data` or `.bss` state makes later inputs depend on\nearlier ones.\n\n## What It Checks\n\n- Writable non-executable `PT_LOAD` segments in the main binary.\n- Writable non-executable `PT_LOAD` segments in loaded shared objects discovered\n  through `dl_iterate_phdr`.\n- Page-level changes using a fast hash, followed by byte-range reporting for\n  changed pages.\n- Clang sanitizer coverage counters are ignored when the `__sancov_cntrs` range\n  is present, so normal libFuzzer coverage does not dominate reports.\n\nThe detector intentionally does not inspect heap objects, anonymous mappings,\nthread-local storage, files, sockets, or other external process state.\n\n## Platform Assumptions\n\nLinux (ELF) and macOS (Mach-O) are supported. The two paths are selected at\ncompile time and share all of the snapshot/diff/reporting logic.\n\nLinux:\n\n- `dl_iterate_phdr` to walk loaded objects\n- `dladdr` and a fallback `/proc/self/exe` `SHT_SYMTAB` parser for symbol\n  resolution (the parser also picks up `STB_LOCAL` symbols that `dladdr`\n  cannot see, e.g. Rust binary-crate statics)\n- ELF program headers from `\u003celf.h\u003e` / `\u003clink.h\u003e`\n- Link the harness with `-ldl -Wl,--export-dynamic -Wl,-z,now` so symbol\n  names in the main executable can be resolved and lazy PLT/GOT binding\n  does not show up as first-iteration writable state.\n\nmacOS:\n\n- `_dyld_image_count` / `_dyld_get_image_header` / `_dyld_get_image_name`\n  to walk loaded Mach-O images\n- `dladdr` for symbol resolution (already iterates the in-memory `nlist`\n  table, including locals — no separate fallback needed)\n- Mach-O headers from `\u003cmach-o/dyld.h\u003e` / `\u003cmach-o/loader.h\u003e` /\n  `\u003cmach-o/getsect.h\u003e`\n- No extra link flags required. `dlopen`/`dladdr` ship in `libSystem`, and\n  modern Mach-O linkers bind eagerly by default (chained fixups), so the\n  Linux `-z,now` / `--export-dynamic` equivalents are unnecessary.\n\nBuilds should use a clang-based fuzzer compiler (e.g. `afl-clang-fast` for\nAFL++, `clang` for libFuzzer).\n\n### AddressSanitizer Is Broken On Recent macOS\n\nOn recent Darwin, `-fsanitize=address` does not work: any harness — even\nhello-world — wedges at process init and spins at 100% CPU before `main`\nruns. This has been observed with both Apple Silicon Apple Clang\n(clang-2100.x) and Homebrew LLVM 21 on macOS 15+. The issue is in the\nASan runtime / loader interaction, not in this detector; the detector\nitself works fine on macOS without ASan.\n\nThe provided Makefile therefore drops `address` from the example's\nsanitizer set on Darwin, building with `-fsanitize=fuzzer,undefined`\ninstead of the full Linux `-fsanitize=fuzzer,address,undefined`. To\nexercise ASan integration on macOS you'll need to find a working\nclang/runtime combination and override `FUZZER_CFLAGS` /\n`FUZZER_LDFLAGS` by hand.\n\n## Build\n\n```sh\nmake\n```\n\nThis builds:\n\n- `global_state_detector.o`\n- `harness_example`\n\n## Harness Integration\n\nInclude the `global_state_detector.h` header and run any one-time target\ninitialization before taking the detector snapshot:\n\n```c\n#include \"global_state_detector.h\"\n\nint LLVMFuzzerInitialize(int *argc, char ***argv) {\n  (void)argc;\n  (void)argv;\n\n  /* target_init(); */\n\n  return 0;\n}\n```\n\nTake or refresh the snapshot immediately before target execution and check\nimmediately after it. This avoids reporting a fuzzer's own bookkeeping mutations\nbetween callbacks while still reporting writable global state changed by the\ntarget:\n```c\nint LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {\n  static int detector_ready = 0;\n\n  if (!detector_ready) {\n    detector_ready = 1;\n    global_state_detector_init();\n  } else {\n    global_state_detector_rebaseline();\n  }\n\n  target_process(data, size);\n\n  global_state_detector_check(/*rebaseline=*/1);\n\n  return 0;\n}\n```\n\nPass a non-zero `rebaseline` value to update the snapshot after reporting. Pass\n`0` to keep comparing against the previous snapshot and report cumulative drift.\n\n## Example\n\nBuild and run the included example:\n\n```sh\nmake\n./harness_example -runs=50\n```\n\nThe example intentionally mutates `target_accumulator` in `target_process`, so\nthe detector should report changed writable state during fuzz iterations.\n\nExample report shape:\n\n```text\n[global-state-detector] CHANGE 0x... len=...  symbol+0x...  ([main])\n               was: ...\n               now: ...\n```\n\n## Noise And Limitations\n\nSome runtime libraries maintain writable process state. The detector skips\ncommon libc, dynamic-linker, pthread, libstdc++, vDSO, and replacement malloc\nimplementations (jemalloc, mimalloc, tcmalloc, Hoard, snmalloc, rpmalloc,\nScudo) by basename prefix to reduce noise, but target-specific libraries may\nstill report expected state. On macOS the entire dyld shared cache and\n`/System` frameworks tree are skipped by path prefix in addition to the\nallocator basenames.\n\nOnly writable ELF segments are covered. If a target stores persistent state on\nthe heap or in custom mappings, this detector will not see it without additional\n`/proc/self/maps` support.\n\nThe detector is not thread-safe. Use it from a single-threaded harness or add\nexternal synchronization around initialization and checks.\n\n## License\n\n`global-state-detector` is distributed under the GNU Affero General Public\nLicense version 3 or later. See `LICENSE` for the full license text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faflplusplus%2Fglobal-state-detector","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faflplusplus%2Fglobal-state-detector","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faflplusplus%2Fglobal-state-detector/lists"}