aboutsummaryrefslogtreecommitdiffstats
path: root/fs/xfs/xfs_log_recover.c
diff options
context:
space:
mode:
authorBrian Foster <bfoster@redhat.com>2015-07-28 21:51:10 -0400
committerDave Chinner <david@fromorbit.com>2015-07-28 21:51:10 -0400
commit89cebc8477290b152618ffa110bbeae340d50900 (patch)
treee17344fbade906bd6fc6f2831770fec66e586db6 /fs/xfs/xfs_log_recover.c
parent4703da7b78776140477a023c99683d3be84b7fca (diff)
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 <bfoster@redhat.com> Reviewed-by: Dave Chinner <dchinner@redhat.com> Signed-off-by: Dave Chinner <david@fromorbit.com>
Diffstat (limited to 'fs/xfs/xfs_log_recover.c')
-rw-r--r--fs/xfs/xfs_log_recover.c28
1 files changed, 25 insertions, 3 deletions
diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c
index 01dd228ca05e..493a8ef146fc 100644
--- a/fs/xfs/xfs_log_recover.c
+++ b/fs/xfs/xfs_log_recover.c
@@ -3380,14 +3380,24 @@ xlog_recover_add_to_cont_trans(
3380 char *ptr, *old_ptr; 3380 char *ptr, *old_ptr;
3381 int old_len; 3381 int old_len;
3382 3382
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 */
3383 if (list_empty(&trans->r_itemq)) { 3387 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
3385 xlog_recover_add_item(&trans->r_itemq); 3394 xlog_recover_add_item(&trans->r_itemq);
3386 ptr = (char *)&trans->r_theader + 3395 ptr = (char *)&trans->r_theader +
3387 sizeof(xfs_trans_header_t) - len; 3396 sizeof(struct xfs_trans_header) - len;
3388 memcpy(ptr, dp, len); 3397 memcpy(ptr, dp, len);
3389 return 0; 3398 return 0;
3390 } 3399 }
3400
3391 /* take the tail entry */ 3401 /* take the tail entry */
3392 item = list_entry(trans->r_itemq.prev, xlog_recover_item_t, ri_list); 3402 item = list_entry(trans->r_itemq.prev, xlog_recover_item_t, ri_list);
3393 3403
@@ -3436,7 +3446,19 @@ xlog_recover_add_to_trans(
3436 ASSERT(0); 3446 ASSERT(0);
3437 return -EIO; 3447 return -EIO;
3438 } 3448 }
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))
3440 xlog_recover_add_item(&trans->r_itemq); 3462 xlog_recover_add_item(&trans->r_itemq);
3441 memcpy(&trans->r_theader, dp, len); 3463 memcpy(&trans->r_theader, dp, len);
3442 return 0; 3464 return 0;