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

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

+51 -31
+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];
+21 -4
fs/afs/xdr_fs.h
··· 54 54 __be16 hash_next; 55 55 __be32 vnode; 56 56 __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 + */ 61 67 } u; 62 68 u8 extended_name[32]; 63 69 } __packed; ··· 101 95 struct afs_xdr_dir_page { 102 96 union afs_xdr_dir_block blocks[AFS_DIR_BLOCKS_PER_PAGE]; 103 97 }; 98 + 99 + /* 100 + * Calculate the number of dirent slots required for any given name length. 101 + * The calculation is made assuming the part of the name in the first slot is 102 + * 16 bytes, rather than 20, but this miscalculation is now standardised. 103 + */ 104 + static inline unsigned int afs_dir_calc_slots(size_t name_len) 105 + { 106 + name_len++; /* NUL-terminated */ 107 + return 1 + ((name_len + 15) / AFS_DIR_DIRENT_SIZE); 108 + } 104 109 105 110 #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") \