Description:
* SECURITY UPDATE: various out-of-bounds reads in the mysqlnd wire-protocol
parser
- debian/patches/php-5.6-CVE-2025-1218.patch: backport upstream commit
114dbb74368e (GHSA-r6x9-5r99-36j7) across ext/mysqlnd/
mysqlnd_wireprotocol.c, mysqlnd_wireprotocol.h, mysqlnd_ps_codec.c and
mysqlnd_ps.c. The parser read fields out of a server packet before
checking that the packet still held enough bytes, so a malicious or
compromised MySQL server could make the client read past the end of the
packet buffer. BAIL_IF_NO_MORE_DATA, which noticed an over-read only
after it had happened, is replaced by the BAIL_PREMATURE_END /
BAIL_IF_NOT_ENOUGH_DATA_EX family, which refuses the read up front;
php_mysqlnd_net_field_length() and its _ll() variant take the remaining
packet size and report MYSQLND_INVALID_NET_FIELD_LENGTH instead of
blindly consuming 2, 3 or 8 bytes; the server version, the auth-switch
plugin name in the auth-response, change-user and cached-sha2-result
packets are located with memchr() over the remaining bytes instead of
strdup()/strlen(); the greeting's fixed 31-byte block, its extended
scramble, the result-set field metadata lengths, the in-row EOF marker
and the text-protocol field lengths all gain real bounds checks; and
ps_fetch_time/date/datetime reject a declared length too short for the
fixed offsets they read.
- This branch needs the CVE-2024-8929 backport as a prerequisite: it is
what supplies the three mysqlnd_ps_codec.c over-read helpers and the
packet_end guard that the upstream change builds on, none of which are
in the 5.6.40 tarball.
- php-5.6.40-caching-sha2-password.patch introduces
php_mysqlnd_cached_sha2_result_read() on this branch, so both of its
over-reads are live here and are fixed. Its auth-switch branch did
mnd_pestrdup() plus strlen() straight off a 2048-byte stack buffer that
nothing NUL-terminates, and handed the result to the caller; and the
second response-code byte was read before its bounds check, one byte
past a one-byte packet. For that reason this patch is applied after
php-5.6.40-caching-sha2-password.patch rather than with the other CVE
patches.
- The greeting's authentication plugin name is still read with an
unbounded estrdup() on 5.6.40, so the bounded form php-8.1.34 carries
is included as a prerequisite before upstream's removal of the
"buf[header.size] = 0" hack, which would otherwise have converted a
terminated read into an unbounded one.
- Not every hunk is a memory-safety fix, and the ones that are not are
not presented as such: php_mysqlnd_prepare_read() and
php_mysqlnd_sha256_pk_request_response_read() are hardening - behind
the existing PREPARE_RESPONSE_SIZE gate the new prepare_read checks
cannot fire - and the mysqlnd_ps.c hunk is a leak fix on the newly
reachable failure path, releasing the connection reference and the
execute command buffer that the following memset() would otherwise
drop.
- Upstream's guard around MARIADB_RPL_VERSION_HACK has no target here;
that macro does not exist on this branch. Upstream's rejection of any
"def" value in the result-set field packet is also not taken, because
COM_FIELD_LIST is still supported on 5.6 and that change would break
it; the existing def bounds check is kept and only given the remaining
size.
- Upstream's 38 regression tests are NOT carried. They are written
against the dynamic-port fake server upstream added in the same commit:
run_fake_server_in_background() takes no port, the test reads
getPort(), and the expectation asserts the address the server printed.
The harness this branch has, ext/mysqli/tests/fake_server.inc from the
CVE-2024-8929 backport, predates that API and binds a fixed port, so
carrying the tests means rewriting all 38 to hardcode a port and to
drop the address assertion. Nothing would run them either way:
php56.spec gates its suite behind the runselftest toggle, which
defaults to 0, and debian/rules has no test target. They were run
locally instead, on a tree built from this repository's own patch
series, first patched and then with only the C hunks of this patch
reversed out and rebuilt: 32 of 38 pass patched, 0 fail; 8 of 38 pass
unpatched, 24 fail. The 6 skips in both arms are the cached-sha2 and
sha256-pk tests, which need ext/openssl - PHP 5.6's openssl extension
does not compile against OpenSSL 3, so that build had no ext/openssl.
- The same hunk also corrects that path's result free from mnd_efree() to
upstream's php-7.0 form, mnd_pefree(stmt->result,
stmt->result->persistent), since mysqlnd_result_init() allocates the
result with mnd_pecalloc(..., persistent) and a persistent connection
therefore gave the Zend allocator a malloc()ed block; before the change
a hostile prepare over p:127.0.0.1 aborts with "zend_mm_heap corrupted"
on a build with the Zend MM heap checks compiled in, after it the same
run completes
- Also fixes a pre-existing use-after-free on the row-reader failure path.
The CVE-2024-8929 backport added error loops in the binary- and
text-protocol row readers that zval_ptr_dtor() every field decoded so far
and return FAIL without clearing the zvals, so the result set frees the
same zvals again when it is torn down. The loops were already reachable
before this change; the new too-short TIME/DATE/DATETIME check adds a
second way in. All 2 sites now clear the zval after freeing it.
Confirmed under AddressSanitizer with the Zend allocator disabled:
heap-use-after-free in mysqlnd_result_buffered_zval free_result before,
clean after, on both the three new short-length tests and the nine
CVE-2024-8929 stmt-row tests. Upstream still carries the same loops
unguarded in php-8.2.34 through php-8.5.11; php-8.1 is not affected.
- The mysqlnd_ps.c hunk additionally releases the result's own connection
reference on the prepare-EOF path. Before PHP 7.4 (upstream
f365d0e00ed93b1c33e984ff3b4cc8677cbca193) free_result_contents() did not
drop it, so that path leaked one reference. The sibling site in
mysqlnd_stmt store_result() leaks the same reference but is not made
reachable by this change and is left for a separate one.
- CVE-2025-1218
* mysqlnd: initialise every pre-allocated row field before decoding
- debian/patches/php-5.6.40-mysqlnd-rowp-init-fields.patch: the binary- and
text-protocol row readers pre-allocate a zval per column with
MAKE_STD_ZVAL(), which sets no type, and only type it when the decode
loop reaches that column. The CVE-2024-8929 error paths abandon a row
part-way, so every column past the failure point stays allocated but
never typed, and the result set still frees the whole row at teardown -
_zval_ptr_dtor() then branches on an uninitialised Z_TYPE. Each column is
now ZVAL_NULL()ed at allocation. Confirmed with valgrind under
USE_ZEND_ALLOC=0: the CVE-2024-8929 and CVE-2025-1218 stmt-row tests go
from 1 uninitialised-value error to 0. PHP 5 only - from PHP 7 the row is
a caller-owned array that mysqlnd_store_result() memset()s to zero. Kept
separate from the CVE patch because the defect predates it and is
reachable without it