Skip to content

MDEV-40643: Corrupt Heartbeat Log Event can Crash Slave - #5503

Open
bnestere wants to merge 2 commits into
10.6from
10.6-MDEV-40643
Open

MDEV-40643: Corrupt Heartbeat Log Event can Crash Slave#5503
bnestere wants to merge 2 commits into
10.6from
10.6-MDEV-40643

Conversation

@bnestere

@bnestere bnestere commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

A slave can crash when its master sends an event shorter than the
event's own header. A truncated heartbeat makes debug builds fail an
assertion in Binary_string::realloc_raw(). On 64-bit release builds it
stops the slave IO thread with an error message that omits the log
file name. If the master also declares an oversized common header in
its format description event, the slave allocates and fills nearly 4GB
instead. On 32-bit release builds it writes that error message past
the end of a stack buffer. An event under four bytes crashes every
build, because the slave checksums roughly 16 EiB and reads far past
the end of the packet.

Neither queue_event() nor the Heartbeat_log_event constructor bounded
the length the master sent. queue_event() handed that length to
event_checksum_test(), which subtracted the checksum length from it,
and the result wrapped on an event under four bytes. The constructor
computed the log file name length as event_len minus the header
lengths, and on a truncated heartbeat that subtraction wrapped to a
value near 4GB. queue_event() rejected the heartbeat as invalid, so
its error path appended the log file name to the error message using
the wrapped length.

Add the missing bound in both places. queue_event() now rejects an event
shorter than the common header before anything reads that header, and
the slave IO thread stops with an error. The Heartbeat_log_event
constructor now compares event_len against the combined header lengths
before the subtraction. A heartbeat that clears the first check but
still stops inside its headers keeps ident_len at 0 and log_ident at
NULL, so the error path appends nothing. That constructor check also
precedes the read of the extended log position, which previously ran on
a short event before any validation. A short event from the master now
stops the slave IO thread with an error instead of crashing the server.

This PR is organized in two commits. The first is the regression test and
debug injections; the second is the fix and .result file.

Note I used Claude pretty heavily in creating this patch, yet, I'm sure it took
longer for me to use Claude to go back-and-forth with everything it did
wrong, than if I were to have made this from scratch myself..

A slave can crash when its master sends an event shorter than the
event's own header. A truncated heartbeat makes debug builds fail an
assertion in Binary_string::realloc_raw(). On 64-bit release builds it
stops the slave IO thread with an error message that omits the log
file name. If the master also declares an oversized common header in
its format description event, the slave allocates and fills nearly 4GB
instead. On 32-bit release builds it writes that error message past
the end of a stack buffer. An event under four bytes crashes every
build, because the slave checksums roughly 16 EiB and reads far past
the end of the packet.

Neither queue_event() nor the Heartbeat_log_event constructor bounded
the length the master sent. queue_event() handed that length to
event_checksum_test(), which subtracted the checksum length from it,
and the result wrapped on an event under four bytes. The constructor
computed the log file name length as event_len minus the header
lengths, and on a truncated heartbeat that subtraction wrapped to a
value near 4GB. queue_event() rejected the heartbeat as invalid, so
its error path appended the log file name to the error message using
the wrapped length.

Add the missing bound in both places. queue_event() now rejects an event
shorter than the common header before anything reads that header, and
the slave IO thread stops with an error. The Heartbeat_log_event
constructor now compares event_len against the combined header lengths
before the subtraction. A heartbeat that clears the first check but
still stops inside its headers keeps ident_len at 0 and log_ident at
NULL, so the error path appends nothing. That constructor check also
precedes the read of the extended log position, which previously ran on
a short event before any validation. A short event from the master now
stops the slave IO thread with an error instead of crashing the server.

Reviewed-by: TODO
Signed-off-by: Brandon Nesterenko <brandon.nesterenko@mariadb.com>
@bnestere
bnestere requested a review from knielsen August 6, 2026 19:25
@bnestere bnestere added MariaDB Corporation Replication Patches involved in replication labels Aug 6, 2026
@knielsen

knielsen commented Aug 6, 2026 via email

Copy link
Copy Markdown
Member

@knielsen

knielsen commented Aug 6, 2026 via email

Copy link
Copy Markdown
Member

@bnestere

bnestere commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

I wonder why my above email was posted under @knielsen's name. That's worrying..

@knielsen

knielsen commented Aug 7, 2026 via email

Copy link
Copy Markdown
Member

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

Labels

MariaDB Corporation Replication Patches involved in replication

Development

Successfully merging this pull request may close these issues.

2 participants