Commit 621dc801 authored by Dave Chinner's avatar Dave Chinner
Browse files

Merge branch 'guilt/xfs-5.19-recovery-buf-cancel' into xfs-5.19-for-next



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: default avatarDarrick J. Wong <djwong@kernel.org>
Signed-off-by: default avatarDave Chinner <david@fromorbit.com>
parents 6f5097e3 910bbdf2
Loading
Loading
Loading
Loading
+8 −6
Original line number Diff line number Diff line
@@ -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
@@ -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 −0
Original line number Diff line number Diff line
@@ -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.
@@ -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;
}
+0 −3
Original line number Diff line number Diff line
@@ -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
 */
+11 −23
Original line number Diff line number Diff line
@@ -3223,7 +3223,7 @@ xlog_do_log_recovery(
	xfs_daddr_t	head_blk,
	xfs_daddr_t	tail_blk)
{
	int		error, i;
	int		error;

	ASSERT(head_blk != tail_blk);

@@ -3231,37 +3231,25 @@ xlog_do_log_recovery(
	 * 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;
}