Skip to content

FFI Memory Corruption #20672

@yyny

Description

@yyny

Description

After upgrading to PHP 8.5, we are getting memory corruption in our PHPUnit test suite, eventually resulting in a SIGSEGV or SIGBUS.
We initially suspected a problem in the foreign code or an ABI/calling-convention/libffi issue, as we recently changed several functions to use structs instead of C strings (Additionally, the issue only seems to occur on ARM chips?).
However, we were able to remove ALL foreign code, and narrow the issue down to an problem in PHP.

The culprit is a function that looks like this:

<?php

$s = json_encode($array);

$len = strlen($s);
$cap = max($len, 1);

$buf = $this->ffi->new('unsigned char['.$cap.']'); // `unsigned char(*) buf[cap] = malloc(cap);`
FFI::memcpy($buf, $s, $len); // `memcpy(buf, s, len);`

$ptr = FFI::addr($buf[0]); // `unsigned char *ptr = &buf[0]`

$slice = $this->ffi->new('Slice'); // `Slice slice;`

$slice->ptr = $ptr; // `slice.ptr = ptr;`
// $slice->len = $len; // `slice.len = len;`

// ...rest of the code not necessary to trigger the crash.
// we don't even need to make a FFI call, just constructing a `$slice` and writing to `$slice->ptr` is enough.

(with ffi constructed as):

$this->ffi = FFI::cdef(<<<EOF
typedef struct slice {
    unsigned char *ptr;
    unsigned long len;
} Slice;
EOF
);

When we run this function in a PHPUnit test in lldb, we get:

Process 34987 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x600000004)
    frame #0: 0x00000001000b1578 php`zend_ffi_cdata_write_field + 244
php`zend_ffi_cdata_write_field:
->  0x1000b1578 <+244>: ldr    w22, [x21]
    0x1000b157c <+248>: cmp    w22, #0xf
    0x1000b1580 <+252>: b.eq   0x1000b15e8    ; <+356>
    0x1000b1584 <+256>: ldrb   w8, [x19, #0x8]
Target 0: (php) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x600000004)
  * frame #0: 0x00000001000b1578 php`zend_ffi_cdata_write_field + 244
    frame #1: 0x00000001004ebb9c php`ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DATA_CV_TAILCALL_HANDLER + 476
    frame #2: 0x000000010059dba4 php`execute_ex + 168
    frame #3: 0x00000001005b009c php`zend_execute.cold.1 + 264
    frame #4: 0x000000010049c368 php`zend_execute + 32
    frame #5: 0x000000010056b8f8 php`zend_execute_script + 96
    frame #6: 0x00000001003ff3e4 php`php_execute_script_ex + 472
    frame #7: 0x000000010056e0d4 php`do_cli + 5404
    frame #8: 0x000000010056caac php`main + 720
    frame #9: 0x0000000184ceeb98 dyld`start + 6076

When compiling PHP with asan/ubsan, we also get:

/tmp/php-src/ext/ffi/ffi.c:1373:13: runtime error: load of value 160, which is not a valid value for type 'bool'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /tmp/php-src/ext/ffi/ffi.c:1373:13
zsh: abort      /tmp/php-src/sapi/cli/php  --configuration  --filter

The crash is a bit sensitive to surrounding code, unfortunately, so we can't share a full reproduction, but a couple things of note:

  • The crash happens AFTER the function doing the FFI has already returned (observable since code executing after the function still executes), and instead seems to happen when the class containing FFI instance gets destroyed.
  • To support this, making the FFI instance static no longer gives a crash.
  • The crash only happens when we enable another test to dispatch the class we are testing as a Laravel job (So basically, construct the job class, dispatch it in Laravel, construct the job class, call it normally). The first execution does not create a crash or trigger ubsan.

PHP Version

PHP 8.5.0 (cli) (built: Nov 18 2025 08:02:20) (NTS)
Copyright (c) The PHP Group
Built by Homebrew
Zend Engine v4.5.0, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.0, Copyright (c), by Zend Technologies

Operating System

Darwin macbookpro.home 24.6.0 Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:34 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T8103 arm64

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions