[XFS] Fix xfs_free_extent related NULL pointer dereference.

We recently fixed an out-of-space deadlock in XFS, and part of that fix
involved the addition of the XFS_ALLOC_FLAG_FREEING flag to some of the
space allocator calls to indicate they're freeing space, not allocating
it. There was a missed xfs_alloc_fix_freelist condition test that did not
correctly test "flags". The same test would also test an uninitialised
structure field (args->userdata) and depending on its value either would
or would not return early with a critical buffer pointer set to NULL.

This fixes that up, adds asserts to several places to catch future botches
of this nature, and skips sections of xfs_alloc_fix_freelist that are
irrelevent for the space-freeing case.

SGI-PV: 955303
SGI-Modid: xfs-linux-melb:xfs-kern:26743a

Signed-off-by: Nathan Scott <nathans@sgi.com>
This commit is contained in:
Nathan Scott 2006-08-10 14:40:41 +10:00
parent 9f737633e6
commit 0e1edbd999
1 changed files with 54 additions and 49 deletions

View File

@ -1835,40 +1835,47 @@ xfs_alloc_fix_freelist(
&agbp))) &agbp)))
return error; return error;
if (!pag->pagf_init) { if (!pag->pagf_init) {
ASSERT(flags & XFS_ALLOC_FLAG_TRYLOCK);
ASSERT(!(flags & XFS_ALLOC_FLAG_FREEING));
args->agbp = NULL; args->agbp = NULL;
return 0; return 0;
} }
} else } else
agbp = NULL; agbp = NULL;
/* If this is a metadata preferred pag and we are user data /*
* If this is a metadata preferred pag and we are user data
* then try somewhere else if we are not being asked to * then try somewhere else if we are not being asked to
* try harder at this point * try harder at this point
*/ */
if (pag->pagf_metadata && args->userdata && flags) { if (pag->pagf_metadata && args->userdata &&
(flags & XFS_ALLOC_FLAG_TRYLOCK)) {
ASSERT(!(flags & XFS_ALLOC_FLAG_FREEING));
args->agbp = NULL; args->agbp = NULL;
return 0; return 0;
} }
need = XFS_MIN_FREELIST_PAG(pag, mp); if (!(flags & XFS_ALLOC_FLAG_FREEING)) {
delta = need > pag->pagf_flcount ? need - pag->pagf_flcount : 0; need = XFS_MIN_FREELIST_PAG(pag, mp);
/* delta = need > pag->pagf_flcount ? need - pag->pagf_flcount : 0;
* If it looks like there isn't a long enough extent, or enough /*
* total blocks, reject it. * If it looks like there isn't a long enough extent, or enough
*/ * total blocks, reject it.
longest = (pag->pagf_longest > delta) ? */
(pag->pagf_longest - delta) : longest = (pag->pagf_longest > delta) ?
(pag->pagf_flcount > 0 || pag->pagf_longest > 0); (pag->pagf_longest - delta) :
if (args->minlen + args->alignment + args->minalignslop - 1 > longest || (pag->pagf_flcount > 0 || pag->pagf_longest > 0);
(!(flags & XFS_ALLOC_FLAG_FREEING) && if ((args->minlen + args->alignment + args->minalignslop - 1) >
(int)(pag->pagf_freeblks + pag->pagf_flcount - longest ||
need - args->total) < ((int)(pag->pagf_freeblks + pag->pagf_flcount -
(int)args->minleft)) { need - args->total) < (int)args->minleft)) {
if (agbp) if (agbp)
xfs_trans_brelse(tp, agbp); xfs_trans_brelse(tp, agbp);
args->agbp = NULL; args->agbp = NULL;
return 0; return 0;
}
} }
/* /*
* Get the a.g. freespace buffer. * Get the a.g. freespace buffer.
* Can fail if we're not blocking on locks, and it's held. * Can fail if we're not blocking on locks, and it's held.
@ -1878,6 +1885,8 @@ xfs_alloc_fix_freelist(
&agbp))) &agbp)))
return error; return error;
if (agbp == NULL) { if (agbp == NULL) {
ASSERT(flags & XFS_ALLOC_FLAG_TRYLOCK);
ASSERT(!(flags & XFS_ALLOC_FLAG_FREEING));
args->agbp = NULL; args->agbp = NULL;
return 0; return 0;
} }
@ -1887,22 +1896,24 @@ xfs_alloc_fix_freelist(
*/ */
agf = XFS_BUF_TO_AGF(agbp); agf = XFS_BUF_TO_AGF(agbp);
need = XFS_MIN_FREELIST(agf, mp); need = XFS_MIN_FREELIST(agf, mp);
delta = need > be32_to_cpu(agf->agf_flcount) ?
(need - be32_to_cpu(agf->agf_flcount)) : 0;
/* /*
* If there isn't enough total or single-extent, reject it. * If there isn't enough total or single-extent, reject it.
*/ */
longest = be32_to_cpu(agf->agf_longest); if (!(flags & XFS_ALLOC_FLAG_FREEING)) {
longest = (longest > delta) ? (longest - delta) : delta = need > be32_to_cpu(agf->agf_flcount) ?
(be32_to_cpu(agf->agf_flcount) > 0 || longest > 0); (need - be32_to_cpu(agf->agf_flcount)) : 0;
if (args->minlen + args->alignment + args->minalignslop - 1 > longest || longest = be32_to_cpu(agf->agf_longest);
(!(flags & XFS_ALLOC_FLAG_FREEING) && longest = (longest > delta) ? (longest - delta) :
(int)(be32_to_cpu(agf->agf_freeblks) + (be32_to_cpu(agf->agf_flcount) > 0 || longest > 0);
be32_to_cpu(agf->agf_flcount) - need - args->total) < if ((args->minlen + args->alignment + args->minalignslop - 1) >
(int)args->minleft)) { longest ||
xfs_trans_brelse(tp, agbp); ((int)(be32_to_cpu(agf->agf_freeblks) +
args->agbp = NULL; be32_to_cpu(agf->agf_flcount) - need - args->total) <
return 0; (int)args->minleft)) {
xfs_trans_brelse(tp, agbp);
args->agbp = NULL;
return 0;
}
} }
/* /*
* Make the freelist shorter if it's too long. * Make the freelist shorter if it's too long.
@ -1950,12 +1961,11 @@ xfs_alloc_fix_freelist(
* on a completely full ag. * on a completely full ag.
*/ */
if (targs.agbno == NULLAGBLOCK) { if (targs.agbno == NULLAGBLOCK) {
if (!(flags & XFS_ALLOC_FLAG_FREEING)) { if (flags & XFS_ALLOC_FLAG_FREEING)
xfs_trans_brelse(tp, agflbp); break;
args->agbp = NULL; xfs_trans_brelse(tp, agflbp);
return 0; args->agbp = NULL;
} return 0;
break;
} }
/* /*
* Put each allocated block on the list. * Put each allocated block on the list.
@ -2442,31 +2452,26 @@ xfs_free_extent(
xfs_fsblock_t bno, /* starting block number of extent */ xfs_fsblock_t bno, /* starting block number of extent */
xfs_extlen_t len) /* length of extent */ xfs_extlen_t len) /* length of extent */
{ {
#ifdef DEBUG xfs_alloc_arg_t args;
xfs_agf_t *agf; /* a.g. freespace header */
#endif
xfs_alloc_arg_t args; /* allocation argument structure */
int error; int error;
ASSERT(len != 0); ASSERT(len != 0);
memset(&args, 0, sizeof(xfs_alloc_arg_t));
args.tp = tp; args.tp = tp;
args.mp = tp->t_mountp; args.mp = tp->t_mountp;
args.agno = XFS_FSB_TO_AGNO(args.mp, bno); args.agno = XFS_FSB_TO_AGNO(args.mp, bno);
ASSERT(args.agno < args.mp->m_sb.sb_agcount); ASSERT(args.agno < args.mp->m_sb.sb_agcount);
args.agbno = XFS_FSB_TO_AGBNO(args.mp, bno); args.agbno = XFS_FSB_TO_AGBNO(args.mp, bno);
args.alignment = 1;
args.minlen = args.minleft = args.minalignslop = 0;
down_read(&args.mp->m_peraglock); down_read(&args.mp->m_peraglock);
args.pag = &args.mp->m_perag[args.agno]; args.pag = &args.mp->m_perag[args.agno];
if ((error = xfs_alloc_fix_freelist(&args, XFS_ALLOC_FLAG_FREEING))) if ((error = xfs_alloc_fix_freelist(&args, XFS_ALLOC_FLAG_FREEING)))
goto error0; goto error0;
#ifdef DEBUG #ifdef DEBUG
ASSERT(args.agbp != NULL); ASSERT(args.agbp != NULL);
agf = XFS_BUF_TO_AGF(args.agbp); ASSERT((args.agbno + len) <=
ASSERT(args.agbno + len <= be32_to_cpu(agf->agf_length)); be32_to_cpu(XFS_BUF_TO_AGF(args.agbp)->agf_length));
#endif #endif
error = xfs_free_ag_extent(tp, args.agbp, args.agno, args.agbno, error = xfs_free_ag_extent(tp, args.agbp, args.agno, args.agbno, len, 0);
len, 0);
error0: error0:
up_read(&args.mp->m_peraglock); up_read(&args.mp->m_peraglock);
return error; return error;