SIGILL (Illegal instruction) in `mpn_mul_1` ADX/BMI2 path on CPU without ADX (Intel Haswell / i7-4790)

Mohammad Dahamshi mohammad.dahamshi at gmail.com
Thu Sep 3 03:31:23 CEST 2026


## 1. GMP version and provenance

GMP **6.3.0** (reported by the `gmp` PHP extension via the `GMP_VERSION` 
macro).

**Prepackaged / patched: YES — this is a build-time-embedded GMP, 
statically linked into a third-party prebuilt PHP 8.5.0 binary 
(laravel).** I did NOT build GMP myself; it was compiled and statically 
linked by the software distributor (Beyond Code / php.new, the 
"herd-lite" static PHP builds). I cannot verify whether the distributor 
applied local patches to the GMP source; I have only the shipped static 
object. This is important: the GMP code appears to have been compiled 
with **ADX/BMI2 enabled at build time** in a manner that produces the 
ADX instruction stream, and the distributor's shipping configuration 
evidently does not gate this path on a runtime CPUID check (or the build 
enables it unconditionally for the target build machine's CPU).

## 2. Test program to reproduce the bug

Self-contained PHP script (uses only the bundled PHP `gmp` extension; no 
phpseclib or other library required):

```php
<?php
$a = gmp_init(str_repeat('12345678901234567890', 40), 10); // ~800 
decimal digits
$b = gmp_init(str_repeat('98765432109876543210', 40), 10);
$r = gmp_mul($a, $b);
echo gmp_strval($r, 10), "\n";
```

**How to run:** with the affected static PHP binary (GMP 6.3.0 
statically linked),

```
/path/to/php repro_gmp.php
```

and observe the result. On the affected machine this exits with:

```
Illegal instruction
(exit code 132 / killed by SIGILL)
```

Note: this tests the public `mpz_mul` path which reaches the low-level 
`mpn_mul` → `mpn_mul_basecase` → `mpn_mul_1` (vector×single-word) code 
that faults. GMP developers can reproduce the same fault with an 
equivalent C program using the public API:

```c
#include <gmp.h>
int main(void) {
     mpz_t a, r;
     mpz_inits(a, r, 0);
     mpz_set_str(a, "12345678901234567890...", 10); /* ~800+ digits to 
force the large path */
     mpz_mul(r, a, a);
     mpz_out_str(stdout, 10, r);
     mpz_clears(a, r, 0);
     return 0;
}
```
Compile with `gcc -O3 -o t t.c -lgmp` and run on the affected CPU.

*(I confirmed the exact fault with the shipped binary: `Illegal 
instruction`, exit 132.)*

## 3. What is wrong

This is a **crash**, not incorrect results. The process is killed by 
**SIGILL** while executing an **ADCX (ADX ISA) instruction in GMP's 
big-number multiply**, on a CPU that does not support the ADX extension.

The faulting instruction, seen at two addresses of the same GMP 
vector×single-word multiply (`mpn_mul_1`) routine family, is:

```
19dd420:  66 4d 0f 38 f6 c8    adcx   %r8,%r9        ; SIGILL here
19dff29:  66 4d 0f 38 f6 c3    adcx   %r11,%r8       ; (same routine family)
```

Surrounding code uses the BMI2 `mulx` instruction (which this CPU 
supports) and the ADX `adcx`/`adox` instructions (which this CPU does 
**not** support).

**Root cause:** The ADX code path was selected/compiled into the shipped 
binary even though the target CPU lacks the `adx` CPUID flag. GMP must 
select the ADX variants only after a positive runtime CPUID check for 
ADX, or the distributor's static build must be configured without ADX. 
On this CPU the mix (BMI2 present, ADX absent) is exactly what makes the 
fault reachable — plain `mulq` fallback would have been correct.

## 4. Backtrace

 From GDB (the binary is stripped, so frames show raw addresses, but the 
unwind chain is informative — it goes GMP low-level multiply → PHP `gmp` 
extension → PHP Zend engine):

```
Program received signal SIGILL, Illegal instruction.
0x00000000019dd420 in ?? ()
#0  0x00000000019dd420 in ?? ()      ; mpn_mul_1 ADX loop (adcx %r8,%r9)
#1  0x00000000019e1fb7 in ?? ()      ; GMP multiply code region
#2  0x00000000019dbc02 in ?? ()      ; GMP multiply code region
#3  0x0000000000584402 in ?? ()      ; PHP gmp extension (gmp_mul)
#4  0x0000000000588786 in ?? ()
#5  0x0000000000ab41a1 in ?? ()
#6  0x0000000000ab2c50 in ?? ()
...
#10 0x00000000004445ac in ?? ()      ; PHP Zend engine
```

*(Per GMP's request I am not attaching the core dump, the executable, or 
a strace.)*

## 5. Configure options used to build GMP

**Not applicable / unavailable for my report.** I did not build GMP. It 
is statically embedded in a third-party PHP build, so I do not have the 
distributor's GMP `configure` invocation, and there is no `config.log`, 
`config.m4`, or `mpn/tmp-*.s` accessible to me. What I can state from 
inspection of the shipped binary:

- It was built for **x86_64** against a **musl** libc static toolchain 
(the PHP binary is `x86_64-linux-musl-gcc`, static, stripped).
- The GMP arithmetic object code **contains ADX (`adcx`/`adox`) and BMI2 
(`mulx`) instruction streams**, i.e. an ADX-enabled code path was 
generated and shipped.
- The enabling must have assumed an ADX-capable CPU or failed to gate on 
runtime CPUID; the observed behavior indicates ADX instructions are 
reachable on this non-ADX CPU.

## 6. Output from `configure` (stdout)

Not available — GMP was not configured by me; it is embedded in a 
third-party binary. No configure output exists in my environment.

## 7. Compiler and version

The shipped GMP/PHP was compiled with the distributor's toolchain 
**`x86_64-linux-musl-gcc`** (version not disclosed by the binary). For 
reference, the local machine's compiler (not used for this GMP) is:

```
gcc version 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04)
```

## 8. `uname -a`

```
Linux mmd 6.14.8-2-pve #1 SMP PREEMPT_DYNAMIC PMX 6.14.8-2 
(2025-07-22T10:04Z) x86_64 x86_64 x86_64 GNU/Linux
```

## 9. `config.guess` / `configfsf.guess`

Not run — GMP was not built locally. For reference the machine 
self-identifies as `x86_64-pc-linux-gnu`-class.

## 10. Verify the CPU (the crux of the bug)

```
grep -m1 "model name" /proc/cpuinfo
   Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz   (Haswell)

grep -m1 flags /proc/cpuinfo
   ... bmi1 bmi2 avx2 sse4_2 ...   (BMI2/MULX present)
   ... adx ...                     (ADX is ABSENT)
```

**Intel i7-4790 (Haswell) has BMI2 (so `mulx` is legal) but does NOT 
have ADX (so `adcx`/`adox` are illegal) — exactly the combination that 
trips this code path.**

---

## Summary for the GMP maintainers

GMP's ADX-based multiply is being executed on hardware that lacks ADX. 
Even though this surfaced through a third-party static PHP build, the 
underlying correctness concern is GMP's: **the ADX code path must only 
be selected/used when a runtime CPUID check confirms the `ADX` bit.** If 
the shipped configuration was supposed to enforce this, then the correct 
fix is to ensure ADX gating (or build without `-madx`/ADX) so that 
non-ADX CPUs like Haswell fall back to the plain `mulq` path rather than 
faulting.

---

*Reproducer script referenced in section 2 saved separately as 
`repro_gmp.php`.*

here is the code in the file:

<?php
// Minimal GMP reproducer for the SIGILL (ADX/MULX path) on non-ADX 
(Haswell) CPUs.
// Uses only the gmp extension - no phpseclib needed.
$a = gmp_init(str_repeat('12345678901234567890', 40), 10); // ~800 
decimal digits (~44 limbs)
$b = gmp_init(str_repeat('98765432109876543210', 40), 10);
$r = gmp_mul($a, $b);
echo gmp_strval($r, 10), "\n";



-- 
Mohammad Dahamshi
Senior Embedded & DevOps Software Engineer
📞 	+972-51-507-1989
✉ 	mohammad.dahamshi at gmail.com
📍 	Kafr Kanna, Israel
-------------- next part --------------
A non-text attachment was scrubbed...
Name: repro_gmp.php
Type: application/x-php
Size: 351 bytes
Desc: not available
URL: <https://gmplib.org/list-archives/gmp-bugs/attachments/20260903/d5b83c78/attachment.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: gmp_bug_report.md
Type: text/markdown
Size: 6733 bytes
Desc: not available
URL: <https://gmplib.org/list-archives/gmp-bugs/attachments/20260903/d5b83c78/attachment-0001.bin>


More information about the gmp-bugs mailing list