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

__sanitizer_cov_8bit_counters_init never invoked for interpreter #12

Open
azanegin opened this issue Dec 17, 2023 · 0 comments · May be fixed by #16
Open

__sanitizer_cov_8bit_counters_init never invoked for interpreter #12

azanegin opened this issue Dec 17, 2023 · 0 comments · May be fixed by #16

Comments

@azanegin
Copy link
Contributor

azanegin commented Dec 17, 2023

Will provide PR to fix in some time.

The __sanitizer_cov_8bit_counters_init() is a hook that LLVM SanitizerCoverage uses to point client code to inline counters that are incremented by instrumented code (well, when used 8bit-inline-ctrs instrumentation). Counters are usually allocated as a separate DSO section.

00000000000558c3 d __start___sancov_cntrs
0000000000055968 d __start___sancov_pcs
0000000000055961 d __stop___sancov_cntrs
0000000000056348 d __stop___sancov_pcs
000000000000e100 t sancov.module_ctor_8bit_counters

LibFuzzer uses this hook to a add those counters to monitored list. Global DSO-wide constructor inside sancov.module_ctor_8bit_counters in each loaded module calls this hook with pointers to ctrs section.

Code located here

TestOneInput(const uint8_t* data, size_t size) {

also invoke __sanitizer_cov_8bit_counters_init(). This is most likely intended for pointing to libfuzzer the region of counters that is used for interpreted code of the lua script.

Running test script outputs the following:

Starting program: /usr/bin/lua ../../luzer/tests/test_e2e.lua
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 2552143800
INFO: Loaded 1 modules   (158 inline 8-bit counters): 158 [0x7ffff7c2f8c3, 0x7ffff7c2f961),
INFO: Loaded 1 PC tables (158 PCs): 158 [0x7ffff7c2f968,0x7ffff7c30348),
[New Thread 0x7ffff622a6c0 (LWP 298)]
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: 32 ft: 33 corp: 1/1b exec/s: 0 rss: 36Mb
#7      NEW    cov: 33 ft: 34 corp: 2/2b lim: 4 exec/s: 0 rss: 36Mb L: 1/1 MS: 5 ShuffleBytes-ChangeByte-ShuffleBytes-CrossOver-ChangeBit-
#30     NEW    cov: 34 ft: 36 corp: 3/3b lim: 4 exec/s: 0 rss: 36Mb L: 1/1 MS: 3 ChangeByte-CopyPart-ChangeByte-
        NEW_FUNC[1/1]: 0x7ffff7be9161
#76     NEW    cov: 36 ft: 39 corp: 4/5b lim: 4 exec/s: 0 rss: 36Mb L: 2/2 MS: 1 InsertByte-
#78     NEW    cov: 36 ft: 42 corp: 5/7b lim: 4 exec/s: 0 rss: 36Mb L: 2/2 MS: 2 CrossOver-EraseBytes-
#124    NEW    cov: 36 ft: 44 corp: 6/8b lim: 4 exec/s: 0 rss: 36Mb L: 1/2 MS: 1 EraseBytes-

This creates a false impression that mmap-ed counters for lua code are working. As I pointed out above, each instrumented DSO should call the hook once. Then, luzer.so code should call the hook for dynamic counters. This is not the case.

root@trixie:~/luzer/build/luzer# gdb /usr/bin/lua
GNU gdb (Debian 13.2-1) 13.2
8<==============cut==================
(No debugging symbols found in /usr/bin/lua)
(gdb) b __sanitizer_cov_8bit_counters_init
Function "__sanitizer_cov_8bit_counters_init" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (__sanitizer_cov_8bit_counters_init) pending.
(gdb) run ../../luzer/tests/test_e2e.lua
Starting program: /usr/bin/lua ../../luzer/tests/test_e2e.lua
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x00007ffff7c1a3e8 in __sanitizer_cov_8bit_counters_init () from ./luzer.so
(gdb) bt
#0  0x00007ffff7c1a3e8 in __sanitizer_cov_8bit_counters_init () from ./luzer.so
#1  0x00007ffff7be8117 in sancov.module_ctor_8bit_counters () from ./luzer.so
#2  0x00007ffff7fcfd2e in call_init (env=0x7fffffffed10, argv=0x7fffffffecf8, argc=2, l=<optimized out>) at ./elf/dl-init.c:90
#3  call_init (l=<optimized out>, argc=2, argv=0x7fffffffecf8, env=0x7fffffffed10) at ./elf/dl-init.c:27
#4  0x00007ffff7fcfe14 in _dl_init (main_map=0x555555592280, argc=2, argv=0x7fffffffecf8, env=0x7fffffffed10) at ./elf/dl-init.c:137
#5  0x00007ffff7fcc516 in __GI__dl_catch_exception (exception=exception@entry=0x0, operate=operate@entry=0x7ffff7fd6570 <call_dl_init>, args=args@entry=0x7fffffffe0b0) at ./elf/dl-catch.c:211
#6  0x00007ffff7fd650e in dl_open_worker (a=a@entry=0x7fffffffe250) at ./elf/dl-open.c:808
#7  0x00007ffff7fcc489 in __GI__dl_catch_exception (exception=exception@entry=0x7fffffffe230, operate=operate@entry=0x7ffff7fd6480 <dl_open_worker>, args=args@entry=0x7fffffffe250) at ./elf/dl-catch.c:237
#8  0x00007ffff7fd68a8 in _dl_open (file=0x555555592158 "./luzer.so", mode=<optimized out>, caller_dlopen=0x55555557972b, nsid=<optimized out>, argc=2, argv=0x7fffffffecf8, env=0x7fffffffed10) at ./elf/dl-open.c:884
#9  0x00007ffff7d236f8 in dlopen_doit (a=a@entry=0x7fffffffe4c0) at ./dlfcn/dlopen.c:56
#10 0x00007ffff7fcc489 in __GI__dl_catch_exception (exception=exception@entry=0x7fffffffe420, operate=0x7ffff7d236a0 <dlopen_doit>, args=0x7fffffffe4c0) at ./elf/dl-catch.c:237
#11 0x00007ffff7fcc5af in _dl_catch_error (objname=0x7fffffffe478, errstring=0x7fffffffe480, mallocedp=0x7fffffffe477, operate=<optimized out>, args=<optimized out>) at ./elf/dl-catch.c:256
#12 0x00007ffff7d231e7 in _dlerror_run (operate=operate@entry=0x7ffff7d236a0 <dlopen_doit>, args=args@entry=0x7fffffffe4c0) at ./dlfcn/dlerror.c:138
#13 0x00007ffff7d237a9 in dlopen_implementation (dl_caller=<optimized out>, mode=<optimized out>, file=<optimized out>) at ./dlfcn/dlopen.c:71
#14 ___dlopen (file=<optimized out>, mode=<optimized out>) at ./dlfcn/dlopen.c:81
#15 0x000055555557972b in ?? ()
8<=======cut============
#37 0x000055555555a7ba in ?? ()
(gdb) c
Continuing.
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 3118463730
INFO: Loaded 1 modules   (158 inline 8-bit counters): 158 [0x7ffff7c2f8c3, 0x7ffff7c2f961),
INFO: Loaded 1 PC tables (158 PCs): 158 [0x7ffff7c2f968,0x7ffff7c30348),
[New Thread 0x7ffff622a6c0 (LWP 391)]
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: 32 ft: 33 corp: 1/1b exec/s: 0 rss: 70Mb
#23     NEW    cov: 33 ft: 34 corp: 2/2b lim: 4 exec/s: 0 rss: 70Mb L: 1/1 MS: 1 ChangeBinInt-
        NEW_FUNC[1/1]: 0x7ffff7be9161
#77     NEW    cov: 35 ft: 37 corp: 3/5b lim: 4 exec/s: 0 rss: 70Mb L: 3/3 MS: 4 CrossOver-InsertByte-ChangeByte-ChangeByte-
8<=====cut========
/usr/bin/lua: ../../luzer/tests/test_e2e.lua:7: assert has triggered
stack traceback:
        [C]: in function 'assert'
        ../../luzer/tests/test_e2e.lua:7: in function <../../luzer/tests/test_e2e.lua:3>
        [C]: in function 'Fuzz'
        ../../luzer/tests/test_e2e.lua:15: in main chunk
        [C]: ?
==390== ERROR: libFuzzer: fuzz target exited
[Thread 0x7ffff622a6c0 (LWP 391) exited]
[Inferior 1 (process 390) exited with code 0115]

As we can see here, _init() hook is called just once. And this call is from none other place than luzer.so DSO constructor. No call for mmap-ed counters ever made. Why?

if (counter_index_registered >= next_index) {

This condition is always true, as (contrast to atheris) in this code no counters are ever registered.

Basically, right now the Lua "debug hook instrumentation" is useless. Only way it ever reaches LibFuzzer is via another bug: #11

But luzer works for compiled, native lua modules, that were build with SanCov and register their own counters.

azanegin added a commit to azanegin/luzer that referenced this issue Dec 29, 2023
Until now, luzer had not used at all coverage information for
interpreted code. Hook-based instrumentation collected data, but
it were never passed to libfuzzer to drew features from. Memory
always were allocated in a fixed default kMax... size. This
commit includes a fix to properly pass counters to libfuzzer,
two systems to approximate optimal amount of 8-bit counters:
one based on testing, pre-run phase, and one based on active bytecode
size. Also, a minor fix to signal handling.

Fixes ligurio#12
azanegin added a commit to azanegin/luzer that referenced this issue Jan 24, 2024
Until now, luzer had not used at all coverage information for
interpreted code. Hook-based instrumentation collected data, but
it were never passed to libfuzzer to drew features from. Memory
always were allocated in a fixed default kMax... size. This
commit includes a fix to properly pass counters to libfuzzer,
two systems to approximate optimal amount of 8-bit counters:
one based on testing, pre-run phase, and one based on active bytecode
size. Also, a minor fix to signal handling and parameter name changes
to evade name shadowing of global variables.

Fixes ligurio#12
azanegin added a commit to azanegin/luzer that referenced this issue Jan 28, 2024
Until now, luzer had not used at all coverage information for
interpreted code. Hook-based instrumentation collected data, but
it were never passed to libfuzzer to drew features from. Memory
always were allocated in a fixed default kMax... size. This
commit includes a fix to properly pass counters to libfuzzer,
two systems to approximate optimal amount of 8-bit counters:
one based on testing, pre-run phase, and one based on active bytecode
size.
Changes to signatures of counter functions help fix bugs with
sign arithmetic.
Also, a minor fix to signal handling and parameter name changes
to evade name shadowing of global variables.

Fixes ligurio#12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant