Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[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: Dave Chinner <david@fromorbit.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Lachlan McIlroy <lachlan@sgi.com>

authored by

Dave Chinner and committed by
Lachlan McIlroy
cc09c0dc 6307091f

+66 -19
+8 -2
fs/xfs/xfs_bmap.c
··· 4292 4292 * We have a new transaction, so we should return committed=1, 4293 4293 * even though we're returning an error. 4294 4294 */ 4295 - if (error) { 4295 + if (error) 4296 4296 return error; 4297 - } 4297 + 4298 + /* 4299 + * transaction commit worked ok so we can drop the extra ticket 4300 + * reference that we gained in xfs_trans_dup() 4301 + */ 4302 + xfs_log_ticket_put(ntp->t_ticket); 4303 + 4298 4304 if ((error = xfs_trans_reserve(ntp, 0, logres, 0, XFS_TRANS_PERM_LOG_RES, 4299 4305 logcount))) 4300 4306 return error;
+8 -2
fs/xfs/xfs_inode.c
··· 1782 1782 xfs_trans_ijoin(ntp, ip, XFS_ILOCK_EXCL | XFS_IOLOCK_EXCL); 1783 1783 xfs_trans_ihold(ntp, ip); 1784 1784 1785 - if (!error) 1786 - error = xfs_trans_reserve(ntp, 0, 1785 + if (error) 1786 + return error; 1787 + /* 1788 + * transaction commit worked ok so we can drop the extra ticket 1789 + * reference that we gained in xfs_trans_dup() 1790 + */ 1791 + xfs_log_ticket_put(ntp->t_ticket); 1792 + error = xfs_trans_reserve(ntp, 0, 1787 1793 XFS_ITRUNCATE_LOG_RES(mp), 0, 1788 1794 XFS_TRANS_PERM_LOG_RES, 1789 1795 XFS_ITRUNCATE_LOG_COUNT);
+25 -14
fs/xfs/xfs_log.c
··· 100 100 101 101 102 102 /* local ticket functions */ 103 - STATIC xlog_ticket_t *xlog_ticket_get(xlog_t *log, 103 + STATIC xlog_ticket_t *xlog_ticket_alloc(xlog_t *log, 104 104 int unit_bytes, 105 105 int count, 106 106 char clientid, 107 107 uint flags); 108 - STATIC void xlog_ticket_put(xlog_t *log, xlog_ticket_t *ticket); 109 108 110 109 #if defined(DEBUG) 111 110 STATIC void xlog_verify_dest_ptr(xlog_t *log, __psint_t ptr); ··· 359 360 */ 360 361 xlog_trace_loggrant(log, ticket, "xfs_log_done: (non-permanent)"); 361 362 xlog_ungrant_log_space(log, ticket); 362 - xlog_ticket_put(log, ticket); 363 + xfs_log_ticket_put(ticket); 363 364 } else { 364 365 xlog_trace_loggrant(log, ticket, "xfs_log_done: (permanent)"); 365 366 xlog_regrant_reserve_log_space(log, ticket); ··· 513 514 retval = xlog_regrant_write_log_space(log, internal_ticket); 514 515 } else { 515 516 /* may sleep if need to allocate more tickets */ 516 - internal_ticket = xlog_ticket_get(log, unit_bytes, cnt, 517 + internal_ticket = xlog_ticket_alloc(log, unit_bytes, cnt, 517 518 client, flags); 518 519 if (!internal_ticket) 519 520 return XFS_ERROR(ENOMEM); ··· 748 749 if (tic) { 749 750 xlog_trace_loggrant(log, tic, "unmount rec"); 750 751 xlog_ungrant_log_space(log, tic); 751 - xlog_ticket_put(log, tic); 752 + xfs_log_ticket_put(tic); 752 753 } 753 754 } else { 754 755 /* ··· 3221 3222 */ 3222 3223 3223 3224 /* 3224 - * Free a used ticket. 3225 + * Free a used ticket when it's refcount falls to zero. 3225 3226 */ 3226 - STATIC void 3227 - xlog_ticket_put(xlog_t *log, 3228 - xlog_ticket_t *ticket) 3227 + void 3228 + xfs_log_ticket_put( 3229 + xlog_ticket_t *ticket) 3229 3230 { 3230 - sv_destroy(&ticket->t_wait); 3231 - kmem_zone_free(xfs_log_ticket_zone, ticket); 3232 - } /* xlog_ticket_put */ 3231 + ASSERT(atomic_read(&ticket->t_ref) > 0); 3232 + if (atomic_dec_and_test(&ticket->t_ref)) { 3233 + sv_destroy(&ticket->t_wait); 3234 + kmem_zone_free(xfs_log_ticket_zone, ticket); 3235 + } 3236 + } 3233 3237 3238 + xlog_ticket_t * 3239 + xfs_log_ticket_get( 3240 + xlog_ticket_t *ticket) 3241 + { 3242 + ASSERT(atomic_read(&ticket->t_ref) > 0); 3243 + atomic_inc(&ticket->t_ref); 3244 + return ticket; 3245 + } 3234 3246 3235 3247 /* 3236 3248 * Allocate and initialise a new log ticket. 3237 3249 */ 3238 3250 STATIC xlog_ticket_t * 3239 - xlog_ticket_get(xlog_t *log, 3251 + xlog_ticket_alloc(xlog_t *log, 3240 3252 int unit_bytes, 3241 3253 int cnt, 3242 3254 char client, ··· 3318 3308 unit_bytes += 2*BBSIZE; 3319 3309 } 3320 3310 3311 + atomic_set(&tic->t_ref, 1); 3321 3312 tic->t_unit_res = unit_bytes; 3322 3313 tic->t_curr_res = unit_bytes; 3323 3314 tic->t_cnt = cnt; ··· 3334 3323 xlog_tic_reset_res(tic); 3335 3324 3336 3325 return tic; 3337 - } /* xlog_ticket_get */ 3326 + } 3338 3327 3339 3328 3340 3329 /******************************************************************************
+4
fs/xfs/xfs_log.h
··· 134 134 #ifdef __KERNEL__ 135 135 /* Log manager interfaces */ 136 136 struct xfs_mount; 137 + struct xlog_ticket; 137 138 xfs_lsn_t xfs_log_done(struct xfs_mount *mp, 138 139 xfs_log_ticket_t ticket, 139 140 void **iclog, ··· 177 176 int xfs_log_need_covered(struct xfs_mount *mp); 178 177 179 178 void xlog_iodone(struct xfs_buf *); 179 + 180 + struct xlog_ticket * xfs_log_ticket_get(struct xlog_ticket *ticket); 181 + void xfs_log_ticket_put(struct xlog_ticket *ticket); 180 182 181 183 #endif 182 184
+1
fs/xfs/xfs_log_priv.h
··· 245 245 struct xlog_ticket *t_next; /* :4|8 */ 246 246 struct xlog_ticket *t_prev; /* :4|8 */ 247 247 xlog_tid_t t_tid; /* transaction identifier : 4 */ 248 + atomic_t t_ref; /* ticket reference count : 4 */ 248 249 int t_curr_res; /* current reservation in bytes : 4 */ 249 250 int t_unit_res; /* unit reservation in bytes : 4 */ 250 251 char t_ocnt; /* original count : 1 */
+8 -1
fs/xfs/xfs_trans.c
··· 290 290 ASSERT(tp->t_ticket != NULL); 291 291 292 292 ntp->t_flags = XFS_TRANS_PERM_LOG_RES | (tp->t_flags & XFS_TRANS_RESERVE); 293 - ntp->t_ticket = tp->t_ticket; 293 + ntp->t_ticket = xfs_log_ticket_get(tp->t_ticket); 294 294 ntp->t_blk_res = tp->t_blk_res - tp->t_blk_res_used; 295 295 tp->t_blk_res = tp->t_blk_res_used; 296 296 ntp->t_rtx_res = tp->t_rtx_res - tp->t_rtx_res_used; ··· 1258 1258 return (error); 1259 1259 1260 1260 trans = *tpp; 1261 + 1262 + /* 1263 + * transaction commit worked ok so we can drop the extra ticket 1264 + * reference that we gained in xfs_trans_dup() 1265 + */ 1266 + xfs_log_ticket_put(trans->t_ticket); 1267 + 1261 1268 1262 1269 /* 1263 1270 * Reserve space in the log for th next transaction.
+6
fs/xfs/xfs_utils.c
··· 172 172 *ipp = NULL; 173 173 return code; 174 174 } 175 + 176 + /* 177 + * transaction commit worked ok so we can drop the extra ticket 178 + * reference that we gained in xfs_trans_dup() 179 + */ 180 + xfs_log_ticket_put(tp->t_ticket); 175 181 code = xfs_trans_reserve(tp, 0, log_res, 0, 176 182 XFS_TRANS_PERM_LOG_RES, log_count); 177 183 /*
+6
fs/xfs/xfs_vnodeops.c
··· 1019 1019 goto error0; 1020 1020 } 1021 1021 /* 1022 + * transaction commit worked ok so we can drop the extra ticket 1023 + * reference that we gained in xfs_trans_dup() 1024 + */ 1025 + xfs_log_ticket_put(tp->t_ticket); 1026 + 1027 + /* 1022 1028 * Remove the memory for extent descriptions (just bookkeeping). 1023 1029 */ 1024 1030 if (ip->i_df.if_bytes)