diff options
author | Darrick J. Wong <darrick.wong@oracle.com> | 2018-01-16 21:54:12 -0500 |
---|---|---|
committer | Darrick J. Wong <darrick.wong@oracle.com> | 2018-01-18 00:00:46 -0500 |
commit | ce92d29ddf9908d397895c46b7c78e9db8df414d (patch) | |
tree | e05535b52a47a975f80995bca5e1260fd3248fa7 | |
parent | 638a7174894c8f2195430990b614615ef16e3912 (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>
-rw-r--r-- | fs/xfs/libxfs/xfs_dir2.h | 2 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_dir2_data.c | 43 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_dir2_sf.c | 4 | ||||
-rw-r--r-- | fs/xfs/scrub/dir.c | 38 | ||||
-rw-r--r-- | fs/xfs/xfs_dir2_readdir.c | 4 |
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 | ||
342 | unsigned char xfs_dir3_get_dtype(struct xfs_mount *mp, uint8_t filetype); | 342 | unsigned char xfs_dir3_get_dtype(struct xfs_mount *mp, uint8_t filetype); |
343 | void *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. */ | ||
1090 | void * | ||
1091 | xfs_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. |