diff --git a/sys/vfs/hammer2/hammer2_chain.c b/sys/vfs/hammer2/hammer2_chain.c --- a/sys/vfs/hammer2/hammer2_chain.c +++ b/sys/vfs/hammer2/hammer2_chain.c @@ -938,6 +938,37 @@ if ((chain->bref.data_off & ~HAMMER2_OFF_MASK_RADIX) == 0) return; + /* + * Defense against a malicious/corrupted filesystem image (DF-2583): + * reject a chain whose on-disk radix (chain->bytes, derived from + * the low 6 bits of bref.data_off) implies a buffer larger than the + * hammer2 I/O layer can satisfy in a single DIO. HAMMER2_PBUFSIZE + * is the largest legal radix (HAMMER2_RADIX_MAX). An inflated radix + * otherwise drives two failure modes: + * (1) on INVARIANTS kernels, the next hammer2_io_bread() trips the + * KKASSERT in hammer2_io_alloc() / _hammer2_io_getblk() because + * the request spans multiple PBUFSIZE pages; + * (2) on non-INVARIANTS kernels, the KKASSERT is skipped and the + * oversized chain->bytes later drives an out-of-bounds + * read/write of the blockref array in hammer2_flush_core + * (count = parent->bytes / sizeof(hammer2_blockref_t)). + * Marking the chain errored lets callers (lookup, readdir, flush) + * handle it gracefully instead of panicking. The on-disk bref is + * left unmodified. + */ + if (chain->bytes > HAMMER2_PBUFSIZE) { + static int radix_warnonce; + if (radix_warnonce++ < 4) { + kprintf("hammer2_chain_load_data: %016jx: forged " + "radix (bytes=%u > HAMMER2_PBUFSIZE=%d); " + "rejecting chain\n", + (intmax_t)chain->bref.data_off, + chain->bytes, HAMMER2_PBUFSIZE); + } + chain->error = HAMMER2_ERROR_CHECK; + return; + } + hmp = chain->hmp; KKASSERT(hmp != NULL);