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,15 +180,30 @@ { hammer2_chain_t *chain; u_int bytes; + int badbref = 0; /* * 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 + * + * DF-0763: The on-disk radix is extracted from the low 6 bits of + * data_off and is attacker-controlled via a malicious filesystem + * image. A radix greater than HAMMER2_RADIX_MAX (16, i.e. 64KB + * == HAMMER2_PBUFSIZE) would compute a bytes value larger than + * the DIO page buffer, driving OOB heap reads/writes in + * chain_load_data()/chain_modify() and an assertion failure in + * hammer2_io_alloc(). Validate it here and mark the chain so + * chain_load_data() rejects it cleanly instead of OOB-ing. + */ + if ((int)(bref->data_off & HAMMER2_OFF_MASK_RADIX)) { + int radix = (int)(bref->data_off & HAMMER2_OFF_MASK_RADIX); + + bytes = 1U << radix; + if (radix > HAMMER2_RADIX_MAX) { + badbref = 1; + } + } else bytes = 0; switch(bref->type) { @@ -226,6 +241,10 @@ chain->refs = 1; chain->flags = HAMMER2_CHAIN_ALLOCATED; + /* DF-0763: flag out-of-range radix so chain_load_data() rejects it */ + if (badbref) + chain->error = HAMMER2_ERROR_BADBREF; + /* * Set the PFS boundary flag if this chain represents a PFS root. */ @@ -938,6 +957,15 @@ if ((chain->bref.data_off & ~HAMMER2_OFF_MASK_RADIX) == 0) return; + /* + * DF-0763: Reject chains whose on-disk radix exceeded + * HAMMER2_RADIX_MAX. chain_alloc() flags these as BADBREF; + * loading their data would drive OOB heap accesses in the IO + * layer (lsize > HAMMER2_PBUFSIZE). + */ + if (chain->error & HAMMER2_ERROR_BADBREF) + return; + hmp = chain->hmp; KKASSERT(hmp != NULL);