Skip to content

Commit 0c7fcdb

Browse files
LLfamChandan Babu R
authored andcommitted
xfs: don't walk off the end of a directory data block
This adds sanity checks for xfs_dir2_data_unused and xfs_dir2_data_entry to make sure don't stray beyond valid memory region. Before patching, the loop simply checks that the start offset of the dup and dep is within the range. So in a crafted image, if last entry is xfs_dir2_data_unused, we can change dup->length to dup->length-1 and leave 1 byte of space. In the next traversal, this space will be considered as dup or dep. We may encounter an out of bound read when accessing the fixed members. In the patch, we make sure that the remaining bytes large enough to hold an unused entry before accessing xfs_dir2_data_unused and xfs_dir2_data_unused is XFS_DIR2_DATA_ALIGN byte aligned. We also make sure that the remaining bytes large enough to hold a dirent with a single-byte name before accessing xfs_dir2_data_entry. Signed-off-by: lei lu <[email protected]> Reviewed-by: Darrick J. Wong <[email protected]> Signed-off-by: Chandan Babu R <[email protected]>
1 parent fb63435 commit 0c7fcdb

File tree

2 files changed

+33
-5
lines changed

2 files changed

+33
-5
lines changed

fs/xfs/libxfs/xfs_dir2_data.c

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ __xfs_dir3_data_check(
178178
while (offset < end) {
179179
struct xfs_dir2_data_unused *dup = bp->b_addr + offset;
180180
struct xfs_dir2_data_entry *dep = bp->b_addr + offset;
181+
unsigned int reclen;
182+
183+
/*
184+
* Are the remaining bytes large enough to hold an
185+
* unused entry?
186+
*/
187+
if (offset > end - xfs_dir2_data_unusedsize(1))
188+
return __this_address;
181189

182190
/*
183191
* If it's unused, look for the space in the bestfree table.
@@ -187,9 +195,13 @@ __xfs_dir3_data_check(
187195
if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
188196
xfs_failaddr_t fa;
189197

198+
reclen = xfs_dir2_data_unusedsize(
199+
be16_to_cpu(dup->length));
190200
if (lastfree != 0)
191201
return __this_address;
192-
if (offset + be16_to_cpu(dup->length) > end)
202+
if (be16_to_cpu(dup->length) != reclen)
203+
return __this_address;
204+
if (offset + reclen > end)
193205
return __this_address;
194206
if (be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)) !=
195207
offset)
@@ -207,10 +219,18 @@ __xfs_dir3_data_check(
207219
be16_to_cpu(bf[2].length))
208220
return __this_address;
209221
}
210-
offset += be16_to_cpu(dup->length);
222+
offset += reclen;
211223
lastfree = 1;
212224
continue;
213225
}
226+
227+
/*
228+
* This is not an unused entry. Are the remaining bytes
229+
* large enough for a dirent with a single-byte name?
230+
*/
231+
if (offset > end - xfs_dir2_data_entsize(mp, 1))
232+
return __this_address;
233+
214234
/*
215235
* It's a real entry. Validate the fields.
216236
* If this is a block directory then make sure it's
@@ -219,9 +239,10 @@ __xfs_dir3_data_check(
219239
*/
220240
if (dep->namelen == 0)
221241
return __this_address;
222-
if (!xfs_verify_dir_ino(mp, be64_to_cpu(dep->inumber)))
242+
reclen = xfs_dir2_data_entsize(mp, dep->namelen);
243+
if (offset + reclen > end)
223244
return __this_address;
224-
if (offset + xfs_dir2_data_entsize(mp, dep->namelen) > end)
245+
if (!xfs_verify_dir_ino(mp, be64_to_cpu(dep->inumber)))
225246
return __this_address;
226247
if (be16_to_cpu(*xfs_dir2_data_entry_tag_p(mp, dep)) != offset)
227248
return __this_address;
@@ -245,7 +266,7 @@ __xfs_dir3_data_check(
245266
if (i >= be32_to_cpu(btp->count))
246267
return __this_address;
247268
}
248-
offset += xfs_dir2_data_entsize(mp, dep->namelen);
269+
offset += reclen;
249270
}
250271
/*
251272
* Need to have seen all the entries and all the bestfree slots.

fs/xfs/libxfs/xfs_dir2_priv.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ void xfs_dir2_sf_put_ftype(struct xfs_mount *mp,
189189
extern int xfs_readdir(struct xfs_trans *tp, struct xfs_inode *dp,
190190
struct dir_context *ctx, size_t bufsize);
191191

192+
static inline unsigned int
193+
xfs_dir2_data_unusedsize(
194+
unsigned int len)
195+
{
196+
return round_up(len, XFS_DIR2_DATA_ALIGN);
197+
}
198+
192199
static inline unsigned int
193200
xfs_dir2_data_entsize(
194201
struct xfs_mount *mp,

0 commit comments

Comments
 (0)