On Wed, Apr 01, 2026, Theo de Raadt wrote:
> It feels like even if they are all all unsigned, the largest
> unsigned numbers may operate as 32-bit and wrap and therefore be
> smaller than s.st_size.

You are right that this is a risk.  On 32-bit platforms,
Elf32_Off is uint32_t.  Without the (off_t) cast, the sum
would stay 32-bit and wrap:

  e_shoff = 4294960000, e_shnum = 200, sizeof(Elf32_Shdr) = 40
  uint32: 4294960000 + 8000 = 704 (wrapped)
  704 < st_size -> check bypassed

The (off_t) cast in the check is specifically there to prevent
this.  Since off_t is int64_t on OpenBSD (even on 32-bit), the
cast forces the multiplication into 64-bit, and the addition
then promotes e_shoff to 64-bit as well:

  h.e_shoff + (off_t)h.e_shnum * sizeof(Elf_Shdr) > s.st_size

  1. (off_t)e_shnum: uint16_t -> int64_t
  2. int64_t * size_t -> int64_t (product, max ~4MB)
  3. uint32_t + int64_t -> int64_t (wider type, no wrap)
  4. int64_t > off_t: same type

I verified this on OpenBSD 7.8/arm64 with a test program that
demonstrates the wrap without the cast and the correct result
with it.

Check 1 (e_shoff >= st_size) does not catch this case by itself
because on 32-bit, e_shoff can be close to UINT32_MAX while
still being less than st_size (which is int64_t and can exceed
UINT32_MAX for large files).

Reply via email to