MDEV-40647 OOB read in IO Thread if the FDEv does not support Rotate Events - #5505
MDEV-40647 OOB read in IO Thread if the FDEv does not support Rotate Events#5505ParadoxV5 wants to merge 3 commits into
Conversation
* The crafted invalid FDEs were omitted their checksums, even though
the base code (still) adds the checksum length to the event length.
This commit fixes this discrepancy by not skipping the event footer
step (write the checksum, and finish up encryption if active),
so the fault injections are more self-contained.
This discrepancy did not matter in practice because
* The event loading simply assumes the first
few bytes of the next event as the unused checksum.
* The fix to `get_checksum_alg()` is detecting invalidity before the
code reaches the fixed parser-contructor.
This is rather an implementation detail, though, as the constructor
fix would come to effect if we refactor `get_checksum_alg()` away.
* This commit also disables echoing `SHOW BINLOG EVENTS IN`
to the results in case the `$binlog_file` is not consistent.
It does not typically fail, but should trip MSAN.
…Events If the replication IO Thread receives a Rotate event following a Format Description event (FDE) with no post-header length for Rotate events, the Rotate event’s parser constructor indexes the FDE’s post-header lengths array out of bounds. This commit defends against this situation by checking before the constructor that the FDE describes Rotate events as recognized at all. In practice, because the Binlog Dump thread generates a Fake `ROTATE_EVENT` **before** sending the FDE, it has pinned Rotate events’ post-header length to 8 regardless of FDEs. This fix solution considers that the FDE’s description should still be respected, matching the constructor. Reviewed-by: Kristian Nielsen <knielsen@knielsen-hq.org>
ParadoxV5
left a comment
There was a problem hiding this comment.
I’m still not fully certain about whether I should always consider the post-header length as pinned; that is, ignore whatever the FDE says (or complain if it does not match).
OTOH, if the post-header length is to be used, then it’s another bug that the constructor should uses uint8korr() directly on the buffer to read the log position: this could overrun into the log name or even past the buffer.
Actually, why is the Fake ROTATE_EVENT before the FDE in the first place?
Can we fix this ordering, especially after MDEV-38906 gets rid of IO Thread’s feeble mid-group continuation?
| @@ -0,0 +1,51 @@ | |||
| # MDEV-40647 OOB read in IO Thread if the FDEv does not support Rotate Events | |||
|
|
|||
| --source include/have_debug.inc | |||
There was a problem hiding this comment.
MDEV-40647 requires MSAN to reproduce in CI, but our MSAN CI does not have_debug.
(The alternative is to upload the suspicious binlog the fault injection generates…
Do we want this approach instead?)
| { | ||
| /* | ||
| It's normally done in Log_event::read_log_event(), | ||
| but we bypass it here because it's expensive and costs dynamic memory. |
There was a problem hiding this comment.
The design oversight behind this is that the IO Thread needs to preprocess certain events, but using Log_event::read_log_event() uniformly is unnecessarily expensive, especially for the events it doesn’t preprocess.
|
ParadoxV5 ***@***.***> writes:
MDEV-40647 OOB read in IO Thread if the FDEv does not support Rotate Events
If the replication IO Thread receives a Rotate event following a Format
Description event (FDE) with no post-header length for Rotate events,
the Rotate event’s parser constructor indexes
the FDE’s post-header lengths array out of bounds.
You can view, comment on, or merge this pull request online at:
#5505
Here is my review of this and prior two commits:
case ROTATE_EVENT:
{
+ /*
+ It's normally done in Log_event::read_log_event(),
+ but we bypass it here because it's expensive and costs dynamic memory.
+ */
+ if (unlikely(ROTATE_EVENT >
+ mi->rli.relay_log.description_event_for_queue->number_of_event_types))
+ {
+ // The current FDE does not support `ROTATE_EVENT`.
+ error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE;
+ goto err;
+ }
Rotate_log_event rev(buf, checksum_alg != BINLOG_CHECKSUM_ALG_OFF ?
event_len - BINLOG_CHECKSUM_LEN : event_len,
mi->rli.relay_log.description_event_for_queue);
Can we instead put this check of the
description_event_for_queue->number_of_event_types into that
Rotate_log_event constructor? That seems to be a better place for it (rather
than caller having to remember to check). Also, there seems to be a check
for rev.is_valid() missing (in the original code).
I'd prefer it that way. But if that's not possible/convenient for some
reason, the fix itself looks ok in either case.
@ParadoxV5 commented on this pull request.
I’m still not fully certain about whether I should always consider the
post-header length as pinned; that is, ignore whatever the FDE says
(or complain if it does not match).
I consider the whole idea of the format description event containing the
post-header length a misdesign. It goes way back, and I suppose there was
some intention to use this for backwards-compatible extensions, but I do not
think it works well for this.
The way the format description event is used to interpret the bytes of other
events causes a lot of complexity and trickery in the code, always this need
to try to have the correct format description event available at all times.
There have been lots of bugs from this.
I think your approach in this patch is ok for a 10.6 fix.
Going forward, my preference is to stop relying on these post-header lenghts
in the FDE and even start removing references to it, in new development. My
intention is that we will not change these lengths ever again going forward,
instead of relying eg. on flag bits in the individual event to mark if
certain fields are available (or just the length stored in the event
itself). This is how most of extensions to event formats have been done in
MariaDB.
OTOH, if the post-header length is to be used, then it’s another bug
that the constructor should uses `uint8korr()` directly on the buffer
to read the log position: this could overrun into the log name or even
past the buffer.
I don't fully follow the exact details here, but yes, trying to use the
post-header length is just generally causing complexity and grief.
Actually, why is the Fake `ROTATE_EVENT` before the FDE in the first place?
Can we fix this ordering, especially after
[MDEV-38906](https://jira.mariadb.org/browse/MDEV-38906) gets rid of
IO Thread’s feeble mid-group continuation?
I'm not sure what the consequences are of this. Again, my preferences would
be if we can instead change the code so we don't need to care about the FDE
or its order wrt. other events at all...
Reviewed-by: Kristian Nielsen ***@***.***>
|
Thanks @knielsen,
My opinion is that both this
Indeed.
I remember you mentioned the non-reliance on FDE back in the MDEV-38907 Optimistic Relay Log Crash Recovery pre-review:
I did not fully understand this back then, but now I do; thanks for explaining. I can clearly see that having to keep the FDE around from event streaming to processing is indeed not only quite a bottleneck, but approaches the anti-pattern of a god-object. Perhaps we can get rid of FDEs (Start Events in MariaDB 5) entirely.
I’ve seen it – the presence of both |
This PR is based on #5419 (MDEV-40365/MDEV-40366) for merging their fault injections.
Additionally, the first commit of this PR is a post-approval fixup for #5419; details are in its commit message.
If the replication IO Thread receives a Rotate event following a Format Description event (FDE) with no post-header length for Rotate events, the Rotate event’s parser constructor indexes
the FDE’s post-header lengths array out of bounds.
This commit defends against this situation by checking before the constructor that the FDE describes Rotate events as recognized at all.
(TODO: squash commits 2 into 3)