Summary
Under the IPC grate, several tests print [3i|copy] range invalid: addr=0x…, len=144,
what="destination" and fail. 144 is sizeof(struct stat) on x86-64 Linux, i.e. the IPC grate's
cross-cage copy of a struct stat result buffer back into the calling cage is being rejected by
3i's destination-range validation.
Affected tests
cp/file-perm-race, dd/skip-seek-past-file, misc/cut, misc/wc, misc/shuf, misc/sort,
misc/readlink-fp-loop, misc/runcon-no-reorder, misc/nohup (~8–9 tests).
Reproduction
./run_default_tests.sh --grate ipc misc/wc
Output:
[3i|copy] range invalid: addr=0x775668800f80, len=144, what="destination"
wc: test a0 failed: exit status mismatch: expected 0, got 1
Root cause (traced)
The error comes from threei/src/threei.rs:759, inside _validate_range_rw, called from
copy_data_between_cages at threei.rs:948:
if let Err(code) = _validate_range_rw(destcage, destaddr, copy_len, "destination") {
return code;
}
_validate_range_rw → check_addr_rw(destcage, destaddr, 144) fails — the destination address (the
cage's struct stat buffer) is not recognized as readable+writable in destcage's vmmap.
len = 144 pins this to a struct stat copy (the timespec[2] buffer for futimens would be 32
bytes). So when the IPC grate handles fstat/stat/lstat and copies the kernel struct stat back to
the cage via copy_data_between_cages, the destcage / destaddr pair doesn't validate.
The bug
The grate's IPC fstat branch passes an already-host-translated pointer (arg2) into
copy_data_between_cages, whose destaddr parameter must be a guest address — the function does
its own vmmap-based validation/translation. The two layers disagree on the pointer convention.
(Note rawposix's own fstat_syscall sidesteps this by casting the host pointer directly via
sc_convert_addr_to_statdata — it never calls copy_data_between_cages.)
Fix options for the grate
- Simplest: since arg2 is already a valid host pointer into the cage's linear memory, skip
copy_data_between_cages entirely and write the 144 bytes directly:
unsafe { core::ptr::copy_nonoverlapping(buf.as_ptr(), arg2 as *mut u8, STAT_SIZE); }
- Or: if the grate must use copy_data_between_cages, it needs the guest statbuf pointer — which
means fstat shouldn't be pre-translated in glibc for the grate path, a much bigger change.
The direct-write fix is the right one and is consistent with how rawposix already handles the
statbuf. Worth auditing every other copy_data_between_cages call in the grate for the same
host/guest confusion (fstatfs_handler and any socket-addr copy-outs are prime suspects).
Impact
~8–9 IPC-grate failures.
Summary
Under the IPC grate, several tests print [3i|copy] range invalid: addr=0x…, len=144,
what="destination" and fail. 144 is sizeof(struct stat) on x86-64 Linux, i.e. the IPC grate's
cross-cage copy of a struct stat result buffer back into the calling cage is being rejected by
3i's destination-range validation.
Affected tests
cp/file-perm-race, dd/skip-seek-past-file, misc/cut, misc/wc, misc/shuf, misc/sort,
misc/readlink-fp-loop, misc/runcon-no-reorder, misc/nohup (~8–9 tests).
Reproduction
Root cause (traced)
The error comes from threei/src/threei.rs:759, inside _validate_range_rw, called from
copy_data_between_cages at threei.rs:948:
if let Err(code) = _validate_range_rw(destcage, destaddr, copy_len, "destination") {
return code;
}
_validate_range_rw → check_addr_rw(destcage, destaddr, 144) fails — the destination address (the
cage's struct stat buffer) is not recognized as readable+writable in destcage's vmmap.
len = 144 pins this to a struct stat copy (the timespec[2] buffer for futimens would be 32
bytes). So when the IPC grate handles fstat/stat/lstat and copies the kernel struct stat back to
the cage via copy_data_between_cages, the destcage / destaddr pair doesn't validate.
The bug
The grate's IPC fstat branch passes an already-host-translated pointer (arg2) into
copy_data_between_cages, whose destaddr parameter must be a guest address — the function does
its own vmmap-based validation/translation. The two layers disagree on the pointer convention.
(Note rawposix's own fstat_syscall sidesteps this by casting the host pointer directly via
sc_convert_addr_to_statdata — it never calls copy_data_between_cages.)
Fix options for the grate
copy_data_between_cages entirely and write the 144 bytes directly:
unsafe { core::ptr::copy_nonoverlapping(buf.as_ptr(), arg2 as *mut u8, STAT_SIZE); }
means fstat shouldn't be pre-translated in glibc for the grate path, a much bigger change.
The direct-write fix is the right one and is consistent with how rawposix already handles the
statbuf. Worth auditing every other copy_data_between_cages call in the grate for the same
host/guest confusion (fstatfs_handler and any socket-addr copy-outs are prime suspects).
Impact
~8–9 IPC-grate failures.