Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: 63a4681ff39c ("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>

+43 -28
+26 -23
fs/afs/dir.c
··· 350 350 unsigned blkoff) 351 351 { 352 352 union afs_xdr_dirent *dire; 353 - unsigned offset, next, curr; 353 + unsigned offset, next, curr, nr_slots; 354 354 size_t nlen; 355 355 int tmp; 356 356 ··· 363 363 offset < AFS_DIR_SLOTS_PER_BLOCK; 364 364 offset = next 365 365 ) { 366 - next = offset + 1; 367 - 368 366 /* skip entries marked unused in the bitmap */ 369 367 if (!(block->hdr.bitmap[offset / 8] & 370 368 (1 << (offset % 8)))) { 371 369 _debug("ENT[%zu.%u]: unused", 372 370 blkoff / sizeof(union afs_xdr_dir_block), offset); 371 + next = offset + 1; 373 372 if (offset >= curr) 374 373 ctx->pos = blkoff + 375 374 next * sizeof(union afs_xdr_dirent); ··· 380 381 nlen = strnlen(dire->u.name, 381 382 sizeof(*block) - 382 383 offset * sizeof(union afs_xdr_dirent)); 384 + if (nlen > AFSNAMEMAX - 1) { 385 + _debug("ENT[%zu]: name too long (len %u/%zu)", 386 + blkoff / sizeof(union afs_xdr_dir_block), 387 + offset, nlen); 388 + return afs_bad(dvnode, afs_file_error_dir_name_too_long); 389 + } 383 390 384 391 _debug("ENT[%zu.%u]: %s %zu \"%s\"", 385 392 blkoff / sizeof(union afs_xdr_dir_block), offset, 386 393 (offset < curr ? "skip" : "fill"), 387 394 nlen, dire->u.name); 388 395 389 - /* work out where the next possible entry is */ 390 - for (tmp = nlen; tmp > 15; tmp -= sizeof(union afs_xdr_dirent)) { 391 - if (next >= AFS_DIR_SLOTS_PER_BLOCK) { 392 - _debug("ENT[%zu.%u]:" 393 - " %u travelled beyond end dir block" 394 - " (len %u/%zu)", 396 + nr_slots = afs_dir_calc_slots(nlen); 397 + next = offset + nr_slots; 398 + if (next > AFS_DIR_SLOTS_PER_BLOCK) { 399 + _debug("ENT[%zu.%u]:" 400 + " %u extends beyond end dir block" 401 + " (len %zu)", 402 + blkoff / sizeof(union afs_xdr_dir_block), 403 + offset, next, nlen); 404 + return afs_bad(dvnode, afs_file_error_dir_over_end); 405 + } 406 + 407 + /* Check that the name-extension dirents are all allocated */ 408 + for (tmp = 1; tmp < nr_slots; tmp++) { 409 + unsigned int ix = offset + tmp; 410 + if (!(block->hdr.bitmap[ix / 8] & (1 << (ix % 8)))) { 411 + _debug("ENT[%zu.u]:" 412 + " %u unmarked extension (%u/%u)", 395 413 blkoff / sizeof(union afs_xdr_dir_block), 396 - offset, next, tmp, nlen); 397 - return afs_bad(dvnode, afs_file_error_dir_over_end); 398 - } 399 - if (!(block->hdr.bitmap[next / 8] & 400 - (1 << (next % 8)))) { 401 - _debug("ENT[%zu.%u]:" 402 - " %u unmarked extension (len %u/%zu)", 403 - blkoff / sizeof(union afs_xdr_dir_block), 404 - offset, next, tmp, nlen); 414 + offset, tmp, nr_slots); 405 415 return afs_bad(dvnode, afs_file_error_dir_unmarked_ext); 406 416 } 407 - 408 - _debug("ENT[%zu.%u]: ext %u/%zu", 409 - blkoff / sizeof(union afs_xdr_dir_block), 410 - next, tmp, nlen); 411 - next++; 412 417 } 413 418 414 419 /* skip if starts before the current position */
+2 -4
fs/afs/dir_edit.c
··· 215 215 } 216 216 217 217 /* 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); 220 219 221 220 meta_page = kmap(page0); 222 221 meta = &meta_page->blocks[0]; ··· 392 393 } 393 394 394 395 /* Work out how many slots we're going to discard. */ 395 - need_slots = round_up(12 + name->len + 1 + 4, AFS_DIR_DIRENT_SIZE); 396 - need_slots /= AFS_DIR_DIRENT_SIZE; 396 + need_slots = afs_dir_calc_slots(name->len); 397 397 398 398 meta_page = kmap(page0); 399 399 meta = &meta_page->blocks[0];
+13 -1
fs/afs/xdr_fs.h
··· 58 58 /* When determining the number of dirent slots needed to 59 59 * represent a directory entry, name should be assumed to be 16 60 60 * 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. 62 63 * 63 64 * For names longer than (16 or) 20 bytes, extra slots should 64 65 * be annexed to this one using the extended_name format. ··· 101 100 struct afs_xdr_dir_page { 102 101 union afs_xdr_dir_block blocks[AFS_DIR_BLOCKS_PER_PAGE]; 103 102 }; 103 + 104 + /* 105 + * Calculate the number of dirent slots required for any given name length. 106 + * The calculation is made assuming the part of the name in the first slot is 107 + * 16 bytes, rather than 20, but this miscalculation is now standardised. 108 + */ 109 + static inline unsigned int afs_dir_calc_slots(size_t name_len) 110 + { 111 + name_len++; /* NUL-terminated */ 112 + return 1 + ((name_len + 15) / AFS_DIR_DIRENT_SIZE); 113 + } 104 114 105 115 #endif /* XDR_FS_H */
+2
include/trace/events/afs.h
··· 231 231 afs_file_error_dir_bad_magic, 232 232 afs_file_error_dir_big, 233 233 afs_file_error_dir_missing_page, 234 + afs_file_error_dir_name_too_long, 234 235 afs_file_error_dir_over_end, 235 236 afs_file_error_dir_small, 236 237 afs_file_error_dir_unmarked_ext, ··· 489 488 EM(afs_file_error_dir_bad_magic, "DIR_BAD_MAGIC") \ 490 489 EM(afs_file_error_dir_big, "DIR_BIG") \ 491 490 EM(afs_file_error_dir_missing_page, "DIR_MISSING_PAGE") \ 491 + EM(afs_file_error_dir_name_too_long, "DIR_NAME_TOO_LONG") \ 492 492 EM(afs_file_error_dir_over_end, "DIR_ENT_OVER_END") \ 493 493 EM(afs_file_error_dir_small, "DIR_SMALL") \ 494 494 EM(afs_file_error_dir_unmarked_ext, "DIR_UNMARKED_EXT") \