bryancall opened a new pull request, #13671:
URL: https://github.com/apache/trafficserver/pull/13671

   Four defects in the `tsutil` regex wrapper, all present in shipped releases. 
Every commit
   is a behaviour fix with no change to the public API surface, so the series 
backports as a
   unit.
   
   This supersedes #13661, whose two commits are the first of the four here. 
That PR bundled
   these fixes with a source-breaking change (deleting `RegexMatchContext`'s 
copy and move
   members), which would have kept the whole thing off the release branches. 
The member
   deletion is deferred to #13663, where the type is replaced outright.
   
   ### The fixes
   
   **1. A caller-supplied `RegexMatchContext` ran on a 32 KiB JIT stack** 
(#13660).
   `pcre2_match_context_create(nullptr)` builds a context that configures 
nothing, so a
   caller who wanted only a match limit silently gave up everything the shared 
context
   provides, including its 1 MiB JIT stack. PCRE2 fell back to its own 32 KiB 
machine-stack
   block, which resolves about 1,362 bytes of a subject that backtracks once 
per character; a
   production regex_remap rule hit that bound at 1,377 bytes of query string. 
Copy the shared
   context instead.
   
   A shared context needs a per thread JIT stack, and a `thread_local` holding 
one registers
   its destructor through `__cxa_thread_atexit`, which takes the dynamic loader 
lock. Doing
   that from a match inverts lock order against a `dlopen` caller running a 
plugin's static
   initialization; `Diags::tag_activated` documents that exact deadlock. The 
stack now comes
   from a callback backed by a pthread key, whose destructor is registered once 
at key
   creation. `pthread_key_create` failure is handled, because `jit_stack_key` 
is zero
   initialized and key 0 can belong to another subsystem.
   
   **2. The pcre2 contexts are now process-wide.** The general, compile and 
match contexts
   were themselves in a `thread_local` with a destructor, so the first 
`compile()` or `exec()`
   on a thread still took the loader lock. Nothing in them is per thread: they 
are built once
   and never modified, which is `pcre2api`'s stated condition for sharing a 
context across
   threads. One instance, never destroyed, against a destructor that can run 
while another
   thread is still matching.
   
   **3. A failed recompile left freed memory in the object.** `compile()` freed 
the pattern it
   already held *before* calling `pcre2_compile()`. Every failure path after 
that returned
   with the freed pointer still stored, so `empty()` reported the object as 
compiled, `exec()`
   passed the freed block to `pcre2_match()`, and the destructor freed it 
again. It now
   compiles into a local and replaces the member only on success, so a failed 
compile leaves
   the previous pattern usable.
   
   **4. A copied `Regex` silently changed engine.** `pcre2_code_copy()` 
duplicates a compiled
   pattern but not the machine code the JIT produced for it. The copy 
constructor called
   nothing else, so every copied `Regex` matched on the interpreter: same 
answers, far slower,
   and a different set of resource limits, so a pattern that reports a JIT 
stack limit through
   the original quietly matched through a copy. 
`plugins/experimental/maxmind_acl` copies
   every rule. The copy is now compiled for the JIT, which costs what a compile 
costs
   (measured 7.5 ns to 2549 ns, 1 allocation to 6); nothing copies a `Regex` on 
a request path.
   
   ### Tests
   
   Each test fails against the code it fixes. Built against the unfixed 
implementation, the
   recompile test segmentation faults and the copy test returns a match where 
the original
   returns the stack-limit error.
   
   New: two recompile sections, four copy sections, and an eight-thread 
concurrency test on a
   single instance, half the threads through their own match context. The 
header has always
   promised `exec()` is safe to call concurrently and nothing tested it.
   
   ### Verification
   
   Fedora 44, gcc 16.2.1, PCRE2 10.47:
   
   - Full build with experimental plugins, `ctest` **1058/1058**.
   - `[Regex]` clean under **ThreadSanitizer** and under **AddressSanitizer + 
UBSan** (411 assertions, 22 cases).
   - `regex_remap` autest passes.
   - `RegexContext::~RegexContext()` and the thread-local `ctx` are both gone 
from the object
     file; what remains is a plain static behind a guard variable. The object 
still references
     `__cxa_thread_atexit`, but that belongs to the `inline thread_local` in
     `tsutil/ts_bw_format.h` and is present before and after.
   - No measurable change on any match or compile path (medians of three 
interleaved rounds,
     every delta inside a 5% run-to-run band). The harness is #13670.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to