Public bug reported:
On arm64 (aarch64), the PCRE2 JIT compiler shipped in noble can generate
a direct BL (branch-with-link) to a libpcre2 helper that is just outside
BL's +/-128 MB reach. The branch offset wraps in the 26-bit immediate
and the call lands roughly 256 MB away from the intended target, in
unmapped memory, so any process that JIT-compiles a regex can crash with
SIGSEGV. We hit this repeatedly on production PHP-FPM workers running
ordinary WordPress workloads.
Ubuntu release: 24.04 LTS (noble), aarch64.
Package version: libpcre2-8-0 10.42-4ubuntu2.1 (noble-updates).
Ubuntu 26.04 (pcre2 10.46) is not affected.
Impact: availability only -- random crashes of processes using the PCRE2
JIT. We do not believe it is exploitable beyond denial of service: the
wrong branch target is deterministic (libpcre2_base - ~256 MB), not
influenced by the regex or any input, and in every case we examined it
is unmapped or non-executable memory, so the CPU faults immediately. See
"Exploitability" below.
Root cause:
The bug is in the bundled sljit arm64 backend, src/sljit/sljitNativeARM_64.c,
in detect_jump_type(). The reachability of a jump is computed from the wrong
reference point -- 32 bytes past where the BL is actually written:
- diff = (sljit_sw)target_addr - (sljit_sw)(code_ptr + 4) -
executable_offset;
+ diff = (sljit_sw)target_addr - (sljit_sw)(code_ptr - 4) -
executable_offset;
As a result, a target 0-32 bytes beyond the +/-128 MB range still passes
the range check, so sljit picks the single-instruction BL/B form instead
of the 64-bit indirect (BLR) form. The 26-bit signed immediate then
overflows and the call is taken ~256 MB in the wrong direction.
Upstream fix:
sljit commit 38d96705b6cce2d01bca1cc7cab36747370954cf -- "Fix incorrect branch
offset calculation" (zherczeg/sljit PR #177), merged 2023-08-02. Included in
PCRE2 10.43 and later (confirmed: 10.43's src/sljit/sljitNativeARM_64.c has the
code_ptr - 4 form; 10.42 has code_ptr + 4). The change is a single,
self-contained line.
Requested action: cherry-pick the one-line sljit fix into noble's pcre2
as an SRU.
Evidence:
Three apport crash reports from one arm64 host (/usr/sbin/php-fpm8.4,
php8.4-fpm 8.4.26-1, Signal 11). In each core:
- The faulting PC is an address in unmapped memory; si_addr == pc.
- The instruction immediately before the saved LR is a BL (0x96xxxxxx).
- pc + 0x10000000 (pc + 2^28) lands inside libpcre2-8.so.0.11.2 -- the intended
helper -- confirming the call was meant to reach a libpcre2 routine but wrapped.
- Observed target-vs-BL displacements: 0x8000010, 0x8000014, 0x8000018 (just
past the 0x8000000 = 128 MB limit, by 16/20/24 bytes -- consistent with the
32-byte off-by error).
Abbreviated frame chain (one core):
pc <unmapped>
lr libpcre2-8.so.0.11.2+0x44114 (BL at lr-4)
php-fpm8.4 + 0x16620c
...
libc.so.6 + 0x284c4 (__libc_start_main)
Exploitability (why we assess DoS, not code execution):
- The wrap target is deterministic -- libpcre2_base - ~256 MB -- set by ASLR
placement of libpcre2, not by the regex or input. The pattern only selects
which fixed-offset helper is called.
- In all three cores the target was unmapped; on a live worker it falls inside
a PROT_NONE (---p) guard mapping. Neighbouring mappings are the PHP Zend heap
(rw-p, non-executable; arm64 enforces NX). So the branch hits unmapped or
non-executable memory and faults before executing anything.
- No trust boundary is crossed here: the crash occurs in the worker that
compiled the regex (the site's own PHP-FPM pool/user), which already runs that
user's code.
- Triggering is layout-dependent and unreliable; in practice it fired on
ordinary regex use, not crafted input.
Reproduction:
Hard to reproduce on demand -- it needs ASLR to place a JIT helper call 0-32
bytes past the BL range. In the wild it occurred on normal PHP/WordPress regex
workloads on arm64, intermittently (tens of crashes/day per busy pool). A fresh
forked worker that compiles the same pattern crashes consistently until the
parent's memory layout changes (e.g. on restart), because children inherit the
parent's layout.
Workaround:
Disable the PCRE2 JIT in the affected application. For PHP: pcre.jit = 0
(eliminated the crashes for us). Should be unnecessary once the sljit fix is
backported.
Disclosure:
The investigation (core-dump analysis, root cause, and this write-up) was done
with the help of Claude. I've reviewed it and am filing it because the analysis
looks correct to me and the fix is a known upstream one-liner -- I don't mean
to add noise to the tracker. I'm happy to run further diagnostics or test a
backport on affected arm64 hardware.
ProblemType: Bug
DistroRelease: Ubuntu 24.04
Package: libpcre2-8-0 10.42-4ubuntu2.1
ProcVersionSignature: Ubuntu 6.8.0-147.147-generic 6.8.12
Uname: Linux 6.8.0-147-generic aarch64
ApportVersion: 2.28.3-0ubuntu0.1
Architecture: arm64
CasperMD5CheckResult: unknown
CloudArchitecture: aarch64
CloudID: hetzner
CloudName: hetzner
CloudPlatform: hetzner
CloudSubPlatform: unknown
Date: Wed Oct 21 21:09:30 2026
Dependencies:
gcc-14-base 14.2.0-4ubuntu2~24.04.1
libc6 2.39-0ubuntu8.9
libgcc-s1 14.2.0-4ubuntu2~24.04.1
libidn2-0 2.3.7-2build1.1
libunistring5 1.1-2build1.1
ProcEnviron:
LANG=en_US.UTF-8
PATH=(custom, no user)
SHELL=/bin/bash
TERM=xterm-256color
XDG_RUNTIME_DIR=<set>
SourcePackage: pcre2
UpgradeStatus: No upgrade log present (probably fresh install)
** Affects: pcre2 (Ubuntu)
Importance: Undecided
Status: New
** Tags: apport-bug arm64 noble
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2171806
Title:
arm64: PCRE2 JIT emits an out-of-range BL (sljit branch-offset bug),
crashing JIT users such as PHP-FPM with SIGSEGV
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/pcre2/+bug/2171806/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs