Skip to content

Commit

Permalink
Merge branch 'guilt/xfs-5.19-recovery-buf-cancel' into xfs-5.19-for-next
Browse files Browse the repository at this point in the history
As part of solving the memory leaks and UAF problems in the new LARP
code, kmemleak also reported that log recovery will leak the table
used to hash buffer cancellations if the recovery fails.  Fix this
problem by creating alloc/free helpers that initialize and free the
hashtable contents correctly.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Dave Chinner <david@fromorbit.com>
  • Loading branch information
Dave Chinner committed May 30, 2022
2 parents 6f5097e + 910bbdf commit 621dc80
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 32 deletions.
14 changes: 8 additions & 6 deletions fs/xfs/libxfs/xfs_log_recover.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,6 @@ struct xlog_recover {

#define ITEM_TYPE(i) (*(unsigned short *)(i)->ri_buf[0].i_addr)

/*
* This is the number of entries in the l_buf_cancel_table used during
* recovery.
*/
#define XLOG_BC_TABLE_SIZE 64

#define XLOG_RECOVER_CRCPASS 0
#define XLOG_RECOVER_PASS1 1
#define XLOG_RECOVER_PASS2 2
Expand All @@ -128,5 +122,13 @@ int xlog_recover_iget(struct xfs_mount *mp, xfs_ino_t ino,
struct xfs_inode **ipp);
void xlog_recover_release_intent(struct xlog *log, unsigned short intent_type,
uint64_t intent_id);
int xlog_alloc_buf_cancel_table(struct xlog *log);
void xlog_free_buf_cancel_table(struct xlog *log);

#ifdef DEBUG
void xlog_check_buf_cancel_table(struct xlog *log);
#else
#define xlog_check_buf_cancel_table(log) do { } while (0)
#endif

#endif /* __XFS_LOG_RECOVER_H__ */
66 changes: 66 additions & 0 deletions fs/xfs/xfs_buf_item_recover.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
#include "xfs_dir2.h"
#include "xfs_quota.h"

/*
* This is the number of entries in the l_buf_cancel_table used during
* recovery.
*/
#define XLOG_BC_TABLE_SIZE 64

#define XLOG_BUF_CANCEL_BUCKET(log, blkno) \
((log)->l_buf_cancel_table + ((uint64_t)blkno % XLOG_BC_TABLE_SIZE))

/*
* This structure is used during recovery to record the buf log items which
* have been canceled and should not be replayed.
Expand Down Expand Up @@ -993,3 +1002,60 @@ const struct xlog_recover_item_ops xlog_buf_item_ops = {
.commit_pass1 = xlog_recover_buf_commit_pass1,
.commit_pass2 = xlog_recover_buf_commit_pass2,
};

#ifdef DEBUG
void
xlog_check_buf_cancel_table(
struct xlog *log)
{
int i;

for (i = 0; i < XLOG_BC_TABLE_SIZE; i++)
ASSERT(list_empty(&log->l_buf_cancel_table[i]));
}
#endif

int
xlog_alloc_buf_cancel_table(
struct xlog *log)
{
void *p;
int i;

ASSERT(log->l_buf_cancel_table == NULL);

p = kmalloc_array(XLOG_BC_TABLE_SIZE, sizeof(struct list_head),
GFP_KERNEL);
if (!p)
return -ENOMEM;

log->l_buf_cancel_table = p;
for (i = 0; i < XLOG_BC_TABLE_SIZE; i++)
INIT_LIST_HEAD(&log->l_buf_cancel_table[i]);

return 0;
}

void
xlog_free_buf_cancel_table(
struct xlog *log)
{
int i;

if (!log->l_buf_cancel_table)
return;

for (i = 0; i < XLOG_BC_TABLE_SIZE; i++) {
struct xfs_buf_cancel *bc;

while ((bc = list_first_entry_or_null(
&log->l_buf_cancel_table[i],
struct xfs_buf_cancel, bc_list))) {
list_del(&bc->bc_list);
kmem_free(bc);
}
}

kmem_free(log->l_buf_cancel_table);
log->l_buf_cancel_table = NULL;
}
3 changes: 0 additions & 3 deletions fs/xfs/xfs_log_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -428,9 +428,6 @@ struct xlog {
struct rw_semaphore l_incompat_users;
};

#define XLOG_BUF_CANCEL_BUCKET(log, blkno) \
((log)->l_buf_cancel_table + ((uint64_t)blkno % XLOG_BC_TABLE_SIZE))

/*
* Bits for operational state
*/
Expand Down
34 changes: 11 additions & 23 deletions fs/xfs/xfs_log_recover.c
Original file line number Diff line number Diff line change
Expand Up @@ -3223,45 +3223,33 @@ xlog_do_log_recovery(
xfs_daddr_t head_blk,
xfs_daddr_t tail_blk)
{
int error, i;
int error;

ASSERT(head_blk != tail_blk);

/*
* First do a pass to find all of the cancelled buf log items.
* Store them in the buf_cancel_table for use in the second pass.
*/
log->l_buf_cancel_table = kmem_zalloc(XLOG_BC_TABLE_SIZE *
sizeof(struct list_head),
0);
for (i = 0; i < XLOG_BC_TABLE_SIZE; i++)
INIT_LIST_HEAD(&log->l_buf_cancel_table[i]);
error = xlog_alloc_buf_cancel_table(log);
if (error)
return error;

error = xlog_do_recovery_pass(log, head_blk, tail_blk,
XLOG_RECOVER_PASS1, NULL);
if (error != 0) {
kmem_free(log->l_buf_cancel_table);
log->l_buf_cancel_table = NULL;
return error;
}
if (error != 0)
goto out_cancel;

/*
* Then do a second pass to actually recover the items in the log.
* When it is complete free the table of buf cancel items.
*/
error = xlog_do_recovery_pass(log, head_blk, tail_blk,
XLOG_RECOVER_PASS2, NULL);
#ifdef DEBUG
if (!error) {
int i;

for (i = 0; i < XLOG_BC_TABLE_SIZE; i++)
ASSERT(list_empty(&log->l_buf_cancel_table[i]));
}
#endif /* DEBUG */

kmem_free(log->l_buf_cancel_table);
log->l_buf_cancel_table = NULL;

if (!error)
xlog_check_buf_cancel_table(log);
out_cancel:
xlog_free_buf_cancel_table(log);
return error;
}

Expand Down

0 comments on commit 621dc80

Please sign in to comment.