Skip to content

[mysqlnd] Fix result set field metadata length buffer over-read - #23496

Open
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/rset-field-len-overread-84
Open

[mysqlnd] Fix result set field metadata length buffer over-read#23496
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/rset-field-len-overread-84

Conversation

@iliaal

@iliaal iliaal commented Aug 29, 2026

Copy link
Copy Markdown
Member

In php_mysqlnd_rset_field_read(), a hostile server can place a length marker at the end of a metadata packet whose value exceeds the remaining payload, advancing p past header.size and the command buffer and attaching attacker-controlled lengths to pointers outside the packet that feed memcpy(). Metadata strings now bail out once p leaves the payload and reject lengths beyond the remaining bytes. No other READ_RSET_FIELD users exist and the trailing default-value check never dereferences its length. A hostile-server phpt fails unpatched with an extra protocol-length warning and passes patched.

Comment on lines +1179 to +1182
BAIL_IF_NO_MORE_DATA; \
if (UNEXPECTED(len > (zend_ulong)(packet->header.size - (size_t)(p - begin)))) { \
goto premature_end; \
} \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
BAIL_IF_NO_MORE_DATA; \
if (UNEXPECTED(len > (zend_ulong)(packet->header.size - (size_t)(p - begin)))) { \
goto premature_end; \
} \
BAIL_IF_NO_MORE_DATA; \
if (UNEXPECTED(len > (zend_ulong)(packet->header.size - (size_t)(p - begin)))) { \
php_error_docref(NULL, E_WARNING, "Result set field metadata string length is past the packet size"); \
DBG_RETURN(FAIL); \
} \

This part is untested, but if there was a test the value in the test would underflow, so it's probably better to do it how we do it on line 740. Also, on line 740 we don't do (zend_ulong) cast so I think that's redundant too, but I am not sure.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matched the line 740 check and warning. goto premature_end underflows the reported size when p is still inside the packet; added mysqlnd_rset_field_len_past_packet.phpt for that.

The rset_field metadata reader trusted each length-encoded string size:
a hostile server could send a length marker at the end of a field packet
whose value exceeds the remaining payload, advancing p past header.size
and past the command buffer before the next dereference, and recording
attacker-controlled lengths on pointers outside the packet that later
feed memcpy() into the field memory pool. Bound each metadata string by
bailing once p leaves the payload and rejecting lengths larger than the
remaining bytes, matching php_mysqlnd_auth_response_read() from
GHSA-h35g-vwh6-m678; an audit found no other users of the READ_RSET_FIELD
macro and the trailing default-value check never dereferences its length.
@iliaal
iliaal force-pushed the fix/rset-field-len-overread-84 branch from 907fde5 to ef091dc Compare August 29, 2026 14:31
@iliaal
iliaal requested a review from kamil-tekiela August 29, 2026 14:40

@kamil-tekiela kamil-tekiela left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants