Skip to content

Integer overflow vulnerabilities: unchecked arithmetic on guest-derived usize values #5

@github-actions

Description

@github-actions

Summary

A scan of the codebase found unchecked arithmetic operations on usize values derived from untrusted guest input. These can cause panics in debug builds (via Rust's overflow checks) when a malicious guest supplies crafted values.


Findings

1. 🔴 HIGH: Underflow in socket address write — litebox_shim_linux/src/syscalls/net.rs:1150

Code:

UnixSocketAddr::Path(path) => {
    let offset = offset_of!(CSockUnixAddr, path);
    let max_len = addrlen_val as usize - offset;   // ← no guard

Issue: addrlen_val is read directly from a guest-provided *addrlen pointer (line 1106). offset is the compile-time constant offset_of!(CSockUnixAddr, path) (typically 2 on all platforms). If the guest sets the addrlen value to 0 or 1, the subtraction addrlen_val as usize - offset underflows and panics in debug builds.

Note the inconsistency: the Abstract variant above (lines 1136–1145) correctly guards with if addrlen_val as usize > offset { ... } before a similar subtraction, but the Path variant lacks this guard entirely.

Severity: High — directly triggered by guest-supplied addrlen value.

Fix:

let max_len = (addrlen_val as usize).saturating_sub(offset);
// or add a guard:
if addrlen_val as usize <= offset {
    return Ok(());
}
let max_len = addrlen_val as usize - offset;

2. 🔴 HIGH: Overflow in robust list futex address calculation — litebox_shim_linux/src/syscalls/process.rs:478 and :494

Code:

// line 470: both values read from guest memory via head.read_at_offset(0)
let futex_offset = head.futex_offset;   // guest-controlled usize

// line 478
handle_futex_death(
    crate::ConstPtr::from_usize(entry.as_usize() + futex_offset),  // ← overflow
    ...
)?;

// line 494
handle_futex_death(
    crate::ConstPtr::from_usize(pending.as_usize() + futex_offset), // ← overflow
    ...
)?;

Issue: Both entry (from head.list.next) and futex_offset (from head.futex_offset) are read from the guest's RobustListHead structure, which is set by the guest via set_robust_list. The guest can set entry to a near-MAX address and futex_offset to any value, making the sum overflow and panic in debug builds.

Note: In the Linux kernel, futex_offset is a signed long (it can be negative) but is declared here as usize, which is an additional semantic concern.

Severity: High — both operands are directly from guest-controlled memory.

Fix:

// Use wrapping_add to avoid debug panic; the resulting pointer will be
// validated by the read/write access check inside handle_futex_death.
crate::ConstPtr::from_usize(entry.as_usize().wrapping_add(futex_offset))
crate::ConstPtr::from_usize(pending.as_usize().wrapping_add(futex_offset))

3. 🟡 MEDIUM: Overflow in argv/envp pointer advancement — litebox_shim_linux/src/syscalls/process.rs:1314

Code:

// base is a ConstPtr into user space (argv/envp array)
base = crate::ConstPtr::from_usize(base.as_usize() + core::mem::size_of::(usize)());

Issue: base is a guest-provided pointer to the argv or envp array. If the current base value is within size_of::(usize)() bytes of usize::MAX, incrementing it overflows and panics in debug builds. The resulting wrapped pointer would then be read on the next loop iteration.

Severity: Medium — the guest controls the initial pointer value, but triggering this requires placing the pointer at an extreme address.

Fix:

base = crate::ConstPtr::from_usize(base.as_usize().wrapping_add(core::mem::size_of::(usize)()));

4. 🟡 MEDIUM: Unchecked arithmetic in ELF loader — litebox_common_linux/src/loader.rs:420–423

Code:

// p_vaddr, p_filesz, p_memsz come from ELF program headers (guest binary)
// base_addr comes from mapper.reserve() for ET_DYN
let adjusted_vaddr = base_addr + p_vaddr;          // ← no combined overflow check
let file_end = page_align_up(adjusted_vaddr + p_filesz);   // ← no check
let load_end = page_align_up(adjusted_vaddr + p_memsz);    // ← no check

Issue: Lines 409–414 validate that p_vaddr + p_memsz does not overflow in isolation, but do not check for overflow when base_addr is added. For ET_DYN binaries (where base_addr > 0 is chosen by the allocator), a carefully crafted ELF with a large p_vaddr could overflow base_addr + p_vaddr. The subsequent additions of p_filesz and p_memsz can compound this.

Severity: Medium — triggered by a malicious ELF binary; base_addr = 0 for ET_EXEC so only affects dynamically-linked (ET_DYN) executables.

Fix:

let adjusted_vaddr = base_addr.checked_add(p_vaddr).ok_or(ElfLoadError::InvalidProgramHeader)?;
let file_end = adjusted_vaddr.checked_add(p_filesz).map(page_align_up).ok_or(ElfLoadError::InvalidProgramHeader)?;
let load_end = adjusted_vaddr.checked_add(p_memsz).map(page_align_up).ok_or(ElfLoadError::InvalidProgramHeader)?;

Non-Issues (False Positives Investigated)

The following patterns were examined and found to be safe:

  • signal/mod.rs:266 and :353 (stack.sp + stack.size): set_sigaltstack validates this sum with sp.checked_add(size).is_none() at line 312 before storing; the stored values are safe to add.
  • ldelf.rs:71,81,206,252: Protected by checked_add validations for total_size before the mmap call; the mmap-returned address is kernel-allocated far from usize::MAX.
  • signal/x86.rs and signal/x86_64.rs: All frame address arithmetic already uses wrapping_sub/wrapping_add — correctly hardened.

Background

This type of vulnerability is documented in the repository's memory as a known class: wrapping arithmetic (wrapping_sub, wrapping_add) should be used instead of direct operators on usize values derived from untrusted guest registers or guest-provided memory structures, to prevent debug-mode overflow panics.

Generated by Integer Overflow Scanner

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions