Re: [PATCH] libselinux: fix SEGV in JIT matching of recursive patterns in partial mode
Stephen Smalley <[email protected]>
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <CAEjxPJ4yW94x_DLSe=zdmfKB95aagFmydTKsBnWGfjc6efr4hA@mail.gmail.com> |
On Thu, Aug 13, 2026 at 3:35 AM Petr Matyas <[email protected]> wrote: > > The PCRE2 JIT compiler has a bug in its PCRE2_JIT_PARTIAL_SOFT code > path: patterns containing recursive subroutine calls (the (?1...) > syntax) cause the generated native code to dereference a null pointer > at match time. pcre2_jit_compile() returns 0 (success) and the > compiled JIT size is non-zero, so the bug is undetectable at compile > time and only manifests when pcre2_match() executes the JIT code. > > The root cause is an interaction between OP_RECURSE backtracking and > the hit_start tracking mechanism that the partial-soft JIT uses to > remember the earliest position at which a partial match was seen. > No fix is currently available in PCRE2. > > Commit 92af7f1b61db ("libselinux: prevent ReDoS in file context regex > matching") introduced pcre2_jit_compile() calls with both > PCRE2_JIT_COMPLETE and PCRE2_JIT_PARTIAL_SOFT. ClusterFuzz > subsequently reported a SEGV in selabel_file_compiled-fuzzer via a > crafted compiled file_contexts input that triggers the bug through > the lazy text-compilation fallback path (version/arch mismatch causes > the pre-compiled PCRE2 data to be skipped, so the embedded regex > string is compiled on demand via regex_prepare_data()). > > Fix by dropping PCRE2_JIT_PARTIAL_SOFT. Per the PCRE2 documentation, > when partial-soft JIT code has not been compiled, pcre2_match() > automatically falls back to the interpreter for partial matches. > Partial matches remain protected against ReDoS by the 10 000 000-step > backtrack limit that was introduced in the same commit. Full matches > continue to use PCRE2_JIT_COMPLETE and are unaffected. > > Reported-by: ClusterFuzz (testcase 5660382656790528) > Fixes: 92af7f1b61db ("libselinux: prevent ReDoS in file context regex matching") > Signed-off-by: Petr Matyas <[email protected]> Thank you! This also appears to fix another new oss-fuzz report (selinux:selabel_file_text-fuzzer: Heap-buffer-overflow in _pcre2_script_run_8) that just showed up (reproducer attached and output below). Acked-by: Stephen Smalley <[email protected]> ==250==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7bf4d87e0032 at pc 0x591ed94e7dd5 bp 0x7ffd3265e3f0 sp 0x7ffd3265e3e8 READ of size 1 at 0x7bf4d87e0032 thread T0 #0 0x591ed94e7dd4 in _pcre2_script_run_8 /tmp/pcre2-src/src/pcre2_script_run.c:332:3 #1 0x591ed9437b56 in do_script_run /tmp/pcre2-src/src/pcre2_jit_compile.c:9599:7 #2 0x7fd4d942cc0c (<unknown module>) 0x7bf4d87e0032 is located 0 bytes after 2-byte region [0x7bf4d87e0030,0x7bf4d87e0032) allocated by thread T0 here: #0 0x591ed90f70e4 in __interceptor_malloc /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:67:3 #1 0x591ed913b958 in LLVMFuzzerTestOneInput selinux/libselinux/fuzz/selabel_file_text-fuzzer.c:137:8 #2 0x591ed913b5a9 in ExecuteFilesOnyByOne /src/aflplusplus/utils/aflpp_driver/aflpp_driver.c:267:7 SUMMARY: AddressSanitizer: heap-buffer-overflow (/mnt/scratch0/clusterfuzz/bot/builds/clusterfuzz-builds-afl_selinux_32ddf78d9f476617df7a8f4cf22b4d9340aef874/revisions/selabel_file_text-fuzzer+0x57fdd4) Shadow bytes around the buggy address: 0x7bf4d87dfd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7bf4d87dfe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7bf4d87dfe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7bf4d87dff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7bf4d87dff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x7bf4d87e0000: fa fa 00 05 fa fa[02]fa fa fa 00 04 fa fa 02 fa 0x7bf4d87e0080: fa fa fd fd fa fa 00 fa fa fa fa fa fa fa fa fa 0x7bf4d87e0100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7bf4d87e0180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7bf4d87e0200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7bf4d87e0280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==250==ABORTING > --- > libselinux/src/regex.c | 15 +++++++-------- > 1 file changed, 7 insertions(+), 8 deletions(-) > > diff --git a/libselinux/src/regex.c b/libselinux/src/regex.c > index 3f7df4c7..017647b4 100644 > --- a/libselinux/src/regex.c > +++ b/libselinux/src/regex.c > @@ -103,11 +103,12 @@ int regex_prepare_data(struct regex_data **regex, char const *pattern_string, > goto err; > } > > - /* JIT-compile for complete and partial matching. pcre2_match() uses the > - * JIT automatically when available, avoiding the interpreter's > - * susceptibility to catastrophic backtracking. Failures are non-fatal. */ > - (void)pcre2_jit_compile((*regex)->regex, > - PCRE2_JIT_COMPLETE | PCRE2_JIT_PARTIAL_SOFT); > + /* JIT-compile for complete matching only. pcre2_match() uses the JIT > + * automatically when available, avoiding the interpreter's > + * susceptibility to catastrophic backtracking. Partial matches fall > + * back to the interpreter, protected by the match limit. Failures > + * are non-fatal. */ > + (void)pcre2_jit_compile((*regex)->regex, PCRE2_JIT_COMPLETE); > > return 0; > > @@ -163,9 +164,7 @@ int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, > if (rc != 1) > goto err; > > - (void)pcre2_jit_compile((*regex)->regex, > - PCRE2_JIT_COMPLETE | > - PCRE2_JIT_PARTIAL_SOFT); > + (void)pcre2_jit_compile((*regex)->regex, PCRE2_JIT_COMPLETE); > > *regex_compiled = true; > } > -- > 2.55.0 >
clusterfuzz-testcase-minimized-selabel_file_text-fuzzer-4919041035403264
(application/octet-stream, 19 B) - not displayed