MDEV-39689: Fix OOB reads in Table_map_log_event#5270
Conversation
|
|
There was a problem hiding this comment.
Code Review
This pull request addresses MDEV-39689 by adding robust bounds checks and validation of optional metadata lengths within Table_map_log_event and its associated parse_* functions, preventing slave overflows on malformed events. The review feedback highlights a critical ordering issue in parse_simple_pk where a bounds check is executed after the decoded index is already used to modify metadata, along with redundant nested braces. Additionally, the feedback points out that if bounds checks fail during Table_map_log_event construction, m_meta_memory is left allocated while m_memory is freed, and recommends explicitly freeing m_meta_memory to prevent a partially-freed state.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
cb10bed to
0562b5c
Compare
gkodinov
left a comment
There was a problem hiding this comment.
Thank you for your contribution! This is a preliminary review.
First of all: please rebase on the lowest affected version. This is hardening of the log parser(s), so a bug fix as such. I'd do 10.6, if affected.
Then there's a odd looking variable in your diff that probably needs removing.
Otherwise it's good to go IMHO.
| uchar const dbuf[]= { (uchar) m_dblen }; | ||
| uchar const tbuf[]= { (uchar) m_tbllen }; | ||
|
|
||
| ulong colcnt_to_write= (ulong) m_colcnt; |
There was a problem hiding this comment.
any specific reason why you need to convert a ulong value to ulong?
To tell you frankly I do not see a lot of point from colcnt_to_write to begin with.
There was a problem hiding this comment.
Sorry, this is indeed pointless code that should've been cleaned up earlier.
It used to be as follows when trying to debug using write-side injection:
ulong colcnt_to_write= DBUG_IF("corrupt_table_map_colcnt") ?
(1 << 20) : (ulong) m_colcnt;
I since swapped it to read-side injection for debug, but forgot that this variable becomes totally unnecessary after removing DBUG_IF.
Please disregard this line, we have no need for colcnt_to_write now, and I'll clean it up.
0562b5c to
bdc5681
Compare
Add bounds checks to prevent out-of-bounds memory reads when parsing Table_map_log_event optional metadata and constructor fields. parse_* functions: Add if (unlikely(p > end)) return after every net_field_length() call. Ensures decoded values are validated before being stored to struct fields, preventing information disclosure from OOB reads. On early return, the slave gracefully falls back to positional column mapping. Table_map_log_event constructor: Add bounds checks before memcpy calls for m_colcnt, m_field_metadata_size, and m_null_bits. On overflow, frees allocated memory and sets m_memory=NULL causing is_valid() to return false. Adds read-side DBUG_EXECUTE_IF injection points for testing. Functions fixed: - parse_default_charset - parse_column_charset - parse_column_name - parse_set_str_value - parse_geometry_type - parse_simple_pk - parse_pk_with_prefix - Table_map_log_event constructor (m_colcnt, m_field_metadata, m_null_bits) MTR test rpl_table_map_log_event_overflow covers all affected code paths with 9 test cases using debug injection.
bdc5681 to
06d0757
Compare
Done! I confirmed by checking code and running added MTR test cases for this fix against 10.6 (oldest LTS), and confirmed that this version is also affected. I updated the changes to fit version 10.6, and re-ran MTR to confirm that issue has been fixed, and no regressions were introduced. Also, if this helps with merge process to 13.1, I kept a branch for 13.1 version of this fix in my fork. |
gkodinov
left a comment
There was a problem hiding this comment.
LGTM. Thanks for addressing my remarks!
Please stand by for the final review.
Description
In MariaDB, the
Table_map_log_eventoptional metadataparse_*functions and constructor insql/log_event.cccallnet_field_length()to decode length-encoded integers from the event buffer without validating that the decoded length or advanced pointer remains within bounds. If a corrupted or malicious binlog event contains an inflated length field,net_field_length()advances the read pointer past the buffer end, and subsequent reads (e.g.strmake_root,memcpy, struct assignments) perform OOB memory access.This can result in:
parse_* functions
This patch adds
if (unlikely(p > end)) return;bounds checks after everynet_field_length()call in all affected parse functions. Checks are placed before the decoded value is stored to any struct field, ensuring no OOB data leaks into metadata. On early return, the slave gracefully falls back to positional column mapping — no crash, no OOB read.Table_map_log_event constructor
Adds
if (unlikely(ptr + size > buf + event_len))bounds checks before eachmemcpyin the constructor form_colcnt,m_field_metadata_size, andm_null_bits. On detecting an overflow, the constructor frees allocated memory, setsm_memory = NULL(causingis_valid()to return false), and returns early. The slave then rejects the event gracefully with error 1594.Affected call sites
parse_default_charsetparse_enum_and_set_default_charsetparse_column_charsetparse_enum_and_set_column_charsetparse_column_nameparse_set_str_valueparse_geometry_typeparse_simple_pkparse_pk_with_prefixTable_map_log_eventconstructor (m_colcnt,m_field_metadata,m_null_bits)Release Notes
Slave no longer crashes or reads out-of-bounds memory when receiving a
Table_map_log_eventwith corrupted optional metadata lengths or corrupted column count/metadata size. The slave either falls back to positional column mapping or rejects the event gracefully.How can this PR be tested?
New MTR test
rpl.rpl_table_map_log_event_overflowcovers 9 test cases:Tests 1-7 (master-side injection, parse_ functions):*
parse_column_name— corrupted column name lengthparse_default_charset— corrupted default charset column indexparse_column_charset— corrupted column charset valueparse_set_str_value— corrupted SET string value countparse_geometry_type— corrupted geometry type valueparse_simple_pk— corrupted simple primary key column indexparse_pk_with_prefix— corrupted prefix primary key column indexEach verifies the slave replicates successfully via positional fallback (no crash, correct data).
Tests 8-9 (slave-side injection, constructor):
8.
corrupt_table_map_colcnt_read— inflates decodedm_colcnton the slave9.
corrupt_table_map_field_metadata_size_read— inflates decodedm_field_metadata_sizeon the slaveBoth verify the slave rejects the event with error 1594 (no crash, no OOB read).
Note: A similar bounds check could be added for
infoLenin theRows_log_eventconstructor (RW_V_EXTRAINFO_TAGblock), but MariaDB never writes extra row info — it only reads it from MySQL 5.6+ masters. Since the code path is unreachable in normal MariaDB replication, it cannot be tested via MTR without extreme levels of wizardry - we recommend code review for QA, instead of expensive-to-maintain test case.Basing the PR against the correct MariaDB version
This fix targets the oldest LTS version 10.6 and later.
For convenience, a branch for latest preview 13.1 is available on fork, for simplicity when merging change to later versions.
Copyright
All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc.