Skip to content

Commit 366911c

Browse files
committed
afs: Fix directory entry size calculation
The number of dirent records used by an AFS directory entry should be calculated using the assumption that there is a 16-byte name field in the first block, rather than a 20-byte name field (which is actually the case). This miscalculation is historic and effectively standard, so we have to use it. The calculation we need to use is: 1 + (((strlen(name) + 1) + 15) >> 5) where we are adding one to the strlen() result to account for the NUL termination. Fix this by the following means: (1) Create an inline function to do the calculation for a given name length. (2) Use the function to calculate the number of records used for a dirent in afs_dir_iterate_block(). Use this to move the over-end check out of the loop since it only needs to be done once. Further use this to only go through the loop for the 2nd+ records composing an entry. The only test there now is for if the record is allocated - and we already checked the first block at the top of the outer loop. (3) Add a max name length check in afs_dir_iterate_block(). (4) Make afs_edit_dir_add() and afs_edit_dir_remove() use the function from (1) to calculate the number of blocks rather than doing it incorrectly themselves. Fixes: 63a4681 ("afs: Locally edit directory data for mkdir/create/unlink/...") Fixes: ^1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: David Howells <dhowells@redhat.com> Tested-by: Marc Dionne <marc.dionne@auristor.com>
1 parent 26982a8 commit 366911c

4 files changed

Lines changed: 43 additions & 28 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: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ union afs_xdr_dirent {
5858
/* When determining the number of dirent slots needed to
5959
* represent a directory entry, name should be assumed to be 16
6060
* bytes, due to a now-standardised (mis)calculation, but it is
61-
* in fact 20 bytes in size.
61+
* in fact 20 bytes in size. afs_dir_calc_slots() should be
62+
* used for this.
6263
*
6364
* For names longer than (16 or) 20 bytes, extra slots should
6465
* be annexed to this one using the extended_name format.
@@ -101,4 +102,15 @@ struct afs_xdr_dir_page {
101102
union afs_xdr_dir_block blocks[AFS_DIR_BLOCKS_PER_PAGE];
102103
};
103104

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+
104116
#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)