Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
930ad26
Fix failed seek position on php://memory
marc-mabe Sep 25, 2026
faf7da4
Fix failed seek position on SQLite3 blob streams
marc-mabe Sep 25, 2026
dfaaf25
Fix failed seek position on PDO SQLite blob streams
marc-mabe Sep 25, 2026
aa7691b
Use a uintptr_t constant for ZEND_VM_ENTER_BIT
marc-mabe Sep 23, 2026
1a1592a
Fix ext/zip build on 32-bit targets
marc-mabe Sep 23, 2026
7051651
Added PHP_SYS_SIZE from SIZEOF_SIZE_T
marc-mabe Aug 8, 2026
5562513
Added PHP_STRING_MAX_LENGTH from MIN(ZSTR_MAX_LEN, ZEND_LONG_MAX)
marc-mabe Sep 9, 2026
6f9ae1a
Check for allocation size overflow in the debug reallocators
marc-mabe Sep 9, 2026
02a938e
Fix alignment of Bucket
marc-mabe Aug 8, 2026
2a9779e
Use int64 for zend_long
marc-mabe Jul 7, 2025
23b9c2b
Use size_t rather than zend_long in the memory manager
marc-mabe Sep 11, 2026
a233471
Re-enable ZEND_USE_ASM_ARITHMETIC
marc-mabe Sep 9, 2026
d66921f
Added range checks helpers zend_long vs. size_t and long
marc-mabe Aug 14, 2026
529fe3e
Fixed Zend/tests/fibers/get-return-after-bailout.phpt
marc-mabe Aug 8, 2026
ba6d362
Fix ext/standard
marc-mabe Jul 7, 2025
09b6bfd
Fix ext/date
marc-mabe Jul 7, 2025
7e4020d
Fix ext/socket
marc-mabe Jul 7, 2025
bebd148
Fix ext/gmp
marc-mabe Jul 7, 2025
333a8d1
Fix ext/shmop
marc-mabe Jul 7, 2025
927da2b
Fix ext/opcache
marc-mabe Nov 16, 2025
4f962f4
Disable JIT when zend_long is wider than the platform word
marc-mabe Sep 11, 2026
6d11740
Fix sapi/phpdbg
marc-mabe Nov 16, 2025
d0810bb
Assert zend_string_*_alloc lengths stay within ZSTR_MAX_LEN
marc-mabe Sep 9, 2026
fa49cc2
Fix strncmp() and strncasecmp()
marc-mabe Sep 9, 2026
03dde4c
Add LINUX_X32_INT64 CI job
marc-mabe Sep 11, 2026
b4d9c74
Fix ext/dom
marc-mabe Sep 12, 2026
b203f6e
Fix ext/pcntl
marc-mabe Sep 12, 2026
f84ead2
Fix ext/mysqlnd
marc-mabe Sep 13, 2026
b699adf
Fix ext/posix
marc-mabe Sep 14, 2026
7d034a5
Fix ext/sysvmsg
marc-mabe Sep 14, 2026
740ea87
Reject a negative msg_qbytes in msg_set_queue()
marc-mabe Sep 14, 2026
029c92f
Fix zend_safe_address overflow word type
marc-mabe Sep 19, 2026
5397d27
Guard ZEND_SIGNED_MULTIPLY_LONG asm by zend_long width
marc-mabe Sep 19, 2026
a73e112
Reserve worst-case padding in ZSTR_MAX_OVERHEAD
marc-mabe Sep 19, 2026
e20e9a0
Move the Windows SSIZE_MAX fallback to zend_portability.h
marc-mabe Sep 21, 2026
fbe3d45
Add zend_long vs ssize_t range checks
marc-mabe Sep 21, 2026
8bb788e
Fix SplFixedArray size range checks
marc-mabe Sep 21, 2026
5632217
Fix SplFileObject::setMaxLineLen() range check
marc-mabe Sep 21, 2026
d99c0ea
Added ZEND_INT64 flag to config.w32
marc-mabe Sep 22, 2026
c711c1b
Fix ext/tidy
marc-mabe Sep 23, 2026
306f3e6
Fix ext/odbc and ext/pdo_odbc
marc-mabe Sep 23, 2026
09d226e
Fix ext/bz2
marc-mabe Sep 23, 2026
a5253a0
Fix memory_limit beyond size_t
marc-mabe Sep 24, 2026
76ca18c
Fix ext/gd
marc-mabe Sep 24, 2026
f2583f7
Fix ext/sysvshm
marc-mabe Sep 24, 2026
d26411a
Fix ext/iconv
marc-mabe Sep 24, 2026
f033f35
Fix ext/calendar
marc-mabe Sep 24, 2026
9687d14
Fix mb_substr() and mb_strcut() with huge lengths
marc-mabe Sep 25, 2026
d3b6d83
Fix ext/mbstring
marc-mabe Sep 25, 2026
a5037e9
Fix Bug-81481 XML_GetCurrentByteIndex() truncation on Windows x64
marc-mabe Sep 25, 2026
69ba074
Fix ext/sqlite3 and ext/pdo_sqlite
marc-mabe Sep 25, 2026
e60e1ce
Fix ext/hash
marc-mabe Sep 27, 2026
5b0ce29
Fix ext/dom
marc-mabe Sep 30, 2026
bf7c013
Fix ext/soap
marc-mabe Sep 30, 2026
27a698b
Fix main/streams
marc-mabe Sep 30, 2026
b73bf7d
Fix ext/ffi
marc-mabe Oct 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/actions/configure-x32/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ inputs:
configurationParameters:
default: ''
required: false
cflags:
default: '-m32 -msse2'
required: false
runs:
using: composite
steps:
Expand All @@ -12,8 +15,8 @@ runs:

export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/lib/i386-linux-gnu/pkgconfig"
./buildconf --force
export CFLAGS="-m32 -msse2"
export CXXFLAGS="-m32 -msse2"
export CFLAGS="${{ inputs.cflags }}"
export CXXFLAGS="${{ inputs.cflags }}"
export LDFLAGS=-L/usr/lib/i386-linux-gnu
./configure ${{ inputs.configurationParameters }} \
--enable-option-checking=fatal \
Expand Down
8 changes: 8 additions & 0 deletions .github/matrix.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function select_jobs($repository, $trigger, $nightly, $labels, $php_version, $re
$test_libmysqlclient = in_array('CI: libmysqlclient', $labels, true);
$test_linux_ppc64 = in_array('CI: Linux PPC64', $labels, true);
$test_linux_x32 = in_array('CI: Linux X32', $labels, true);
$test_linux_x32_int64 = in_array('CI: Linux X32 INT64', $labels, true);
$test_linux_x64 = in_array('CI: Linux X64', $labels, true);
$test_macos = in_array('CI: macOS', $labels, true);
$test_msan = in_array('CI: MSAN', $labels, true);
Expand Down Expand Up @@ -124,6 +125,13 @@ function select_jobs($repository, $trigger, $nightly, $labels, $php_version, $re
? ['debug' => [true, false], 'zts' => [true, false]]
: ['debug' => [true], 'zts' => [true]];
}
// 32bit userland with a 64bit zend_long. Version gated because
// --enable-zend-int64 does not exist on the older branches.
if (version_compare($php_version, '8.6', '>=') && ($all_jobs || !$no_jobs || $test_linux_x32_int64)) {
$jobs['LINUX_X32_INT64']['matrix'] = $all_variations
? ['debug' => [true, false], 'zts' => [true, false]]
: ['debug' => [true], 'zts' => [true]];
}
if ($all_jobs || !$no_jobs || $test_macos) {
$test_arm = version_compare($php_version, '8.4', '>=');
$jobs['MACOS']['matrix'] = $all_variations
Expand Down
87 changes: 87 additions & 0 deletions .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,93 @@ jobs:
jitType: function
- name: Extra tests
uses: ./.github/actions/extra-tests
LINUX_X32_INT64:
if: ${{ fromJson(inputs.branch).jobs.LINUX_X32_INT64 }}
strategy:
fail-fast: false
matrix: ${{ fromJson(inputs.branch).jobs.LINUX_X32_INT64.matrix }}
name: "LINUX_X32_INT64_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}"
runs-on: ubuntu-latest
timeout-minutes: 180
container:
image: ubuntu:${{ fromJson(inputs.branch).config.ubuntu_version }}
env:
MYSQL_TEST_HOST: mysql
PDO_MYSQL_TEST_DSN: mysql:host=mysql;dbname=test
PDO_MYSQL_TEST_HOST: mysql
PDO_FIREBIRD_TEST_DSN: firebird:dbname=firebird:test.fdb
services:
mysql:
image: mysql:8.4
ports:
- 3306:3306
env:
MYSQL_DATABASE: test
MYSQL_ROOT_PASSWORD: root
firebird:
image: jacobalberty/firebird
ports:
- 3050:3050
env:
ISC_PASSWORD: test
FIREBIRD_DATABASE: test.fdb
FIREBIRD_USER: test
FIREBIRD_PASSWORD: test
steps:
- name: git checkout
uses: actions/checkout@v6
with:
ref: ${{ fromJson(inputs.branch).ref }}
- name: apt
uses: ./.github/actions/apt-x32
- name: ccache
uses: ./.github/actions/ccache
with:
name: "LINUX_X32_INT64_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}"
- name: System info
run: |
echo "::group::Show host CPU info"
lscpu
echo "::endgroup::"
echo "::group::Show installed package versions"
dpkg -l
echo "::endgroup::"
- name: ./configure
uses: ./.github/actions/configure-x32
with:
# x87 computes at 80bit and feeds unrounded operands into double
# arithmetic, which diverges from every 64bit platform. SSE math
# keeps the results identical.
cflags: '-m32 -msse2 -mfpmath=sse'
configurationParameters: >-
--${{ matrix.debug && 'enable' || 'disable' }}-debug
--${{ matrix.zts && 'enable' || 'disable' }}-zts
--enable-zend-int64
- name: make
run: make -j$(/usr/bin/nproc) >/dev/null
- name: make install
uses: ./.github/actions/install-linux-x32
- name: Test
if: ${{ inputs.all_variations }}
uses: ./.github/actions/test-linux
- name: Test Tracing JIT
uses: ./.github/actions/test-linux
with:
enableOpcache: true
jitType: tracing
- name: Test OpCache
if: ${{ inputs.all_variations }}
uses: ./.github/actions/test-linux
with:
enableOpcache: true
- name: Test Function JIT
if: ${{ inputs.all_variations }}
uses: ./.github/actions/test-linux
with:
enableOpcache: true
jitType: function
- name: Extra tests
uses: ./.github/actions/extra-tests
MACOS:
if: ${{ fromJson(inputs.branch).jobs.MACOS }}
strategy:
Expand Down
38 changes: 38 additions & 0 deletions Zend/Zend.m4
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ AX_CHECK_COMPILE_FLAG([-fno-common],
[CFLAGS="-fno-common $CFLAGS"])

ZEND_CHECK_ALIGNMENT
ZEND_CHECK_INT64
ZEND_CHECK_SIGNALS
ZEND_CHECK_MAX_EXECUTION_TIMERS
])
Expand Down Expand Up @@ -434,6 +435,43 @@ AS_VAR_IF([php_cv_align_mm], [failed],
])
])

dnl
dnl ZEND_CHECK_INT64
dnl
dnl Check whether to enable 64 bit integer if supported by the system.
dnl
AC_DEFUN([ZEND_CHECK_INT64], [dnl
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[[]],
[[
#if !(defined(__x86_64__) || defined(__LP64__) || defined(_LP64) || defined(_WIN64))
#error "Not a 64-bit platform"
#endif
]]
)],
[ZEND_INT64=yes],
[ZEND_INT64=no])

AC_ARG_ENABLE([zend-int64],
[AS_HELP_STRING([--enable-zend-int64], [Enable 64bit integer support (enabled by default on 64bit arch)])],
[ZEND_INT64=$enableval],
[ZEND_INT64=$ZEND_INT64])

AS_VAR_IF([ZEND_INT64], [yes],
AC_CHECK_TYPE([int64_t],,
[AC_MSG_ERROR([int64_t not found])],
[#include <stdint.h>]))

AS_VAR_IF([ZEND_INT64], [yes],
[AC_DEFINE([ZEND_INT64], [1],
[Define to 1 if zend_long as int64 is supported and enabled.])
AS_VAR_APPEND([CFLAGS], [" -DZEND_INT64"])])

AC_MSG_CHECKING([whether to enable 64 bit integer support])
AC_MSG_RESULT([$ZEND_INT64])
])

dnl
dnl ZEND_CHECK_SIGNALS
dnl
Expand Down
3 changes: 2 additions & 1 deletion Zend/tests/fibers/get-return-after-bailout.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ register_shutdown_function(static function (): void {
});

$fiber = new Fiber(static function (): void {
str_repeat('X', PHP_INT_MAX);
$allocSize = PHP_INT_SIZE <= PHP_SYS_SIZE ? PHP_INT_MAX : 2 ** (PHP_SYS_SIZE * 8 - 1) - 1;
str_repeat('X', $allocSize);
});
$fiber->start();

Expand Down
17 changes: 17 additions & 0 deletions Zend/tests/int_overflow_64bit.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ foreach ($doubles as $d) {
var_dump($l);
}

/* ++ and -- overflow to float at the zend_long boundary, and must not do so
* anywhere below it. The 32-bit boundaries matter on builds where zend_long is
* wider than the platform word: they are the values a 32-bit increment would
* either wrap or wrongly report as overflowing. */
$i = PHP_INT_MAX; $i++; var_dump($i);
$i = PHP_INT_MIN; $i--; var_dump($i);
$i = 2147483647; $i++; var_dump($i);
$i = 4294967295; $i++; var_dump($i);
$i = -2147483648; $i--; var_dump($i);
$i = -4294967296; $i--; var_dump($i);

echo "Done\n";
?>
--EXPECTF--
Expand All @@ -36,4 +47,10 @@ int(0)
int(-9223372036854775808)
int(-9223372036854775808)
int(-9223372036854775808)
float(9.223372036854776E+18)
float(-9.223372036854776E+18)
int(2147483648)
int(4294967296)
int(-2147483649)
int(-4294967297)
Done
69 changes: 69 additions & 0 deletions Zend/tests/str_offset_assign_sizet.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
--TEST--
Assigning to a string offset that would exceed the maximum string length
--INI--
memory_limit=128M
--SKIPIF--
<?php
if (PHP_SYS_SIZE >= PHP_INT_SIZE) {
die("skip size_t is not narrower than zend_long on this platform");
}
?>
--FILE--
<?php
/* Assigning to offset N extends the string to N + 1 bytes, so the largest
* usable offset is PHP_STRING_MAX_LENGTH - 1. Above that the length is
* narrowed to size_t on its way into zend_string_extend(): offsets beyond
* SIZE_MAX alias down to a small index and the assignment silently succeeds
* at the wrong place, while offsets just below it wrap the allocation to a
* tiny buffer carrying a huge ZSTR_LEN which the gap fill then memset()s past
* the end of. Every one of them has to be refused with the string left
* untouched. */
$sizeMax = 2 ** (PHP_SYS_SIZE * 8) - 1;

$offsets = [
'SIZE_MAX+11' => $sizeMax + 11,
'STR_MAX' => PHP_STRING_MAX_LENGTH,
'STR_MAX+1' => PHP_STRING_MAX_LENGTH + 1,
'STR_MAX+2' => PHP_STRING_MAX_LENGTH + 2,
'SIZE_MAX-2' => $sizeMax - 2,
'SIZE_MAX-1' => $sizeMax - 1,
'SIZE_MAX' => $sizeMax,
'INT_MAX' => PHP_INT_MAX,
];

foreach ($offsets as $label => $offset) {
echo "$label: ";
$s = "abc";
try {
$s[$offset] = 'x';
$result = 'no error';
} catch (Error $e) {
$result = $e::class . ': ' . $e->getMessage();
}
echo $result, '; $s = ', var_export($s, true), "\n";
}

/* The largest usable offset must not be refused: it reaches the allocator and
* fails there, as it would on a build where zend_long is no wider than size_t.
*
* Which diagnostic comes back depends on the build, so only the fact that it
* got that far is asserted. ZSTR_MAX_LEN does not account for the per
* allocation zend_mm_debug_info that a debug build adds, so at the very top of
* the range a debug build reports the overflow of that addition ("Possible
* integer overflow in memory allocation") while a release build has no such
* overhead, attempts the allocation for real and reports the memory limit
* ("Allowed memory size exhausted"). */
$s = "abc";
$s[PHP_STRING_MAX_LENGTH - 1] = 'x';
?>
--EXPECTF--
SIZE_MAX+11: Error: String size overflow; $s = 'abc'
STR_MAX: Error: String size overflow; $s = 'abc'
STR_MAX+1: Error: String size overflow; $s = 'abc'
STR_MAX+2: Error: String size overflow; $s = 'abc'
SIZE_MAX-2: Error: String size overflow; $s = 'abc'
SIZE_MAX-1: Error: String size overflow; $s = 'abc'
SIZE_MAX: Error: String size overflow; $s = 'abc'
INT_MAX: Error: String size overflow; $s = 'abc'

Fatal error: %s in %s on line %d
41 changes: 41 additions & 0 deletions Zend/tests/strncmp_length_sizet.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
strncmp()/strncasecmp() $length must not be narrowed to size_t
--SKIPIF--
<?php
if (PHP_SYS_SIZE >= PHP_INT_SIZE) {
die("skip size_t is not narrower than zend_long on this platform");
}
?>
--FILE--
<?php
/* Both functions hand $length to zend_binary_strncmp()/zend_binary_strncasecmp()
* as a size_t. Where zend_long is wider, a length above SIZE_MAX is narrowed:
* SIZE_MAX + 1 becomes 0 and SIZE_MAX + 3 becomes 2, so the comparison stops
* short and reports equality for operands that differ further along. Any length
* at or beyond the operand lengths has to behave like SIZE_MAX, which is what
* the comparators already do internally with MIN(length, MIN(len1, len2)). */
$sizeMax = 2 ** (PHP_SYS_SIZE * 8) - 1;

$lengths = [
'2' => 2,
'3' => 3,
'SIZE_MAX' => $sizeMax,
'SIZE_MAX+1' => $sizeMax + 1,
'SIZE_MAX+3' => $sizeMax + 3,
'INT_MAX' => PHP_INT_MAX,
];

foreach ($lengths as $label => $length) {
echo "$label: ";
printf("strncmp=%d strncasecmp=%d\n",
strncmp('abc', 'abd', $length),
strncasecmp('ABC', 'abd', $length));
}
?>
--EXPECT--
2: strncmp=0 strncasecmp=0
3: strncmp=-1 strncasecmp=-1
SIZE_MAX: strncmp=-1 strncasecmp=-1
SIZE_MAX+1: strncmp=-1 strncasecmp=-1
SIZE_MAX+3: strncmp=-1 strncasecmp=-1
INT_MAX: strncmp=-1 strncasecmp=-1
Loading
Loading