Skip to content

Commit 89cebc8

Browse files
Brian Fosterdchinner
authored andcommitted
xfs: validate transaction header length on log recovery
When log recovery hits a new transaction, it copies the transaction header from the expected location in the log to the in-core structure using the length from the op record header. This length is validated to ensure it doesn't exceed the length of the record, but not against the expected size of a transaction header (and thus the size of the in-core structure). If the on-disk length is corrupted, the associated memcpy() can overflow, write to unrelated memory and lead to crashes. This has been reproduced via filesystem fuzzing. The code currently handles the possibility that the transaction header is split across two op records. Neither instance accounts for corruption where the op record length might be larger than the in-core transaction header. Update both sites to detect such corruption, warn and return an error from log recovery. Also add some comments and assert that if the record is split, the copy of the second portion is less than a full header. Otherwise, this suggests the copy of the second portion could have overwritten bits from the first and thus that something could be wrong. Signed-off-by: Brian Foster <[email protected]> Reviewed-by: Dave Chinner <[email protected]> Signed-off-by: Dave Chinner <[email protected]>
1 parent 4703da7 commit 89cebc8

File tree

1 file changed

+25
-3
lines changed

1 file changed

+25
-3
lines changed

fs/xfs/xfs_log_recover.c

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3380,14 +3380,24 @@ xlog_recover_add_to_cont_trans(
33803380
char *ptr, *old_ptr;
33813381
int old_len;
33823382

3383+
/*
3384+
* If the transaction is empty, the header was split across this and the
3385+
* previous record. Copy the rest of the header.
3386+
*/
33833387
if (list_empty(&trans->r_itemq)) {
3384-
/* finish copying rest of trans header */
3388+
ASSERT(len < sizeof(struct xfs_trans_header));
3389+
if (len > sizeof(struct xfs_trans_header)) {
3390+
xfs_warn(log->l_mp, "%s: bad header length", __func__);
3391+
return -EIO;
3392+
}
3393+
33853394
xlog_recover_add_item(&trans->r_itemq);
33863395
ptr = (char *)&trans->r_theader +
3387-
sizeof(xfs_trans_header_t) - len;
3396+
sizeof(struct xfs_trans_header) - len;
33883397
memcpy(ptr, dp, len);
33893398
return 0;
33903399
}
3400+
33913401
/* take the tail entry */
33923402
item = list_entry(trans->r_itemq.prev, xlog_recover_item_t, ri_list);
33933403

@@ -3436,7 +3446,19 @@ xlog_recover_add_to_trans(
34363446
ASSERT(0);
34373447
return -EIO;
34383448
}
3439-
if (len == sizeof(xfs_trans_header_t))
3449+
3450+
if (len > sizeof(struct xfs_trans_header)) {
3451+
xfs_warn(log->l_mp, "%s: bad header length", __func__);
3452+
ASSERT(0);
3453+
return -EIO;
3454+
}
3455+
3456+
/*
3457+
* The transaction header can be arbitrarily split across op
3458+
* records. If we don't have the whole thing here, copy what we
3459+
* do have and handle the rest in the next record.
3460+
*/
3461+
if (len == sizeof(struct xfs_trans_header))
34403462
xlog_recover_add_item(&trans->r_itemq);
34413463
memcpy(&trans->r_theader, dp, len);
34423464
return 0;

0 commit comments

Comments
 (0)