aboutsummaryrefslogtreecommitdiffstats
path: root/fs/xfs
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2018-01-16 21:54:12 -0500
committerDarrick J. Wong <darrick.wong@oracle.com>2018-01-18 00:00:46 -0500
commitce92d29ddf9908d397895c46b7c78e9db8df414d (patch)
treee05535b52a47a975f80995bca5e1260fd3248fa7 /fs/xfs
parent638a7174894c8f2195430990b614615ef16e3912 (diff)
xfs: directory scrubber must walk through data block to offset
In xfs_scrub_dir_rec, we must walk through the directory block entries to arrive at the offset given by the hash structure. If we blindly trust the hash address, we can end up midway into a directory entry and stray outside the block. Found by lastbit fuzzing lents[3].address in xfs/390 with KASAN enabled. Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com> Reviewed-by: Dave Chinner <dchinner@redhat.com>
Diffstat (limited to 'fs/xfs')
-rw-r--r--fs/xfs/libxfs/xfs_dir2.h2
-rw-r--r--fs/xfs/libxfs/xfs_dir2_data.c43
-rw-r--r--fs/xfs/libxfs/xfs_dir2_sf.c4
-rw-r--r--fs/xfs/scrub/dir.c38
-rw-r--r--fs/xfs/xfs_dir2_readdir.c4
5 files changed, 58 insertions, 33 deletions
diff --git a/fs/xfs/libxfs/xfs_dir2.h b/fs/xfs/libxfs/xfs_dir2.h
index 1a8f2cf977ca..388d67c5c903 100644
--- a/fs/xfs/libxfs/xfs_dir2.h
+++ b/fs/xfs/libxfs/xfs_dir2.h
@@ -340,5 +340,7 @@ xfs_dir2_leaf_tail_p(struct xfs_da_geometry *geo, struct xfs_dir2_leaf *lp)
340#define XFS_READDIR_BUFSIZE (32768) 340#define XFS_READDIR_BUFSIZE (32768)
341 341
342unsigned char xfs_dir3_get_dtype(struct xfs_mount *mp, uint8_t filetype); 342unsigned char xfs_dir3_get_dtype(struct xfs_mount *mp, uint8_t filetype);
343void *xfs_dir3_data_endp(struct xfs_da_geometry *geo,
344 struct xfs_dir2_data_hdr *hdr);
343 345
344#endif /* __XFS_DIR2_H__ */ 346#endif /* __XFS_DIR2_H__ */
diff --git a/fs/xfs/libxfs/xfs_dir2_data.c b/fs/xfs/libxfs/xfs_dir2_data.c
index 853d9abdd545..a1e30c751c00 100644
--- a/fs/xfs/libxfs/xfs_dir2_data.c
+++ b/fs/xfs/libxfs/xfs_dir2_data.c
@@ -89,7 +89,6 @@ __xfs_dir3_data_check(
89 case cpu_to_be32(XFS_DIR2_BLOCK_MAGIC): 89 case cpu_to_be32(XFS_DIR2_BLOCK_MAGIC):
90 btp = xfs_dir2_block_tail_p(geo, hdr); 90 btp = xfs_dir2_block_tail_p(geo, hdr);
91 lep = xfs_dir2_block_leaf_p(btp); 91 lep = xfs_dir2_block_leaf_p(btp);
92 endp = (char *)lep;
93 92
94 /* 93 /*
95 * The number of leaf entries is limited by the size of the 94 * The number of leaf entries is limited by the size of the
@@ -104,11 +103,13 @@ __xfs_dir3_data_check(
104 break; 103 break;
105 case cpu_to_be32(XFS_DIR3_DATA_MAGIC): 104 case cpu_to_be32(XFS_DIR3_DATA_MAGIC):
106 case cpu_to_be32(XFS_DIR2_DATA_MAGIC): 105 case cpu_to_be32(XFS_DIR2_DATA_MAGIC):
107 endp = (char *)hdr + geo->blksize;
108 break; 106 break;
109 default: 107 default:
110 return __this_address; 108 return __this_address;
111 } 109 }
110 endp = xfs_dir3_data_endp(geo, hdr);
111 if (!endp)
112 return __this_address;
112 113
113 /* 114 /*
114 * Account for zero bestfree entries. 115 * Account for zero bestfree entries.
@@ -546,7 +547,6 @@ xfs_dir2_data_freescan_int(
546 struct xfs_dir2_data_hdr *hdr, 547 struct xfs_dir2_data_hdr *hdr,
547 int *loghead) 548 int *loghead)
548{ 549{
549 xfs_dir2_block_tail_t *btp; /* block tail */
550 xfs_dir2_data_entry_t *dep; /* active data entry */ 550 xfs_dir2_data_entry_t *dep; /* active data entry */
551 xfs_dir2_data_unused_t *dup; /* unused data entry */ 551 xfs_dir2_data_unused_t *dup; /* unused data entry */
552 struct xfs_dir2_data_free *bf; 552 struct xfs_dir2_data_free *bf;
@@ -568,12 +568,7 @@ xfs_dir2_data_freescan_int(
568 * Set up pointers. 568 * Set up pointers.
569 */ 569 */
570 p = (char *)ops->data_entry_p(hdr); 570 p = (char *)ops->data_entry_p(hdr);
571 if (hdr->magic == cpu_to_be32(XFS_DIR2_BLOCK_MAGIC) || 571 endp = xfs_dir3_data_endp(geo, hdr);
572 hdr->magic == cpu_to_be32(XFS_DIR3_BLOCK_MAGIC)) {
573 btp = xfs_dir2_block_tail_p(geo, hdr);
574 endp = (char *)xfs_dir2_block_leaf_p(btp);
575 } else
576 endp = (char *)hdr + geo->blksize;
577 /* 572 /*
578 * Loop over the block's entries. 573 * Loop over the block's entries.
579 */ 574 */
@@ -786,17 +781,9 @@ xfs_dir2_data_make_free(
786 /* 781 /*
787 * Figure out where the end of the data area is. 782 * Figure out where the end of the data area is.
788 */ 783 */
789 if (hdr->magic == cpu_to_be32(XFS_DIR2_DATA_MAGIC) || 784 endptr = xfs_dir3_data_endp(args->geo, hdr);
790 hdr->magic == cpu_to_be32(XFS_DIR3_DATA_MAGIC)) 785 ASSERT(endptr != NULL);
791 endptr = (char *)hdr + args->geo->blksize;
792 else {
793 xfs_dir2_block_tail_t *btp; /* block tail */
794 786
795 ASSERT(hdr->magic == cpu_to_be32(XFS_DIR2_BLOCK_MAGIC) ||
796 hdr->magic == cpu_to_be32(XFS_DIR3_BLOCK_MAGIC));
797 btp = xfs_dir2_block_tail_p(args->geo, hdr);
798 endptr = (char *)xfs_dir2_block_leaf_p(btp);
799 }
800 /* 787 /*
801 * If this isn't the start of the block, then back up to 788 * If this isn't the start of the block, then back up to
802 * the previous entry and see if it's free. 789 * the previous entry and see if it's free.
@@ -1098,3 +1085,21 @@ xfs_dir2_data_use_free(
1098 } 1085 }
1099 *needscanp = needscan; 1086 *needscanp = needscan;
1100} 1087}
1088
1089/* Find the end of the entry data in a data/block format dir block. */
1090void *
1091xfs_dir3_data_endp(
1092 struct xfs_da_geometry *geo,
1093 struct xfs_dir2_data_hdr *hdr)
1094{
1095 switch (hdr->magic) {
1096 case cpu_to_be32(XFS_DIR3_BLOCK_MAGIC):
1097 case cpu_to_be32(XFS_DIR2_BLOCK_MAGIC):
1098 return xfs_dir2_block_leaf_p(xfs_dir2_block_tail_p(geo, hdr));
1099 case cpu_to_be32(XFS_DIR3_DATA_MAGIC):
1100 case cpu_to_be32(XFS_DIR2_DATA_MAGIC):
1101 return (char *)hdr + geo->blksize;
1102 default:
1103 return NULL;
1104 }
1105}
diff --git a/fs/xfs/libxfs/xfs_dir2_sf.c b/fs/xfs/libxfs/xfs_dir2_sf.c
index 8500fa2a1321..0c75a7f00883 100644
--- a/fs/xfs/libxfs/xfs_dir2_sf.c
+++ b/fs/xfs/libxfs/xfs_dir2_sf.c
@@ -156,7 +156,6 @@ xfs_dir2_block_to_sf(
156 xfs_dir2_sf_hdr_t *sfhp) /* shortform directory hdr */ 156 xfs_dir2_sf_hdr_t *sfhp) /* shortform directory hdr */
157{ 157{
158 xfs_dir2_data_hdr_t *hdr; /* block header */ 158 xfs_dir2_data_hdr_t *hdr; /* block header */
159 xfs_dir2_block_tail_t *btp; /* block tail pointer */
160 xfs_dir2_data_entry_t *dep; /* data entry pointer */ 159 xfs_dir2_data_entry_t *dep; /* data entry pointer */
161 xfs_inode_t *dp; /* incore directory inode */ 160 xfs_inode_t *dp; /* incore directory inode */
162 xfs_dir2_data_unused_t *dup; /* unused data pointer */ 161 xfs_dir2_data_unused_t *dup; /* unused data pointer */
@@ -192,9 +191,8 @@ xfs_dir2_block_to_sf(
192 /* 191 /*
193 * Set up to loop over the block's entries. 192 * Set up to loop over the block's entries.
194 */ 193 */
195 btp = xfs_dir2_block_tail_p(args->geo, hdr);
196 ptr = (char *)dp->d_ops->data_entry_p(hdr); 194 ptr = (char *)dp->d_ops->data_entry_p(hdr);
197 endptr = (char *)xfs_dir2_block_leaf_p(btp); 195 endptr = xfs_dir3_data_endp(args->geo, hdr);
198 sfep = xfs_dir2_sf_firstentry(sfp); 196 sfep = xfs_dir2_sf_firstentry(sfp);
199 /* 197 /*
200 * Loop over the active and unused entries. 198 * Loop over the active and unused entries.
diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c
index f5a0d179eac0..50b6a26b0299 100644
--- a/fs/xfs/scrub/dir.c
+++ b/fs/xfs/scrub/dir.c
@@ -200,6 +200,7 @@ xfs_scrub_dir_rec(
200 struct xfs_inode *dp = ds->dargs.dp; 200 struct xfs_inode *dp = ds->dargs.dp;
201 struct xfs_dir2_data_entry *dent; 201 struct xfs_dir2_data_entry *dent;
202 struct xfs_buf *bp; 202 struct xfs_buf *bp;
203 char *p, *endp;
203 xfs_ino_t ino; 204 xfs_ino_t ino;
204 xfs_dablk_t rec_bno; 205 xfs_dablk_t rec_bno;
205 xfs_dir2_db_t db; 206 xfs_dir2_db_t db;
@@ -239,8 +240,35 @@ xfs_scrub_dir_rec(
239 } 240 }
240 xfs_scrub_buffer_recheck(ds->sc, bp); 241 xfs_scrub_buffer_recheck(ds->sc, bp);
241 242
242 /* Retrieve the entry, sanity check it, and compare hashes. */
243 dent = (struct xfs_dir2_data_entry *)(((char *)bp->b_addr) + off); 243 dent = (struct xfs_dir2_data_entry *)(((char *)bp->b_addr) + off);
244
245 /* Make sure we got a real directory entry. */
246 p = (char *)mp->m_dir_inode_ops->data_entry_p(bp->b_addr);
247 endp = xfs_dir3_data_endp(mp->m_dir_geo, bp->b_addr);
248 if (!endp) {
249 xfs_scrub_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno);
250 goto out_relse;
251 }
252 while (p < endp) {
253 struct xfs_dir2_data_entry *dep;
254 struct xfs_dir2_data_unused *dup;
255
256 dup = (struct xfs_dir2_data_unused *)p;
257 if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
258 p += be16_to_cpu(dup->length);
259 continue;
260 }
261 dep = (struct xfs_dir2_data_entry *)p;
262 if (dep == dent)
263 break;
264 p += mp->m_dir_inode_ops->data_entsize(dep->namelen);
265 }
266 if (p >= endp) {
267 xfs_scrub_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno);
268 goto out_relse;
269 }
270
271 /* Retrieve the entry, sanity check it, and compare hashes. */
244 ino = be64_to_cpu(dent->inumber); 272 ino = be64_to_cpu(dent->inumber);
245 hash = be32_to_cpu(ent->hashval); 273 hash = be32_to_cpu(ent->hashval);
246 tag = be16_to_cpup(dp->d_ops->data_entry_tag_p(dent)); 274 tag = be16_to_cpup(dp->d_ops->data_entry_tag_p(dent));
@@ -363,13 +391,7 @@ xfs_scrub_directory_data_bestfree(
363 391
364 /* Make sure the bestfrees are actually the best free spaces. */ 392 /* Make sure the bestfrees are actually the best free spaces. */
365 ptr = (char *)d_ops->data_entry_p(bp->b_addr); 393 ptr = (char *)d_ops->data_entry_p(bp->b_addr);
366 if (is_block) { 394 endptr = xfs_dir3_data_endp(mp->m_dir_geo, bp->b_addr);
367 struct xfs_dir2_block_tail *btp;
368
369 btp = xfs_dir2_block_tail_p(mp->m_dir_geo, bp->b_addr);
370 endptr = (char *)xfs_dir2_block_leaf_p(btp);
371 } else
372 endptr = (char *)bp->b_addr + BBTOB(bp->b_length);
373 395
374 /* Iterate the entries, stopping when we hit or go past the end. */ 396 /* Iterate the entries, stopping when we hit or go past the end. */
375 while (ptr < endptr) { 397 while (ptr < endptr) {
diff --git a/fs/xfs/xfs_dir2_readdir.c b/fs/xfs/xfs_dir2_readdir.c
index 0c58918bc0ad..b6ae3597bfb0 100644
--- a/fs/xfs/xfs_dir2_readdir.c
+++ b/fs/xfs/xfs_dir2_readdir.c
@@ -152,7 +152,6 @@ xfs_dir2_block_getdents(
152 struct xfs_inode *dp = args->dp; /* incore directory inode */ 152 struct xfs_inode *dp = args->dp; /* incore directory inode */
153 xfs_dir2_data_hdr_t *hdr; /* block header */ 153 xfs_dir2_data_hdr_t *hdr; /* block header */
154 struct xfs_buf *bp; /* buffer for block */ 154 struct xfs_buf *bp; /* buffer for block */
155 xfs_dir2_block_tail_t *btp; /* block tail */
156 xfs_dir2_data_entry_t *dep; /* block data entry */ 155 xfs_dir2_data_entry_t *dep; /* block data entry */
157 xfs_dir2_data_unused_t *dup; /* block unused entry */ 156 xfs_dir2_data_unused_t *dup; /* block unused entry */
158 char *endptr; /* end of the data entries */ 157 char *endptr; /* end of the data entries */
@@ -185,9 +184,8 @@ xfs_dir2_block_getdents(
185 /* 184 /*
186 * Set up values for the loop. 185 * Set up values for the loop.
187 */ 186 */
188 btp = xfs_dir2_block_tail_p(geo, hdr);
189 ptr = (char *)dp->d_ops->data_entry_p(hdr); 187 ptr = (char *)dp->d_ops->data_entry_p(hdr);
190 endptr = (char *)xfs_dir2_block_leaf_p(btp); 188 endptr = xfs_dir3_data_endp(geo, hdr);
191 189
192 /* 190 /*
193 * Loop over the data portion of the block. 191 * Loop over the data portion of the block.