lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 39952362558f6e2ae1acdc78e966cc0f304ba2fa
parent 832523ed3652cec1dc9e657c84ffa22666375918
Author: triesap <tyson@radroots.org>
Date:   Sun,  9 Aug 2026 19:12:33 +0000

merge: reconcile host descriptor ownership

- Preserve service-hardening and descriptor-fix histories.
- Duplicate borrowed media descriptors without consuming hosts.
- Reject out-of-range values before unsafe descriptor borrowing.
- Verify mobile checks, tests, clippy, and architecture guards.

Diffstat:
MCargo.lock | 1+
MCargo.toml | 1+
Mcrates/mobile_ffi/Cargo.toml | 1+
Mcrates/mobile_ffi/src/dto.rs | 54+++++++++++++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -3607,6 +3607,7 @@ version = "0.1.0-alpha" dependencies = [ "async-trait", "hex", + "libc", "nostr 0.44.1", "nostr-relay-builder", "nostr-sdk 0.44.0", diff --git a/Cargo.toml b/Cargo.toml @@ -214,6 +214,7 @@ keyring = { version = "3.6.3", default-features = false, features = [ "linux-native-sync-persistent", "vendored", ] } +libc = "0.2" nostr = { version = "0.44.7", default-features = false } nostr-relay-pool = { version = "0.44.0" } nostr-sdk = { version = "0.44.1" } diff --git a/crates/mobile_ffi/Cargo.toml b/crates/mobile_ffi/Cargo.toml @@ -35,6 +35,7 @@ thiserror = { workspace = true } uniffi = { workspace = true, features = ["tokio"] } [target.'cfg(unix)'.dependencies] +libc = { workspace = true } rustix = { workspace = true } [dev-dependencies] diff --git a/crates/mobile_ffi/src/dto.rs b/crates/mobile_ffi/src/dto.rs @@ -1,6 +1,8 @@ //! Focused, versioned value types owned by the native boundary. #[cfg(unix)] +use std::os::fd::{FromRawFd, OwnedFd, RawFd}; +#[cfg(unix)] use std::os::unix::fs::FileExt; use radroots_blossom::{BlobDescriptor, MediaType, Sha256}; @@ -1115,8 +1117,21 @@ 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: `fcntl(F_DUPFD_CLOEXEC)` accepts any in-range integer descriptor + // and reports EBADF for an unavailable one. No borrowed or owned Rust + // descriptor is constructed until the kernel has duplicated it. + let duplicated = unsafe { libc::fcntl(raw_file_descriptor, libc::F_DUPFD_CLOEXEC, 0) }; + if duplicated < 0 { + return Err(RadrootsAppError::invalid_argument( + "media_handle_unavailable", + )); + } + // SAFETY: a nonnegative F_DUPFD_CLOEXEC result is a new descriptor owned by + // this call. The host's original descriptor remains independently owned. + let owned = unsafe { OwnedFd::from_raw_fd(duplicated) }; + let file = std::fs::File::from(owned); let metadata = file .metadata() .map_err(|_| RadrootsAppError::invalid_argument("media_handle_unavailable"))?; @@ -2182,6 +2197,43 @@ 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" + ); + } + + #[cfg(unix)] + #[test] + fn prepared_media_rejects_unavailable_in_range_file_descriptors() { + let bytes = png(2, 2); + let blossom = blossom_slot(); + let input = photo_input(i32::MAX as u64, &bytes, Sha256::digest(&bytes).to_hex()); + + assert_eq!( + input + .command_and_media(1_800_000_000, Some(&blossom)) + .expect_err("unavailable in-range descriptor") + .report() + .code, + "media_handle_unavailable" + ); } #[test]