Skip to content

Commit 6207214

Browse files
committed
Merge tag 'afs-fixes-04012021' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs
Pull AFS fixes from David Howells: "Two fixes. The first is the fix for the strnlen() array limit check and the second fixes the calculation of the number of dirent records used to represent any particular filename length" * tag 'afs-fixes-04012021' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs: afs: Fix directory entry size calculation afs: Work around strnlen() oops with CONFIG_FORTIFIED_SOURCE=y
2 parents c2407cf + 366911c commit 6207214

4 files changed

Lines changed: 51 additions & 31 deletions

File tree

fs/afs/dir.c

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ static int afs_dir_iterate_block(struct afs_vnode *dvnode,
350350
unsigned blkoff)
351351
{
352352
union afs_xdr_dirent *dire;
353-
unsigned offset, next, curr;
353+
unsigned offset, next, curr, nr_slots;
354354
size_t nlen;
355355
int tmp;
356356

@@ -363,13 +363,12 @@ static int afs_dir_iterate_block(struct afs_vnode *dvnode,
363363
offset < AFS_DIR_SLOTS_PER_BLOCK;
364364
offset = next
365365
) {
366-
next = offset + 1;
367-
368366
/* skip entries marked unused in the bitmap */
369367
if (!(block->hdr.bitmap[offset / 8] &
370368
(1 << (offset % 8)))) {
371369
_debug("ENT[%zu.%u]: unused",
372370
blkoff / sizeof(union afs_xdr_dir_block), offset);
371+
next = offset + 1;
373372
if (offset >= curr)
374373
ctx->pos = blkoff +
375374
next * sizeof(union afs_xdr_dirent);
@@ -381,35 +380,39 @@ static int afs_dir_iterate_block(struct afs_vnode *dvnode,
381380
nlen = strnlen(dire->u.name,
382381
sizeof(*block) -
383382
offset * sizeof(union afs_xdr_dirent));
383+
if (nlen > AFSNAMEMAX - 1) {
384+
_debug("ENT[%zu]: name too long (len %u/%zu)",
385+
blkoff / sizeof(union afs_xdr_dir_block),
386+
offset, nlen);
387+
return afs_bad(dvnode, afs_file_error_dir_name_too_long);
388+
}
384389

385390
_debug("ENT[%zu.%u]: %s %zu \"%s\"",
386391
blkoff / sizeof(union afs_xdr_dir_block), offset,
387392
(offset < curr ? "skip" : "fill"),
388393
nlen, dire->u.name);
389394

390-
/* work out where the next possible entry is */
391-
for (tmp = nlen; tmp > 15; tmp -= sizeof(union afs_xdr_dirent)) {
392-
if (next >= AFS_DIR_SLOTS_PER_BLOCK) {
393-
_debug("ENT[%zu.%u]:"
394-
" %u travelled beyond end dir block"
395-
" (len %u/%zu)",
396-
blkoff / sizeof(union afs_xdr_dir_block),
397-
offset, next, tmp, nlen);
398-
return afs_bad(dvnode, afs_file_error_dir_over_end);
399-
}
400-
if (!(block->hdr.bitmap[next / 8] &
401-
(1 << (next % 8)))) {
402-
_debug("ENT[%zu.%u]:"
403-
" %u unmarked extension (len %u/%zu)",
395+
nr_slots = afs_dir_calc_slots(nlen);
396+
next = offset + nr_slots;
397+
if (next > AFS_DIR_SLOTS_PER_BLOCK) {
398+
_debug("ENT[%zu.%u]:"
399+
" %u extends beyond end dir block"
400+
" (len %zu)",
401+
blkoff / sizeof(union afs_xdr_dir_block),
402+
offset, next, nlen);
403+
return afs_bad(dvnode, afs_file_error_dir_over_end);
404+
}
405+
406+
/* Check that the name-extension dirents are all allocated */
407+
for (tmp = 1; tmp < nr_slots; tmp++) {
408+
unsigned int ix = offset + tmp;
409+
if (!(block->hdr.bitmap[ix / 8] & (1 << (ix % 8)))) {
410+
_debug("ENT[%zu.u]:"
411+
" %u unmarked extension (%u/%u)",
404412
blkoff / sizeof(union afs_xdr_dir_block),
405-
offset, next, tmp, nlen);
413+
offset, tmp, nr_slots);
406414
return afs_bad(dvnode, afs_file_error_dir_unmarked_ext);
407415
}
408-
409-
_debug("ENT[%zu.%u]: ext %u/%zu",
410-
blkoff / sizeof(union afs_xdr_dir_block),
411-
next, tmp, nlen);
412-
next++;
413416
}
414417

415418
/* skip if starts before the current position */

fs/afs/dir_edit.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,7 @@ void afs_edit_dir_add(struct afs_vnode *vnode,
215215
}
216216

217217
/* Work out how many slots we're going to need. */
218-
need_slots = round_up(12 + name->len + 1 + 4, AFS_DIR_DIRENT_SIZE);
219-
need_slots /= AFS_DIR_DIRENT_SIZE;
218+
need_slots = afs_dir_calc_slots(name->len);
220219

221220
meta_page = kmap(page0);
222221
meta = &meta_page->blocks[0];
@@ -393,8 +392,7 @@ void afs_edit_dir_remove(struct afs_vnode *vnode,
393392
}
394393

395394
/* Work out how many slots we're going to discard. */
396-
need_slots = round_up(12 + name->len + 1 + 4, AFS_DIR_DIRENT_SIZE);
397-
need_slots /= AFS_DIR_DIRENT_SIZE;
395+
need_slots = afs_dir_calc_slots(name->len);
398396

399397
meta_page = kmap(page0);
400398
meta = &meta_page->blocks[0];

fs/afs/xdr_fs.h

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,16 @@ union afs_xdr_dirent {
5454
__be16 hash_next;
5555
__be32 vnode;
5656
__be32 unique;
57-
u8 name[16];
58-
u8 overflow[4]; /* if any char of the name (inc
59-
* NUL) reaches here, consume
60-
* the next dirent too */
57+
u8 name[];
58+
/* When determining the number of dirent slots needed to
59+
* represent a directory entry, name should be assumed to be 16
60+
* bytes, due to a now-standardised (mis)calculation, but it is
61+
* in fact 20 bytes in size. afs_dir_calc_slots() should be
62+
* used for this.
63+
*
64+
* For names longer than (16 or) 20 bytes, extra slots should
65+
* be annexed to this one using the extended_name format.
66+
*/
6167
} u;
6268
u8 extended_name[32];
6369
} __packed;
@@ -96,4 +102,15 @@ struct afs_xdr_dir_page {
96102
union afs_xdr_dir_block blocks[AFS_DIR_BLOCKS_PER_PAGE];
97103
};
98104

105+
/*
106+
* Calculate the number of dirent slots required for any given name length.
107+
* The calculation is made assuming the part of the name in the first slot is
108+
* 16 bytes, rather than 20, but this miscalculation is now standardised.
109+
*/
110+
static inline unsigned int afs_dir_calc_slots(size_t name_len)
111+
{
112+
name_len++; /* NUL-terminated */
113+
return 1 + ((name_len + 15) / AFS_DIR_DIRENT_SIZE);
114+
}
115+
99116
#endif /* XDR_FS_H */

include/trace/events/afs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ enum afs_file_error {
231231
afs_file_error_dir_bad_magic,
232232
afs_file_error_dir_big,
233233
afs_file_error_dir_missing_page,
234+
afs_file_error_dir_name_too_long,
234235
afs_file_error_dir_over_end,
235236
afs_file_error_dir_small,
236237
afs_file_error_dir_unmarked_ext,
@@ -488,6 +489,7 @@ enum afs_cb_break_reason {
488489
EM(afs_file_error_dir_bad_magic, "DIR_BAD_MAGIC") \
489490
EM(afs_file_error_dir_big, "DIR_BIG") \
490491
EM(afs_file_error_dir_missing_page, "DIR_MISSING_PAGE") \
492+
EM(afs_file_error_dir_name_too_long, "DIR_NAME_TOO_LONG") \
491493
EM(afs_file_error_dir_over_end, "DIR_ENT_OVER_END") \
492494
EM(afs_file_error_dir_small, "DIR_SMALL") \
493495
EM(afs_file_error_dir_unmarked_ext, "DIR_UNMARKED_EXT") \

0 commit comments

Comments
 (0)