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 @@ -180,16 +180,42 @@ { hammer2_chain_t *chain; u_int bytes; + int radix; /* * Special case - radix of 0 indicates a chain that does not * need a data reference (context is completely embedded in the * bref). - */ - if ((int)(bref->data_off & HAMMER2_OFF_MASK_RADIX)) - bytes = 1U << (int)(bref->data_off & HAMMER2_OFF_MASK_RADIX); - else + * + * Validate the radix (DF-2605): the low 6 bits of data_off are + * attacker-controlled on a mounted image. A radix greater than + * HAMMER2_RADIX_MAX (16) yields bytes > HAMMER2_PBUFSIZE which the + * I/O layer cannot satisfy in a single DIO (KKASSERT panic in + * hammer2_io_alloc() / _hammer2_io_getblk()); a radix >= 32 also + * makes `1U << radix` undefined behavior (shift count >= type + * width). Cap bytes at the legal maximum so the shift is always + * defined and the value is always I/O-safe, and mark the chain + * errored so hammer2_chain_load_data() rejects it before any I/O. + * The on-disk bref is left unmodified. + */ + radix = (int)(bref->data_off & HAMMER2_OFF_MASK_RADIX); + if (radix) { + if (radix > HAMMER2_RADIX_MAX) { + static int radix_warnonce; + if (radix_warnonce++ < 4) { + kprintf("hammer2_chain_alloc: %016jx: forged " + "radix=%d (max=%d); capping and " + "marking chain errored\n", + (intmax_t)bref->data_off, radix, + HAMMER2_RADIX_MAX); + } + bytes = 1U << HAMMER2_RADIX_MAX; + } else { + bytes = 1U << radix; + } + } else { bytes = 0; + } switch(bref->type) { case HAMMER2_BREF_TYPE_INODE: @@ -227,6 +253,13 @@ chain->flags = HAMMER2_CHAIN_ALLOCATED; /* + * A forged/oversized radix (above) is recorded on the chain so that + * hammer2_chain_load_data() rejects it before issuing any I/O. + */ + if (radix > HAMMER2_RADIX_MAX) + chain->error = HAMMER2_ERROR_CHECK; + + /* * Set the PFS boundary flag if this chain represents a PFS root. */ if (bref->flags & HAMMER2_BREF_FLAG_PFSROOT) @@ -938,6 +971,18 @@ if ((chain->bref.data_off & ~HAMMER2_OFF_MASK_RADIX) == 0) return; + /* + * Reject a chain whose on-disk radix was forged/corrupted beyond the + * legal maximum (DF-2605 / DF-2583). hammer2_chain_alloc() caps the + * (potentially UB) bytes derivation and marks such chains errored; + * bailing here before hammer2_io_bread() avoids the oversized I/O + * that would otherwise KKASSERT in hammer2_io_alloc() on INVARIANTS + * kernels, and the OOB blockref-array walk in hammer2_flush_core() + * on non-INVARIANTS kernels. + */ + if (chain->error || chain->bytes > HAMMER2_PBUFSIZE) + return; + hmp = chain->hmp; KKASSERT(hmp != NULL);