commit eb751990e3568896a913fc7366e17f92279216c6
parent 51ea1582d9c4751892cc375056aea0a678b5c8d3
Author: triesap <tyson@radroots.org>
Date: Sun, 9 Aug 2026 18:56:38 +0000
mobile_ffi: duplicate host media descriptors
- avoid /dev/fd path reliance on sandboxed Unix hosts
- preserve caller descriptor ownership through immediate duplication
- reject descriptor values outside the platform range
- verify formatting, checks, clippy, and focused tests
Diffstat:
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/core/crates/tera_ffi/src/dto.rs b/core/crates/tera_ffi/src/dto.rs
@@ -2,6 +2,8 @@
#[cfg(unix)]
use std::os::unix::fs::FileExt;
+#[cfg(unix)]
+use std::os::{fd::BorrowedFd, unix::io::RawFd};
use radroots_blossom::{BlobDescriptor, MediaType, Sha256};
use radroots_event::{
@@ -1115,8 +1117,15 @@ fn read_media_file_descriptor(
expected_size: u64,
byte_size: usize,
) -> Result<Vec<u8>, RadrootsAppError> {
- let file = std::fs::File::open(format!("/dev/fd/{file_descriptor}"))
+ let raw_file_descriptor = RawFd::try_from(file_descriptor)
+ .map_err(|_| RadrootsAppError::invalid_argument("media_handle_unavailable"))?;
+ // SAFETY: the host owns this descriptor for the duration of the synchronous
+ // FFI call. Duplicating it immediately gives Rust independent ownership
+ // without closing or otherwise consuming the host descriptor.
+ let borrowed = unsafe { BorrowedFd::borrow_raw(raw_file_descriptor) };
+ let owned = rustix::io::dup(borrowed)
.map_err(|_| RadrootsAppError::invalid_argument("media_handle_unavailable"))?;
+ let file = std::fs::File::from(owned);
let metadata = file
.metadata()
.map_err(|_| RadrootsAppError::invalid_argument("media_handle_unavailable"))?;
@@ -2182,6 +2191,26 @@ mod tests {
media[0].url(),
format!("http://127.0.0.1:3000/{digest}.png")
);
+ assert_eq!(
+ file.as_file().metadata().expect("caller-owned media").len(),
+ bytes.len() as u64
+ );
+ }
+
+ #[test]
+ fn prepared_media_rejects_file_descriptors_outside_the_platform_range() {
+ let bytes = png(2, 2);
+ let blossom = blossom_slot();
+ let input = photo_input(u64::MAX, &bytes, Sha256::digest(&bytes).to_hex());
+
+ assert_eq!(
+ input
+ .command_and_media(1_800_000_000, Some(&blossom))
+ .expect_err("out-of-range descriptor")
+ .report()
+ .code,
+ "media_handle_unavailable"
+ );
}
#[test]