Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc09c0dc authored by Dave Chinner's avatar Dave Chinner Committed by Lachlan McIlroy
Browse files

[XFS] Fix double free of log tickets



When an I/O error occurs during an intermediate commit on a rolling
transaction, xfs_trans_commit() will free the transaction structure
and the related ticket. However, the duplicate transaction that
gets used as the transaction continues still contains a pointer
to the ticket. Hence when the duplicate transaction is cancelled
and freed, we free the ticket a second time.

Add reference counting to the ticket so that we hold an extra
reference to the ticket over the transaction commit. We drop the
extra reference once we have checked that the transaction commit
did not return an error, thus avoiding a double free on commit
error.

Credit to Nick Piggin for tripping over the problem.

SGI-PV: 989741

Signed-off-by: default avatarDave Chinner <david@fromorbit.com>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
Signed-off-by: default avatarLachlan McIlroy <lachlan@sgi.com>
parent 6307091f
Loading
Loading
Loading
Loading
+8 −2
Original line number Original line Diff line number Diff line
@@ -4292,9 +4292,15 @@ xfs_bmap_finish(
	 * We have a new transaction, so we should return committed=1,
	 * We have a new transaction, so we should return committed=1,
	 * even though we're returning an error.
	 * even though we're returning an error.
	 */
	 */
	if (error) {
	if (error)
		return error;
		return error;
	}

	/*
	 * transaction commit worked ok so we can drop the extra ticket
	 * reference that we gained in xfs_trans_dup()
	 */
	xfs_log_ticket_put(ntp->t_ticket);

	if ((error = xfs_trans_reserve(ntp, 0, logres, 0, XFS_TRANS_PERM_LOG_RES,
	if ((error = xfs_trans_reserve(ntp, 0, logres, 0, XFS_TRANS_PERM_LOG_RES,
			logcount)))
			logcount)))
		return error;
		return error;
+8 −2
Original line number Original line Diff line number Diff line
@@ -1782,7 +1782,13 @@ xfs_itruncate_finish(
		xfs_trans_ijoin(ntp, ip, XFS_ILOCK_EXCL | XFS_IOLOCK_EXCL);
		xfs_trans_ijoin(ntp, ip, XFS_ILOCK_EXCL | XFS_IOLOCK_EXCL);
		xfs_trans_ihold(ntp, ip);
		xfs_trans_ihold(ntp, ip);


		if (!error)
		if (error)
			return error;
		/*
		 * transaction commit worked ok so we can drop the extra ticket
		 * reference that we gained in xfs_trans_dup()
		 */
		xfs_log_ticket_put(ntp->t_ticket);
		error = xfs_trans_reserve(ntp, 0,
		error = xfs_trans_reserve(ntp, 0,
					XFS_ITRUNCATE_LOG_RES(mp), 0,
					XFS_ITRUNCATE_LOG_RES(mp), 0,
					XFS_TRANS_PERM_LOG_RES,
					XFS_TRANS_PERM_LOG_RES,
+25 −14
Original line number Original line Diff line number Diff line
@@ -100,12 +100,11 @@ STATIC void xlog_ungrant_log_space(xlog_t *log,




/* local ticket functions */
/* local ticket functions */
STATIC xlog_ticket_t	*xlog_ticket_get(xlog_t *log,
STATIC xlog_ticket_t	*xlog_ticket_alloc(xlog_t *log,
					 int	unit_bytes,
					 int	unit_bytes,
					 int	count,
					 int	count,
					 char	clientid,
					 char	clientid,
					 uint	flags);
					 uint	flags);
STATIC void		xlog_ticket_put(xlog_t *log, xlog_ticket_t *ticket);


#if defined(DEBUG)
#if defined(DEBUG)
STATIC void	xlog_verify_dest_ptr(xlog_t *log, __psint_t ptr);
STATIC void	xlog_verify_dest_ptr(xlog_t *log, __psint_t ptr);
@@ -360,7 +359,7 @@ xfs_log_done(xfs_mount_t *mp,
		 */
		 */
		xlog_trace_loggrant(log, ticket, "xfs_log_done: (non-permanent)");
		xlog_trace_loggrant(log, ticket, "xfs_log_done: (non-permanent)");
		xlog_ungrant_log_space(log, ticket);
		xlog_ungrant_log_space(log, ticket);
		xlog_ticket_put(log, ticket);
		xfs_log_ticket_put(ticket);
	} else {
	} else {
		xlog_trace_loggrant(log, ticket, "xfs_log_done: (permanent)");
		xlog_trace_loggrant(log, ticket, "xfs_log_done: (permanent)");
		xlog_regrant_reserve_log_space(log, ticket);
		xlog_regrant_reserve_log_space(log, ticket);
@@ -514,7 +513,7 @@ xfs_log_reserve(xfs_mount_t *mp,
		retval = xlog_regrant_write_log_space(log, internal_ticket);
		retval = xlog_regrant_write_log_space(log, internal_ticket);
	} else {
	} else {
		/* may sleep if need to allocate more tickets */
		/* may sleep if need to allocate more tickets */
		internal_ticket = xlog_ticket_get(log, unit_bytes, cnt,
		internal_ticket = xlog_ticket_alloc(log, unit_bytes, cnt,
						  client, flags);
						  client, flags);
		if (!internal_ticket)
		if (!internal_ticket)
			return XFS_ERROR(ENOMEM);
			return XFS_ERROR(ENOMEM);
@@ -749,7 +748,7 @@ xfs_log_unmount_write(xfs_mount_t *mp)
		if (tic) {
		if (tic) {
			xlog_trace_loggrant(log, tic, "unmount rec");
			xlog_trace_loggrant(log, tic, "unmount rec");
			xlog_ungrant_log_space(log, tic);
			xlog_ungrant_log_space(log, tic);
			xlog_ticket_put(log, tic);
			xfs_log_ticket_put(tic);
		}
		}
	} else {
	} else {
		/*
		/*
@@ -3222,22 +3221,33 @@ xlog_state_want_sync(xlog_t *log, xlog_in_core_t *iclog)
 */
 */


/*
/*
 * Free a used ticket.
 * Free a used ticket when it's refcount falls to zero.
 */
 */
STATIC void
void
xlog_ticket_put(xlog_t		*log,
xfs_log_ticket_put(
	xlog_ticket_t	*ticket)
	xlog_ticket_t	*ticket)
{
{
	ASSERT(atomic_read(&ticket->t_ref) > 0);
	if (atomic_dec_and_test(&ticket->t_ref)) {
		sv_destroy(&ticket->t_wait);
		sv_destroy(&ticket->t_wait);
		kmem_zone_free(xfs_log_ticket_zone, ticket);
		kmem_zone_free(xfs_log_ticket_zone, ticket);
}	/* xlog_ticket_put */
	}
}


xlog_ticket_t *
xfs_log_ticket_get(
	xlog_ticket_t	*ticket)
{
	ASSERT(atomic_read(&ticket->t_ref) > 0);
	atomic_inc(&ticket->t_ref);
	return ticket;
}


/*
/*
 * Allocate and initialise a new log ticket.
 * Allocate and initialise a new log ticket.
 */
 */
STATIC xlog_ticket_t *
STATIC xlog_ticket_t *
xlog_ticket_get(xlog_t		*log,
xlog_ticket_alloc(xlog_t		*log,
		int		unit_bytes,
		int		unit_bytes,
		int		cnt,
		int		cnt,
		char		client,
		char		client,
@@ -3308,6 +3318,7 @@ xlog_ticket_get(xlog_t *log,
		unit_bytes += 2*BBSIZE;
		unit_bytes += 2*BBSIZE;
        }
        }


	atomic_set(&tic->t_ref, 1);
	tic->t_unit_res		= unit_bytes;
	tic->t_unit_res		= unit_bytes;
	tic->t_curr_res		= unit_bytes;
	tic->t_curr_res		= unit_bytes;
	tic->t_cnt		= cnt;
	tic->t_cnt		= cnt;
@@ -3323,7 +3334,7 @@ xlog_ticket_get(xlog_t *log,
	xlog_tic_reset_res(tic);
	xlog_tic_reset_res(tic);


	return tic;
	return tic;
}	/* xlog_ticket_get */
}




/******************************************************************************
/******************************************************************************
+4 −0
Original line number Original line Diff line number Diff line
@@ -134,6 +134,7 @@ typedef struct xfs_log_callback {
#ifdef __KERNEL__
#ifdef __KERNEL__
/* Log manager interfaces */
/* Log manager interfaces */
struct xfs_mount;
struct xfs_mount;
struct xlog_ticket;
xfs_lsn_t xfs_log_done(struct xfs_mount *mp,
xfs_lsn_t xfs_log_done(struct xfs_mount *mp,
		       xfs_log_ticket_t ticket,
		       xfs_log_ticket_t ticket,
		       void		**iclog,
		       void		**iclog,
@@ -177,6 +178,9 @@ int xfs_log_need_covered(struct xfs_mount *mp);


void	  xlog_iodone(struct xfs_buf *);
void	  xlog_iodone(struct xfs_buf *);


struct xlog_ticket * xfs_log_ticket_get(struct xlog_ticket *ticket);
void	  xfs_log_ticket_put(struct xlog_ticket *ticket);

#endif
#endif




+1 −0
Original line number Original line Diff line number Diff line
@@ -245,6 +245,7 @@ typedef struct xlog_ticket {
	struct xlog_ticket *t_next;	 /*			         :4|8 */
	struct xlog_ticket *t_next;	 /*			         :4|8 */
	struct xlog_ticket *t_prev;	 /*				 :4|8 */
	struct xlog_ticket *t_prev;	 /*				 :4|8 */
	xlog_tid_t	   t_tid;	 /* transaction identifier	 : 4  */
	xlog_tid_t	   t_tid;	 /* transaction identifier	 : 4  */
	atomic_t	   t_ref;	 /* ticket reference count       : 4  */
	int		   t_curr_res;	 /* current reservation in bytes : 4  */
	int		   t_curr_res;	 /* current reservation in bytes : 4  */
	int		   t_unit_res;	 /* unit reservation in bytes    : 4  */
	int		   t_unit_res;	 /* unit reservation in bytes    : 4  */
	char		   t_ocnt;	 /* original count		 : 1  */
	char		   t_ocnt;	 /* original count		 : 1  */
Loading