use std::collections::BTreeMap; use std::sync::Arc; use log::warn; use redox_scheme::scheme::SchemeSync; use redox_scheme::{CallerCtx, OpenResult}; use syscall::error::*; use syscall::schemev2::NewFdFlags; use syscall::{EventFlags, Stat, MODE_FILE}; use crate::blob::FirmwareRegistry; const SCHEME_ROOT_ID: usize = 1; struct Handle { blob_key: String, data: Arc>, map_count: usize, closed: bool, } pub struct FirmwareScheme { registry: FirmwareRegistry, next_id: usize, handles: BTreeMap, } impl FirmwareScheme { pub fn new(registry: FirmwareRegistry) -> Self { FirmwareScheme { registry, next_id: SCHEME_ROOT_ID + 1, handles: BTreeMap::new(), } } fn handle(&self, id: usize) -> Result<&Handle> { self.handles.get(&id).ok_or(Error::new(EBADF)) } fn handle_mut(&mut self, id: usize) -> Result<&mut Handle> { self.handles.get_mut(&id).ok_or(Error::new(EBADF)) } } fn resolve_key(path: &str) -> Option { let cleaned = path.trim_matches('/'); if cleaned.is_empty() || cleaned.ends_with('/') { return None; } // Reject path traversal attempts — only allow safe characters if cleaned.starts_with('.') || cleaned.contains("..") { log::warn!( "firmware-loader: rejecting path traversal in key: {:?}", path ); return None; } let key = if cleaned.ends_with(".bin") { cleaned.trim_end_matches(".bin").to_string() } else { cleaned.to_string() }; // Final sanity: key must be purely alphanumeric with /, -, _ if !key .chars() .all(|c| c.is_alphanumeric() || c == '/' || c == '-' || c == '_') { log::warn!( "firmware-loader: rejecting invalid characters in key: {:?}", key ); return None; } Some(key) } impl SchemeSync for FirmwareScheme { fn scheme_root(&mut self) -> Result { Ok(SCHEME_ROOT_ID) } fn openat( &mut self, dirfd: usize, path: &str, _flags: usize, _fcntl_flags: u32, _ctx: &CallerCtx, ) -> Result { if dirfd != SCHEME_ROOT_ID { return Err(Error::new(EACCES)); } let key = resolve_key(path).ok_or(Error::new(EISDIR))?; if !self.registry.contains(&key) { warn!("firmware-loader: firmware not found: {}", path); return Err(Error::new(ENOENT)); } let data = self.registry.load(&key).map_err(|e| { warn!("firmware-loader: failed to load firmware '{}': {}", key, e); Error::new(ENOENT) })?; let id = self.next_id; self.next_id += 1; self.handles.insert( id, Handle { blob_key: key, data, map_count: 0, closed: false, }, ); Ok(OpenResult::ThisScheme { number: id, flags: NewFdFlags::empty(), }) } fn read( &mut self, id: usize, buf: &mut [u8], offset: u64, _flags: u32, _ctx: &CallerCtx, ) -> Result { let handle = self.handle(id)?; let offset = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?; let data = &handle.data; if offset >= data.len() { return Ok(0); } let available = data.len() - offset; let to_copy = available.min(buf.len()); buf[..to_copy].copy_from_slice(&data[offset..offset + to_copy]); Ok(to_copy) } fn write( &mut self, id: usize, _buf: &[u8], _offset: u64, _flags: u32, _ctx: &CallerCtx, ) -> Result { let _ = self.handle(id)?; Err(Error::new(EROFS)) } fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { let handle = self.handle(id)?; let path = format!("firmware:/{}.bin", handle.blob_key); let bytes = path.as_bytes(); let len = bytes.len().min(buf.len()); buf[..len].copy_from_slice(&bytes[..len]); Ok(len) } fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> { let handle = self.handle(id)?; stat.st_mode = MODE_FILE | 0o444; stat.st_size = handle.data.len() as u64; stat.st_blksize = 4096; stat.st_blocks = (handle.data.len() as u64 + 511) / 512; stat.st_nlink = 1; Ok(()) } fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> { let _ = self.handle(id)?; Ok(()) } fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result { let _ = self.handle(id)?; Ok(0) } fn fsize(&mut self, id: usize, _ctx: &CallerCtx) -> Result { let handle = self.handle(id)?; Ok(handle.data.len() as u64) } fn ftruncate(&mut self, id: usize, _len: u64, _ctx: &CallerCtx) -> Result<()> { let _ = self.handle(id)?; Err(Error::new(EROFS)) } fn fevent(&mut self, id: usize, _flags: EventFlags, _ctx: &CallerCtx) -> Result { let _ = self.handle(id)?; Ok(EventFlags::empty()) } fn mmap_prep( &mut self, id: usize, offset: u64, size: usize, _flags: syscall::MapFlags, _ctx: &CallerCtx, ) -> Result { let handle = self.handle_mut(id)?; let data_len = handle.data.len() as u64; if offset > data_len { return Err(Error::new(EINVAL)); } if offset + size as u64 > data_len { return Err(Error::new(EINVAL)); } let ptr = &handle.data[offset as usize] as *const u8; handle.map_count += 1; Ok(ptr as usize) } fn munmap( &mut self, id: usize, _offset: u64, _size: usize, _flags: syscall::MunmapFlags, _ctx: &CallerCtx, ) -> Result<()> { let handle = self.handle_mut(id)?; if handle.map_count > 0 { handle.map_count -= 1; } let should_cleanup = handle.closed && handle.map_count == 0; if should_cleanup { self.handles.remove(&id); } Ok(()) } fn on_close(&mut self, id: usize) { if id == SCHEME_ROOT_ID { return; } if let Some(handle) = self.handles.get_mut(&id) { handle.closed = true; let should_remove = handle.map_count == 0; if should_remove { self.handles.remove(&id); } } } }