Skip to content

Commit ec7fdfb

Browse files
committed
fix(runtime): reject malformed capability arguments
Fail closed on signed/overflow/wrong-type host args, zero pagination limits, and unsupported readdir errno instead of coercing them.
1 parent e77f4cb commit ec7fdfb

5 files changed

Lines changed: 709 additions & 106 deletions

File tree

‎src/capabilities/confined_io.rs‎

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,7 @@ mod unix {
297297
clear_errno();
298298
let entry = unsafe { libc::readdir(guard.0) };
299299
if entry.is_null() {
300-
let errno = current_errno();
301-
if errno != 0 {
302-
return Err(map_io("fs::enumerate", io::Error::from_raw_os_error(errno)));
303-
}
300+
classify_readdir_end(errno_abi())?;
304301
break;
305302
}
306303
let name = unsafe { CStr::from_ptr((*entry).d_name.as_ptr()) };
@@ -370,16 +367,66 @@ mod unix {
370367
unsafe {
371368
*libc::__errno_location() = 0;
372369
}
370+
#[cfg(any(
371+
target_os = "dragonfly",
372+
target_os = "freebsd",
373+
target_os = "ios",
374+
target_os = "macos",
375+
target_os = "netbsd",
376+
target_os = "openbsd"
377+
))]
378+
unsafe {
379+
*libc::__error() = 0;
380+
}
373381
}
374382

375-
fn current_errno() -> i32 {
383+
enum ErrnoAbi {
384+
Known(i32),
385+
#[allow(dead_code)]
386+
Unsupported,
387+
}
388+
389+
fn errno_abi() -> ErrnoAbi {
376390
#[cfg(any(target_os = "linux", target_os = "android"))]
377391
{
378-
unsafe { *libc::__errno_location() }
379-
}
380-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
392+
ErrnoAbi::Known(unsafe { *libc::__errno_location() })
393+
}
394+
#[cfg(any(
395+
target_os = "dragonfly",
396+
target_os = "freebsd",
397+
target_os = "ios",
398+
target_os = "macos",
399+
target_os = "netbsd",
400+
target_os = "openbsd"
401+
))]
381402
{
382-
0
403+
ErrnoAbi::Known(unsafe { *libc::__error() })
404+
}
405+
#[cfg(not(any(
406+
target_os = "linux",
407+
target_os = "android",
408+
target_os = "dragonfly",
409+
target_os = "freebsd",
410+
target_os = "ios",
411+
target_os = "macos",
412+
target_os = "netbsd",
413+
target_os = "openbsd"
414+
)))]
415+
{
416+
ErrnoAbi::Unsupported
417+
}
418+
}
419+
420+
fn classify_readdir_end(errno: ErrnoAbi) -> Result<(), CapabilityError> {
421+
match errno {
422+
ErrnoAbi::Known(0) => Ok(()),
423+
ErrnoAbi::Known(code) => {
424+
Err(map_io("fs::enumerate", io::Error::from_raw_os_error(code)))
425+
}
426+
ErrnoAbi::Unsupported => Err(CapabilityError::new(
427+
"unsupported_platform",
428+
"readdir errno is unavailable on this target",
429+
)),
383430
}
384431
}
385432

@@ -567,4 +614,73 @@ mod unix {
567614
};
568615
CapabilityError::new(code, format!("{operation}: {error}"))
569616
}
617+
618+
#[cfg(test)]
619+
mod errno_tests {
620+
use super::*;
621+
622+
#[test]
623+
fn readdir_end_never_treats_unknown_errno_abi_as_eof() {
624+
assert!(classify_readdir_end(ErrnoAbi::Known(0)).is_ok());
625+
let error = classify_readdir_end(ErrnoAbi::Known(5))
626+
.expect_err("nonzero errno must not be treated as EOF");
627+
assert_ne!(error.code(), "unsupported_platform");
628+
let unsupported = classify_readdir_end(ErrnoAbi::Unsupported)
629+
.expect_err("missing errno ABI must fail closed");
630+
assert_eq!(unsupported.code(), "unsupported_platform");
631+
}
632+
633+
#[cfg(any(target_os = "linux", target_os = "android"))]
634+
#[test]
635+
fn linux_errno_location_clears_and_reads_zero() {
636+
clear_errno();
637+
assert!(matches!(errno_abi(), ErrnoAbi::Known(0)));
638+
}
639+
640+
#[cfg(any(
641+
target_os = "dragonfly",
642+
target_os = "freebsd",
643+
target_os = "ios",
644+
target_os = "macos",
645+
target_os = "netbsd",
646+
target_os = "openbsd"
647+
))]
648+
#[test]
649+
fn bsd_errno_error_clears_and_reads_zero() {
650+
clear_errno();
651+
assert!(matches!(errno_abi(), ErrnoAbi::Known(0)));
652+
}
653+
654+
#[test]
655+
fn current_target_does_not_report_unsupported_when_accessor_exists() {
656+
match errno_abi() {
657+
ErrnoAbi::Known(_) => {
658+
#[cfg(not(any(
659+
target_os = "linux",
660+
target_os = "android",
661+
target_os = "dragonfly",
662+
target_os = "freebsd",
663+
target_os = "ios",
664+
target_os = "macos",
665+
target_os = "netbsd",
666+
target_os = "openbsd"
667+
)))]
668+
panic!("unsupported Unix target must fail closed");
669+
}
670+
ErrnoAbi::Unsupported => {
671+
#[cfg(any(
672+
target_os = "linux",
673+
target_os = "android",
674+
target_os = "dragonfly",
675+
target_os = "freebsd",
676+
target_os = "ios",
677+
target_os = "macos",
678+
target_os = "netbsd",
679+
target_os = "openbsd"
680+
))]
681+
panic!("supported target must expose a real errno accessor");
682+
}
683+
}
684+
}
685+
}
570686
}

‎src/capabilities/filesystem.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ impl FilesystemCapability {
146146
limit: usize,
147147
) -> Result<FsRead, CapabilityError> {
148148
let _claims = self.authorize(token, CapabilityRisk::Read)?;
149+
if limit == 0 {
150+
return Err(CapabilityError::new(
151+
"invalid_request",
152+
"limit must be positive",
153+
));
154+
}
149155
if limit > self.limits.max_read_bytes {
150156
return Err(CapabilityError::new(
151157
"budget_exceeded",
@@ -184,6 +190,12 @@ impl FilesystemCapability {
184190
limit: usize,
185191
) -> Result<FsList, CapabilityError> {
186192
let _claims = self.authorize(token, CapabilityRisk::Read)?;
193+
if limit == 0 {
194+
return Err(CapabilityError::new(
195+
"invalid_request",
196+
"limit must be positive",
197+
));
198+
}
187199
if limit > self.limits.max_list_entries {
188200
return Err(CapabilityError::new(
189201
"budget_exceeded",

‎src/capabilities/process.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,12 @@ impl ProcessCapability {
244244
cursor: u64,
245245
limit: usize,
246246
) -> Result<ProcessSnapshot, CapabilityError> {
247+
if limit == 0 {
248+
return Err(CapabilityError::new(
249+
"invalid_request",
250+
"limit must be positive",
251+
));
252+
}
247253
let owned = self.lookup(token, handle)?;
248254
let _ = owned.handle.poll().map_err(map_process_error)?;
249255
Ok(snapshot(
@@ -284,6 +290,12 @@ impl ProcessCapability {
284290
cursor: u64,
285291
limit: usize,
286292
) -> Result<ProcessSnapshot, CapabilityError> {
293+
if limit == 0 {
294+
return Err(CapabilityError::new(
295+
"invalid_request",
296+
"limit must be positive",
297+
));
298+
}
287299
let owned = self.lookup(token, handle)?;
288300
Ok(snapshot(
289301
&owned.handle,

0 commit comments

Comments
 (0)