aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Chinner <dchinner@redhat.com>2018-06-05 22:42:13 -0400
committerDarrick J. Wong <darrick.wong@oracle.com>2018-06-06 11:12:00 -0400
commit9e6c08d4a8fc21fc496bf4543e5b2360fc610866 (patch)
tree4327a5e43e0ecb12a4dbb5afbed86ad487d1ba34
parent29cad0b3edaffb65f78f61b63cb0c43f87f98865 (diff)
xfs: validate btree records on retrieval
So we don't check the validity of records as we walk the btree. When there are corrupt records in the free space btree (e.g. zero startblock/length or beyond EOAG) we just blindly use it and things go bad from there. That leads to assert failures on debug kernels like this: XFS: Assertion failed: fs_is_ok, file: fs/xfs/libxfs/xfs_alloc.c, line: 450 .... Call Trace: xfs_alloc_fixup_trees+0x368/0x5c0 xfs_alloc_ag_vextent_near+0x79a/0xe20 xfs_alloc_ag_vextent+0x1d3/0x330 xfs_alloc_vextent+0x5e9/0x870 Or crashes like this: XFS (loop0): xfs_buf_find: daddr 0x7fb28 out of range, EOFS 0x8000 ..... BUG: unable to handle kernel NULL pointer dereference at 00000000000000c8 .... Call Trace: xfs_bmap_add_extent_hole_real+0x67d/0x930 xfs_bmapi_write+0x934/0xc90 xfs_da_grow_inode_int+0x27e/0x2f0 xfs_dir2_grow_inode+0x55/0x130 xfs_dir2_sf_to_block+0x94/0x5d0 xfs_dir2_sf_addname+0xd0/0x590 xfs_dir_createname+0x168/0x1a0 xfs_rename+0x658/0x9b0 By checking that free space records pulled from the trees are within the valid range, we catch many of these corruptions before they can do damage. This is a generic btree record checking deficiency. We need to validate the records we fetch from all the different btrees before we use them to catch corruptions like this. This patch results in a corrupt record emitting an error message and returning -EFSCORRUPTED, and the higher layers catch that and abort: XFS (loop0): Size Freespace BTree record corruption in AG 0 detected! XFS (loop0): start block 0x0 block count 0x0 XFS (loop0): Internal error xfs_trans_cancel at line 1012 of file fs/xfs/xfs_trans.c. Caller xfs_create+0x42a/0x670 ..... Call Trace: dump_stack+0x85/0xcb xfs_trans_cancel+0x19f/0x1c0 xfs_create+0x42a/0x670 xfs_generic_create+0x1f6/0x2c0 vfs_create+0xf9/0x180 do_mknodat+0x1f9/0x210 do_syscall_64+0x5a/0x180 entry_SYSCALL_64_after_hwframe+0x49/0xbe ..... XFS (loop0): xfs_do_force_shutdown(0x8) called from line 1013 of file fs/xfs/xfs_trans.c. Return address = ffffffff81500868 XFS (loop0): Corruption of in-memory data detected. Shutting down filesystem Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com> Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
-rw-r--r--fs/xfs/libxfs/xfs_alloc.c22
-rw-r--r--fs/xfs/libxfs/xfs_ialloc.c31
-rw-r--r--fs/xfs/libxfs/xfs_refcount.c47
-rw-r--r--fs/xfs/libxfs/xfs_rmap.c41
4 files changed, 132 insertions, 9 deletions
diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c
index 0214a77808d0..1bd405eec285 100644
--- a/fs/xfs/libxfs/xfs_alloc.c
+++ b/fs/xfs/libxfs/xfs_alloc.c
@@ -227,6 +227,8 @@ xfs_alloc_get_rec(
227 xfs_extlen_t *len, /* output: length of extent */ 227 xfs_extlen_t *len, /* output: length of extent */
228 int *stat) /* output: success/failure */ 228 int *stat) /* output: success/failure */
229{ 229{
230 struct xfs_mount *mp = cur->bc_mp;
231 xfs_agnumber_t agno = cur->bc_private.a.agno;
230 union xfs_btree_rec *rec; 232 union xfs_btree_rec *rec;
231 int error; 233 int error;
232 234
@@ -234,12 +236,28 @@ xfs_alloc_get_rec(
234 if (error || !(*stat)) 236 if (error || !(*stat))
235 return error; 237 return error;
236 if (rec->alloc.ar_blockcount == 0) 238 if (rec->alloc.ar_blockcount == 0)
237 return -EFSCORRUPTED; 239 goto out_bad_rec;
238 240
239 *bno = be32_to_cpu(rec->alloc.ar_startblock); 241 *bno = be32_to_cpu(rec->alloc.ar_startblock);
240 *len = be32_to_cpu(rec->alloc.ar_blockcount); 242 *len = be32_to_cpu(rec->alloc.ar_blockcount);
241 243
242 return error; 244 /* check for valid extent range, including overflow */
245 if (!xfs_verify_agbno(mp, agno, *bno))
246 goto out_bad_rec;
247 if (*bno > *bno + *len)
248 goto out_bad_rec;
249 if (!xfs_verify_agbno(mp, agno, *bno + *len - 1))
250 goto out_bad_rec;
251
252 return 0;
253
254out_bad_rec:
255 xfs_warn(mp,
256 "%s Freespace BTree record corruption in AG %d detected!",
257 cur->bc_btnum == XFS_BTNUM_BNO ? "Block" : "Size", agno);
258 xfs_warn(mp,
259 "start block 0x%x block count 0x%x", *bno, *len);
260 return -EFSCORRUPTED;
243} 261}
244 262
245/* 263/*
diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c
index 48296adbb0fb..ad53aa5e4ea7 100644
--- a/fs/xfs/libxfs/xfs_ialloc.c
+++ b/fs/xfs/libxfs/xfs_ialloc.c
@@ -133,16 +133,45 @@ xfs_inobt_get_rec(
133 struct xfs_inobt_rec_incore *irec, 133 struct xfs_inobt_rec_incore *irec,
134 int *stat) 134 int *stat)
135{ 135{
136 struct xfs_mount *mp = cur->bc_mp;
137 xfs_agnumber_t agno = cur->bc_private.a.agno;
136 union xfs_btree_rec *rec; 138 union xfs_btree_rec *rec;
137 int error; 139 int error;
140 uint64_t realfree;
138 141
139 error = xfs_btree_get_rec(cur, &rec, stat); 142 error = xfs_btree_get_rec(cur, &rec, stat);
140 if (error || *stat == 0) 143 if (error || *stat == 0)
141 return error; 144 return error;
142 145
143 xfs_inobt_btrec_to_irec(cur->bc_mp, rec, irec); 146 xfs_inobt_btrec_to_irec(mp, rec, irec);
147
148 if (!xfs_verify_agino(mp, agno, irec->ir_startino))
149 goto out_bad_rec;
150 if (irec->ir_count < XFS_INODES_PER_HOLEMASK_BIT ||
151 irec->ir_count > XFS_INODES_PER_CHUNK)
152 goto out_bad_rec;
153 if (irec->ir_freecount > XFS_INODES_PER_CHUNK)
154 goto out_bad_rec;
155
156 /* if there are no holes, return the first available offset */
157 if (!xfs_inobt_issparse(irec->ir_holemask))
158 realfree = irec->ir_free;
159 else
160 realfree = irec->ir_free & xfs_inobt_irec_to_allocmask(irec);
161 if (hweight64(realfree) != irec->ir_freecount)
162 goto out_bad_rec;
144 163
145 return 0; 164 return 0;
165
166out_bad_rec:
167 xfs_warn(mp,
168 "%s Inode BTree record corruption in AG %d detected!",
169 cur->bc_btnum == XFS_BTNUM_INO ? "Used" : "Free", agno);
170 xfs_warn(mp,
171"start inode 0x%x, count 0x%x, free 0x%x freemask 0x%llx, holemask 0x%x",
172 irec->ir_startino, irec->ir_count, irec->ir_freecount,
173 irec->ir_free, irec->ir_holemask);
174 return -EFSCORRUPTED;
146} 175}
147 176
148/* 177/*
diff --git a/fs/xfs/libxfs/xfs_refcount.c b/fs/xfs/libxfs/xfs_refcount.c
index 418d53295893..e8ef951887ef 100644
--- a/fs/xfs/libxfs/xfs_refcount.c
+++ b/fs/xfs/libxfs/xfs_refcount.c
@@ -125,16 +125,53 @@ xfs_refcount_get_rec(
125 struct xfs_refcount_irec *irec, 125 struct xfs_refcount_irec *irec,
126 int *stat) 126 int *stat)
127{ 127{
128 struct xfs_mount *mp = cur->bc_mp;
129 xfs_agnumber_t agno = cur->bc_private.a.agno;
128 union xfs_btree_rec *rec; 130 union xfs_btree_rec *rec;
129 int error; 131 int error;
132 xfs_agblock_t realstart;
130 133
131 error = xfs_btree_get_rec(cur, &rec, stat); 134 error = xfs_btree_get_rec(cur, &rec, stat);
132 if (!error && *stat == 1) { 135 if (error || !*stat)
133 xfs_refcount_btrec_to_irec(rec, irec); 136 return error;
134 trace_xfs_refcount_get(cur->bc_mp, cur->bc_private.a.agno, 137
135 irec); 138 xfs_refcount_btrec_to_irec(rec, irec);
139
140 agno = cur->bc_private.a.agno;
141 if (irec->rc_blockcount == 0 || irec->rc_blockcount > MAXREFCEXTLEN)
142 goto out_bad_rec;
143
144 /* handle special COW-staging state */
145 realstart = irec->rc_startblock;
146 if (realstart & XFS_REFC_COW_START) {
147 if (irec->rc_refcount != 1)
148 goto out_bad_rec;
149 realstart &= ~XFS_REFC_COW_START;
150 } else if (irec->rc_refcount < 2) {
151 goto out_bad_rec;
136 } 152 }
137 return error; 153
154 /* check for valid extent range, including overflow */
155 if (!xfs_verify_agbno(mp, agno, realstart))
156 goto out_bad_rec;
157 if (realstart > realstart + irec->rc_blockcount)
158 goto out_bad_rec;
159 if (!xfs_verify_agbno(mp, agno, realstart + irec->rc_blockcount - 1))
160 goto out_bad_rec;
161
162 if (irec->rc_refcount == 0 || irec->rc_refcount > MAXREFCOUNT)
163 goto out_bad_rec;
164
165 trace_xfs_refcount_get(cur->bc_mp, cur->bc_private.a.agno, irec);
166 return 0;
167
168out_bad_rec:
169 xfs_warn(mp,
170 "Refcount BTree record corruption in AG %d detected!", agno);
171 xfs_warn(mp,
172 "Start block 0x%x, block count 0x%x, references 0x%x",
173 irec->rc_startblock, irec->rc_blockcount, irec->rc_refcount);
174 return -EFSCORRUPTED;
138} 175}
139 176
140/* 177/*
diff --git a/fs/xfs/libxfs/xfs_rmap.c b/fs/xfs/libxfs/xfs_rmap.c
index c0644f1be8a8..220cc72754e4 100644
--- a/fs/xfs/libxfs/xfs_rmap.c
+++ b/fs/xfs/libxfs/xfs_rmap.c
@@ -39,6 +39,7 @@
39#include "xfs_extent_busy.h" 39#include "xfs_extent_busy.h"
40#include "xfs_bmap.h" 40#include "xfs_bmap.h"
41#include "xfs_inode.h" 41#include "xfs_inode.h"
42#include "xfs_ialloc.h"
42 43
43/* 44/*
44 * Lookup the first record less than or equal to [bno, len, owner, offset] 45 * Lookup the first record less than or equal to [bno, len, owner, offset]
@@ -203,6 +204,8 @@ xfs_rmap_get_rec(
203 struct xfs_rmap_irec *irec, 204 struct xfs_rmap_irec *irec,
204 int *stat) 205 int *stat)
205{ 206{
207 struct xfs_mount *mp = cur->bc_mp;
208 xfs_agnumber_t agno = cur->bc_private.a.agno;
206 union xfs_btree_rec *rec; 209 union xfs_btree_rec *rec;
207 int error; 210 int error;
208 211
@@ -210,7 +213,43 @@ xfs_rmap_get_rec(
210 if (error || !*stat) 213 if (error || !*stat)
211 return error; 214 return error;
212 215
213 return xfs_rmap_btrec_to_irec(rec, irec); 216 if (xfs_rmap_btrec_to_irec(rec, irec))
217 goto out_bad_rec;
218
219 if (irec->rm_blockcount == 0)
220 goto out_bad_rec;
221 if (irec->rm_startblock <= XFS_AGFL_BLOCK(mp)) {
222 if (irec->rm_owner != XFS_RMAP_OWN_FS)
223 goto out_bad_rec;
224 if (irec->rm_blockcount != XFS_AGFL_BLOCK(mp) + 1)
225 goto out_bad_rec;
226 } else {
227 /* check for valid extent range, including overflow */
228 if (!xfs_verify_agbno(mp, agno, irec->rm_startblock))
229 goto out_bad_rec;
230 if (irec->rm_startblock >
231 irec->rm_startblock + irec->rm_blockcount)
232 goto out_bad_rec;
233 if (!xfs_verify_agbno(mp, agno,
234 irec->rm_startblock + irec->rm_blockcount - 1))
235 goto out_bad_rec;
236 }
237
238 if (!(xfs_verify_ino(mp, irec->rm_owner) ||
239 (irec->rm_owner <= XFS_RMAP_OWN_FS &&
240 irec->rm_owner >= XFS_RMAP_OWN_MIN)))
241 goto out_bad_rec;
242
243 return 0;
244out_bad_rec:
245 xfs_warn(mp,
246 "Reverse Mapping BTree record corruption in AG %d detected!",
247 agno);
248 xfs_warn(mp,
249 "Owner 0x%llx, flags 0x%x, start block 0x%x block count 0x%x",
250 irec->rm_owner, irec->rm_flags, irec->rm_startblock,
251 irec->rm_blockcount);
252 return -EFSCORRUPTED;
214} 253}
215 254
216struct xfs_find_left_neighbor_info { 255struct xfs_find_left_neighbor_info {