| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::mm::barrier::{BarrierType, system_barrier}; |
| use crate::mm::debugger::notify_debugger_of_module_list; |
| use crate::mm::{ |
| DesiredAddress, FutexKey, IOVecPtr, MappingName, MappingOptions, MembarrierType, |
| MemoryAccessorExt, MremapFlags, PAGE_SIZE, PrivateFutexKey, ProtectionFlags, SharedFutexKey, |
| }; |
| use crate::security; |
| use crate::syscalls::time::TimeSpecPtr; |
| use crate::task::{CurrentTask, TargetTime, Task}; |
| use crate::time::utc::estimate_boot_deadline_from_utc; |
| use crate::vfs::buffers::{OutputBuffer, UserBuffersInputBuffer, UserBuffersOutputBuffer}; |
| use crate::vfs::{FdFlags, FdNumber, UserFaultFile}; |
| use fuchsia_runtime::UtcTimeline; |
| use linux_uapi::MLOCK_ONFAULT; |
| use starnix_logging::{CATEGORY_STARNIX_MM, log_trace, trace_duration, track_stub}; |
| use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked}; |
| use starnix_syscalls::SyscallArg; |
| use starnix_types::time::{duration_from_timespec, time_from_timespec, timespec_from_time}; |
| use starnix_uapi::auth::{CAP_SYS_PTRACE, PTRACE_MODE_ATTACH_REALCREDS}; |
| use starnix_uapi::errors::{EINTR, Errno}; |
| use starnix_uapi::open_flags::OpenFlags; |
| use starnix_uapi::user_address::{UserAddress, UserRef}; |
| use starnix_uapi::user_value::UserValue; |
| use starnix_uapi::{ |
| FUTEX_BITSET_MATCH_ANY, FUTEX_CLOCK_REALTIME, FUTEX_CMD_MASK, FUTEX_CMP_REQUEUE, |
| FUTEX_CMP_REQUEUE_PI, FUTEX_LOCK_PI, FUTEX_LOCK_PI2, FUTEX_PRIVATE_FLAG, FUTEX_REQUEUE, |
| FUTEX_TRYLOCK_PI, FUTEX_UNLOCK_PI, FUTEX_WAIT, FUTEX_WAIT_BITSET, FUTEX_WAIT_REQUEUE_PI, |
| FUTEX_WAKE, FUTEX_WAKE_BITSET, FUTEX_WAKE_OP, MAP_ANONYMOUS, MAP_DENYWRITE, MAP_FIXED, |
| MAP_FIXED_NOREPLACE, MAP_GROWSDOWN, MAP_LOCKED, MAP_NORESERVE, MAP_POPULATE, MAP_PRIVATE, |
| MAP_SHARED, MAP_SHARED_VALIDATE, MAP_STACK, MS_INVALIDATE, O_CLOEXEC, O_NONBLOCK, PROT_EXEC, |
| UFFD_USER_MODE_ONLY, errno, error, robust_list_head, tid_t, uapi, |
| }; |
| use std::ops::Deref as _; |
| use zx; |
| |
| #[cfg(target_arch = "x86_64")] |
| use starnix_uapi::MAP_32BIT; |
| |
| // Returns any platform-specific mmap flags. This is a separate function because as of this writing |
| // "attributes on expressions are experimental." |
| #[cfg(target_arch = "x86_64")] |
| fn get_valid_platform_mmap_flags() -> u32 { |
| MAP_32BIT |
| } |
| #[cfg(not(target_arch = "x86_64"))] |
| fn get_valid_platform_mmap_flags() -> u32 { |
| 0 |
| } |
| |
| /// sys_mmap takes a mutable reference to current_task because it may modify the IP register. |
| pub fn sys_mmap( |
| locked: &mut Locked<Unlocked>, |
| current_task: &mut CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| prot: u32, |
| flags: u32, |
| fd: FdNumber, |
| offset: u64, |
| ) -> Result<UserAddress, Errno> { |
| let user_address = do_mmap(locked, current_task, addr, length, prot, flags, fd, offset)?; |
| if prot & PROT_EXEC != 0 { |
| // Possibly loads a new module. Notify debugger for the change. |
| // We only care about dynamic linker loading modules for now, which uses mmap. In the future |
| // we might want to support unloading modules in munmap or JIT compilation in mprotect. |
| notify_debugger_of_module_list(current_task)?; |
| } |
| Ok(user_address) |
| } |
| |
| pub fn do_mmap<L>( |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| prot: u32, |
| flags: u32, |
| fd: FdNumber, |
| offset: u64, |
| ) -> Result<UserAddress, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| let prot_flags = ProtectionFlags::from_access_bits(prot).ok_or_else(|| { |
| track_stub!(TODO("https://fxbug.dev/322874211"), "mmap parse protection", prot); |
| errno!(EINVAL) |
| })?; |
| |
| let valid_flags: u32 = get_valid_platform_mmap_flags() |
| | MAP_PRIVATE |
| | MAP_SHARED |
| | MAP_SHARED_VALIDATE |
| | MAP_ANONYMOUS |
| | MAP_FIXED |
| | MAP_FIXED_NOREPLACE |
| | MAP_POPULATE |
| | MAP_NORESERVE |
| | MAP_STACK |
| | MAP_DENYWRITE |
| | MAP_GROWSDOWN |
| | MAP_LOCKED; |
| if flags & !valid_flags != 0 { |
| if flags & MAP_SHARED_VALIDATE != 0 { |
| return error!(EOPNOTSUPP); |
| } |
| track_stub!(TODO("https://fxbug.dev/322873638"), "mmap check flags", flags); |
| return error!(EINVAL); |
| } |
| |
| let file = if flags & MAP_ANONYMOUS != 0 { None } else { Some(current_task.files.get(fd)?) }; |
| if flags & (MAP_PRIVATE | MAP_SHARED) == 0 |
| || flags & (MAP_PRIVATE | MAP_SHARED) == MAP_PRIVATE | MAP_SHARED |
| { |
| return error!(EINVAL); |
| } |
| if length == 0 { |
| return error!(EINVAL); |
| } |
| if offset % *PAGE_SIZE != 0 { |
| return error!(EINVAL); |
| } |
| |
| // TODO(tbodt): should we consider MAP_NORESERVE? |
| |
| let addr = match (addr, flags & MAP_FIXED != 0, flags & MAP_FIXED_NOREPLACE != 0) { |
| (UserAddress::NULL, false, false) => DesiredAddress::Any, |
| (UserAddress::NULL, true, _) | (UserAddress::NULL, _, true) => return error!(EINVAL), |
| (addr, false, false) => DesiredAddress::Hint(addr), |
| (addr, _, true) => DesiredAddress::Fixed(addr), |
| (addr, true, false) => DesiredAddress::FixedOverwrite(addr), |
| }; |
| |
| let memory_offset = if flags & MAP_ANONYMOUS != 0 { 0 } else { offset }; |
| |
| let mut options = MappingOptions::empty(); |
| if flags & MAP_SHARED != 0 { |
| options |= MappingOptions::SHARED; |
| } |
| if flags & MAP_ANONYMOUS != 0 { |
| options |= MappingOptions::ANONYMOUS; |
| } |
| #[cfg(target_arch = "x86_64")] |
| if flags & MAP_FIXED == 0 && flags & MAP_32BIT != 0 { |
| options |= MappingOptions::LOWER_32BIT; |
| } |
| if flags & MAP_GROWSDOWN != 0 { |
| options |= MappingOptions::GROWSDOWN; |
| } |
| if flags & MAP_POPULATE != 0 { |
| options |= MappingOptions::POPULATE; |
| } |
| if flags & MAP_LOCKED != 0 { |
| // The kernel isn't expected to return an error if locking fails with this flag, so for now |
| // this implementation will always fail to lock memory even if mapping succeeds. |
| track_stub!(TODO("https://fxbug.dev/406377606"), "MAP_LOCKED"); |
| } |
| |
| security::mmap_file(current_task, file.as_ref(), prot_flags, options)?; |
| |
| if flags & MAP_ANONYMOUS != 0 { |
| trace_duration!(CATEGORY_STARNIX_MM, "AnonymousMmap"); |
| current_task.mm()?.map_anonymous(addr, length, prot_flags, options, MappingName::None) |
| } else { |
| trace_duration!(CATEGORY_STARNIX_MM, "FileBackedMmap"); |
| // TODO(tbodt): maximize protection flags so that mprotect works |
| let file = file.expect("file retrieved above for file-backed mapping"); |
| file.mmap( |
| locked, |
| current_task, |
| addr, |
| memory_offset, |
| length, |
| prot_flags, |
| options, |
| file.name.to_passive(), |
| ) |
| } |
| } |
| |
| pub fn sys_mprotect( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| prot: u32, |
| ) -> Result<(), Errno> { |
| let prot_flags = ProtectionFlags::from_bits(prot).ok_or_else(|| { |
| track_stub!(TODO("https://fxbug.dev/322874672"), "mprotect parse protection", prot); |
| errno!(EINVAL) |
| })?; |
| current_task.mm()?.protect(current_task, addr, length, prot_flags)?; |
| Ok(()) |
| } |
| |
| pub fn sys_mremap( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| old_length: usize, |
| new_length: usize, |
| flags: u32, |
| new_addr: UserAddress, |
| ) -> Result<UserAddress, Errno> { |
| let flags = MremapFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL))?; |
| let addr = |
| current_task.mm()?.remap(current_task, addr, old_length, new_length, flags, new_addr)?; |
| Ok(addr) |
| } |
| |
| pub fn sys_munmap( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| ) -> Result<(), Errno> { |
| current_task.mm()?.unmap(addr, length)?; |
| Ok(()) |
| } |
| |
| pub fn sys_msync( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| flags: u32, |
| ) -> Result<(), Errno> { |
| track_stub!(TODO("https://fxbug.dev/322874588"), "msync"); |
| |
| let mm = current_task.mm()?; |
| |
| // Perform some basic validation of the address range given to satisfy gvisor tests that |
| // use msync as a way to probe whether a page is mapped or not. |
| mm.ensure_mapped(addr, length)?; |
| |
| let addr_end = (addr + length).map_err(|_| errno!(ENOMEM))?; |
| if flags & MS_INVALIDATE != 0 && mm.state.read().num_locked_bytes(addr..addr_end) > 0 { |
| // gvisor mlock tests rely on returning EBUSY from msync on locked ranges. |
| return error!(EBUSY); |
| } |
| |
| Ok(()) |
| } |
| |
| pub fn sys_madvise( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| advice: u32, |
| ) -> Result<(), Errno> { |
| current_task.mm()?.madvise(current_task, addr, length, advice)?; |
| Ok(()) |
| } |
| |
| pub fn sys_process_madvise( |
| _locked: &mut Locked<Unlocked>, |
| _current_task: &CurrentTask, |
| _pidfd: FdNumber, |
| _iovec_addr: IOVecPtr, |
| _iovec_count: UserValue<i32>, |
| _advice: UserValue<i32>, |
| _flags: UserValue<u32>, |
| ) -> Result<usize, Errno> { |
| track_stub!(TODO("https://fxbug.dev/409060664"), "process_madvise"); |
| error!(ENOSYS) |
| } |
| |
| pub fn sys_brk( |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| ) -> Result<UserAddress, Errno> { |
| current_task.mm()?.set_brk(locked, current_task, addr) |
| } |
| |
| pub fn sys_process_vm_readv( |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| tid: tid_t, |
| local_iov_addr: IOVecPtr, |
| local_iov_count: UserValue<i32>, |
| remote_iov_addr: IOVecPtr, |
| remote_iov_count: UserValue<i32>, |
| flags: usize, |
| ) -> Result<usize, Errno> { |
| if flags != 0 { |
| return error!(EINVAL); |
| } |
| |
| // Source and destination are allowed to be of different length. It is valid to use a nullptr if |
| // the associated length is 0. Thus, if either source or destination length is 0 and nullptr, |
| // make sure to return Ok(0) before doing any other validation/operations. |
| if (local_iov_count == 0 && local_iov_addr.is_null()) |
| || (remote_iov_count == 0 && remote_iov_addr.is_null()) |
| { |
| return Ok(0); |
| } |
| |
| let weak_remote_task = current_task.get_task(tid); |
| let remote_task = Task::from_weak(&weak_remote_task)?; |
| |
| current_task.check_ptrace_access_mode(locked, PTRACE_MODE_ATTACH_REALCREDS, &remote_task)?; |
| |
| let local_iov = current_task.read_iovec(local_iov_addr, local_iov_count)?; |
| let remote_iov = current_task.read_iovec(remote_iov_addr, remote_iov_count)?; |
| log_trace!( |
| "process_vm_readv(tid={}, local_iov={:?}, remote_iov={:?})", |
| tid, |
| local_iov, |
| remote_iov |
| ); |
| |
| track_stub!(TODO("https://fxbug.dev/322874765"), "process_vm_readv single-copy"); |
| // According to the man page, this syscall was added to Linux specifically to |
| // avoid doing two copies like other IPC mechanisms require. We should avoid this too at some |
| // point. |
| let mut output = UserBuffersOutputBuffer::unified_new(current_task, local_iov)?; |
| let remote_mm = remote_task.mm().ok(); |
| if current_task.has_same_address_space(remote_mm.as_ref()) { |
| let mut input = UserBuffersInputBuffer::unified_new(current_task, remote_iov)?; |
| output.write_buffer(&mut input) |
| } else { |
| let mut input = UserBuffersInputBuffer::syscall_new(remote_task.deref(), remote_iov)?; |
| output.write_buffer(&mut input) |
| } |
| } |
| |
| pub fn sys_process_vm_writev( |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| tid: tid_t, |
| local_iov_addr: IOVecPtr, |
| local_iov_count: UserValue<i32>, |
| remote_iov_addr: IOVecPtr, |
| remote_iov_count: UserValue<i32>, |
| flags: usize, |
| ) -> Result<usize, Errno> { |
| if flags != 0 { |
| return error!(EINVAL); |
| } |
| |
| // Source and destination are allowed to be of different length. It is valid to use a nullptr if |
| // the associated length is 0. Thus, if either source or destination length is 0 and nullptr, |
| // make sure to return Ok(0) before doing any other validation/operations. |
| if (local_iov_count == 0 && local_iov_addr.is_null()) |
| || (remote_iov_count == 0 && remote_iov_addr.is_null()) |
| { |
| return Ok(0); |
| } |
| |
| let weak_remote_task = current_task.get_task(tid); |
| let remote_task = Task::from_weak(&weak_remote_task)?; |
| |
| current_task.check_ptrace_access_mode(locked, PTRACE_MODE_ATTACH_REALCREDS, &remote_task)?; |
| |
| let local_iov = current_task.read_iovec(local_iov_addr, local_iov_count)?; |
| let remote_iov = current_task.read_iovec(remote_iov_addr, remote_iov_count)?; |
| log_trace!( |
| "sys_process_vm_writev(tid={}, local_iov={:?}, remote_iov={:?})", |
| tid, |
| local_iov, |
| remote_iov |
| ); |
| |
| track_stub!(TODO("https://fxbug.dev/322874339"), "process_vm_writev single-copy"); |
| // NB: According to the man page, this syscall was added to Linux specifically to |
| // avoid doing two copies like other IPC mechanisms require. We should avoid this too at some |
| // point. |
| let mut input = UserBuffersInputBuffer::unified_new(current_task, local_iov)?; |
| let remote_mm = remote_task.mm().ok(); |
| if current_task.has_same_address_space(remote_mm.as_ref()) { |
| let mut output = UserBuffersOutputBuffer::unified_new(current_task, remote_iov)?; |
| output.write_buffer(&mut input) |
| } else { |
| let mut output = UserBuffersOutputBuffer::syscall_new(remote_task.deref(), remote_iov)?; |
| output.write_buffer(&mut input) |
| } |
| } |
| |
| pub fn sys_process_mrelease( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| pidfd: FdNumber, |
| flags: u32, |
| ) -> Result<(), Errno> { |
| if flags != 0 { |
| return error!(EINVAL); |
| } |
| let file = current_task.files.get(pidfd)?; |
| let task = current_task.get_task(file.as_thread_group_key()?.pid()); |
| let task = task.upgrade().ok_or_else(|| errno!(ESRCH))?; |
| if !task.load_stopped().is_stopped() { |
| return error!(EINVAL); |
| } |
| |
| let mm = task.mm()?; |
| let mm_state = mm.state.write(); |
| mm_state.mrelease() |
| } |
| |
| pub fn sys_membarrier( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| cmd: uapi::membarrier_cmd, |
| _flags: u32, |
| _cpu_id: i32, |
| ) -> Result<u32, Errno> { |
| match cmd { |
| // This command returns a bit mask of all supported commands. |
| // We support everything except for the RSEQ family. |
| uapi::membarrier_cmd_MEMBARRIER_CMD_QUERY => Ok(uapi::membarrier_cmd_MEMBARRIER_CMD_GLOBAL |
| | uapi::membarrier_cmd_MEMBARRIER_CMD_GLOBAL_EXPEDITED |
| | uapi::membarrier_cmd_MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED |
| | uapi::membarrier_cmd_MEMBARRIER_CMD_PRIVATE_EXPEDITED |
| | uapi::membarrier_cmd_MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED |
| | uapi::membarrier_cmd_MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE |
| | uapi::membarrier_cmd_MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE), |
| // Global and global expedited barriers are treated identically. We don't track |
| // registration for global expedited barriers currently. |
| uapi::membarrier_cmd_MEMBARRIER_CMD_GLOBAL |
| | uapi::membarrier_cmd_MEMBARRIER_CMD_GLOBAL_EXPEDITED => { |
| system_barrier(BarrierType::DataMemory); |
| Ok(0) |
| } |
| // Global registration commands are ignored. |
| uapi::membarrier_cmd_MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED => Ok(0), |
| uapi::membarrier_cmd_MEMBARRIER_CMD_PRIVATE_EXPEDITED => { |
| // A private expedited barrier is only issued if the address space is registered |
| // for these barriers. |
| if current_task.mm()?.membarrier_private_expedited_registered(MembarrierType::Memory) { |
| // If a barrier is requested, issue a global barrier. |
| system_barrier(BarrierType::DataMemory); |
| Ok(0) |
| } else { |
| error!(EPERM) |
| } |
| } |
| // Private sync core barriers are treated as global instruction stream barriers. |
| uapi::membarrier_cmd_MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE => { |
| if current_task.mm()?.membarrier_private_expedited_registered(MembarrierType::SyncCore) |
| { |
| system_barrier(BarrierType::InstructionStream); |
| Ok(0) |
| } else { |
| error!(EPERM) |
| } |
| } |
| uapi::membarrier_cmd_MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED => { |
| let _ = |
| current_task.mm()?.register_membarrier_private_expedited(MembarrierType::Memory)?; |
| Ok(0) |
| } |
| |
| uapi::membarrier_cmd_MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE => { |
| let _ = current_task |
| .mm()? |
| .register_membarrier_private_expedited(MembarrierType::SyncCore)?; |
| Ok(0) |
| } |
| uapi::membarrier_cmd_MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ => { |
| track_stub!(TODO("https://fxbug.dev/447158570"), "membarrier rseq"); |
| error!(ENOSYS) |
| } |
| uapi::membarrier_cmd_MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ => { |
| track_stub!(TODO("https://fxbug.dev/447158570"), "membarrier rseq"); |
| error!(ENOSYS) |
| } |
| _ => error!(EINVAL), |
| } |
| } |
| |
| pub fn sys_userfaultfd( |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| raw_flags: u32, |
| ) -> Result<FdNumber, Errno> { |
| let unknown_flags = raw_flags & !(O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY); |
| if unknown_flags != 0 { |
| return error!(EINVAL, format!("unknown flags provided: {unknown_flags:x?}")); |
| } |
| let mut open_flags = OpenFlags::empty(); |
| if raw_flags & O_NONBLOCK != 0 { |
| open_flags |= OpenFlags::NONBLOCK; |
| } |
| if raw_flags & O_CLOEXEC != 0 { |
| open_flags |= OpenFlags::CLOEXEC; |
| } |
| |
| let fd_flags = if raw_flags & O_CLOEXEC != 0 { |
| FdFlags::CLOEXEC |
| } else { |
| track_stub!(TODO("https://fxbug.dev/297375964"), "userfaultfds that survive exec()"); |
| return error!(ENOSYS); |
| }; |
| |
| let user_mode_only = raw_flags & UFFD_USER_MODE_ONLY == 0; |
| let uff_handle = UserFaultFile::new(locked, current_task, open_flags, user_mode_only)?; |
| current_task.add_file(locked, uff_handle, fd_flags) |
| } |
| |
| pub fn sys_futex( |
| locked: &mut Locked<Unlocked>, |
| current_task: &mut CurrentTask, |
| addr: UserAddress, |
| op: u32, |
| value: u32, |
| timeout_or_value2: SyscallArg, |
| addr2: UserAddress, |
| value3: u32, |
| ) -> Result<usize, Errno> { |
| if op & FUTEX_PRIVATE_FLAG != 0 { |
| do_futex::<PrivateFutexKey>( |
| locked, |
| current_task, |
| addr, |
| op, |
| value, |
| timeout_or_value2, |
| addr2, |
| value3, |
| ) |
| } else { |
| do_futex::<SharedFutexKey>( |
| locked, |
| current_task, |
| addr, |
| op, |
| value, |
| timeout_or_value2, |
| addr2, |
| value3, |
| ) |
| } |
| } |
| |
| fn do_futex<Key: FutexKey>( |
| locked: &mut Locked<Unlocked>, |
| current_task: &mut CurrentTask, |
| addr: UserAddress, |
| op: u32, |
| value: u32, |
| timeout_or_value2: SyscallArg, |
| addr2: UserAddress, |
| value3: u32, |
| ) -> Result<usize, Errno> { |
| let futexes = Key::get_table_from_task(current_task)?; |
| let cmd = op & (FUTEX_CMD_MASK as u32); |
| |
| let is_realtime = match (cmd, op & FUTEX_CLOCK_REALTIME != 0) { |
| // This option bit can be employed only with the FUTEX_WAIT_BITSET, FUTEX_WAIT_REQUEUE_PI, |
| // (since Linux 4.5) FUTEX_WAIT, and (since Linux 5.14) FUTEX_LOCK_PI2 operations. |
| (FUTEX_WAIT_BITSET | FUTEX_WAIT_REQUEUE_PI | FUTEX_WAIT | FUTEX_LOCK_PI2, true) => true, |
| (_, true) => return error!(EINVAL), |
| |
| // FUTEX_LOCK_PI always uses realtime. |
| (FUTEX_LOCK_PI, _) => true, |
| |
| (_, false) => false, |
| }; |
| |
| // The timeout is interpreted differently by WAIT and WAIT_BITSET: WAIT takes a |
| // timeout and WAIT_BITSET takes a deadline. |
| let read_timespec = |current_task: &CurrentTask| { |
| let utime = TimeSpecPtr::new(current_task, timeout_or_value2); |
| if utime.is_null() { |
| Ok(timespec_from_time(zx::MonotonicInstant::INFINITE)) |
| } else { |
| current_task.read_multi_arch_object(utime) |
| } |
| }; |
| let read_timeout = |current_task: &CurrentTask| { |
| let timespec = read_timespec(current_task)?; |
| let timeout = duration_from_timespec(timespec); |
| let deadline = zx::MonotonicInstant::after(timeout?); |
| if is_realtime { |
| // Since this is a timeout, waiting on the monotonic timeline before it's paused is |
| // just as good as actually estimating UTC here. |
| track_stub!(TODO("https://fxbug.dev/356912301"), "FUTEX_CLOCK_REALTIME timeout"); |
| } |
| Ok(deadline) |
| }; |
| let read_deadline = |current_task: &CurrentTask| { |
| let timespec = read_timespec(current_task)?; |
| if is_realtime { |
| Ok(TargetTime::RealTime(time_from_timespec::<UtcTimeline>(timespec)?)) |
| } else { |
| Ok(TargetTime::Monotonic(time_from_timespec::<zx::MonotonicTimeline>(timespec)?)) |
| } |
| }; |
| |
| match cmd { |
| FUTEX_WAIT => { |
| let deadline = read_timeout(current_task)?; |
| let bitset = FUTEX_BITSET_MATCH_ANY; |
| do_futex_wait_with_restart::<Key>( |
| locked, |
| current_task, |
| addr, |
| value, |
| bitset, |
| TargetTime::Monotonic(deadline), |
| )?; |
| Ok(0) |
| } |
| FUTEX_WAKE => { |
| futexes.wake(locked, current_task, addr, value as usize, FUTEX_BITSET_MATCH_ANY) |
| } |
| FUTEX_WAKE_OP => { |
| track_stub!(TODO("https://fxbug.dev/361181940"), "FUTEX_WAKE_OP"); |
| error!(ENOSYS) |
| } |
| FUTEX_WAIT_BITSET => { |
| if value3 == 0 { |
| return error!(EINVAL); |
| } |
| let deadline = read_deadline(current_task)?; |
| do_futex_wait_with_restart::<Key>(locked, current_task, addr, value, value3, deadline)?; |
| Ok(0) |
| } |
| FUTEX_WAKE_BITSET => { |
| if value3 == 0 { |
| return error!(EINVAL); |
| } |
| futexes.wake(locked, current_task, addr, value as usize, value3) |
| } |
| FUTEX_REQUEUE | FUTEX_CMP_REQUEUE => { |
| let wake_count = value as usize; |
| let requeue_count: usize = timeout_or_value2.into(); |
| if wake_count > std::i32::MAX as usize || requeue_count > std::i32::MAX as usize { |
| return error!(EINVAL); |
| } |
| let expected_value = if cmd == FUTEX_CMP_REQUEUE { Some(value3) } else { None }; |
| futexes.requeue( |
| locked, |
| current_task, |
| addr, |
| wake_count, |
| requeue_count, |
| addr2, |
| expected_value, |
| ) |
| } |
| FUTEX_WAIT_REQUEUE_PI => { |
| track_stub!(TODO("https://fxbug.dev/361181558"), "FUTEX_WAIT_REQUEUE_PI"); |
| error!(ENOSYS) |
| } |
| FUTEX_CMP_REQUEUE_PI => { |
| track_stub!(TODO("https://fxbug.dev/361181773"), "FUTEX_CMP_REQUEUE_PI"); |
| error!(ENOSYS) |
| } |
| FUTEX_LOCK_PI | FUTEX_LOCK_PI2 => { |
| futexes.lock_pi(locked, current_task, addr, read_timeout(current_task)?)?; |
| Ok(0) |
| } |
| FUTEX_TRYLOCK_PI => { |
| track_stub!(TODO("https://fxbug.dev/361175318"), "FUTEX_TRYLOCK_PI"); |
| error!(ENOSYS) |
| } |
| FUTEX_UNLOCK_PI => { |
| futexes.unlock_pi(locked, current_task, addr)?; |
| Ok(0) |
| } |
| _ => { |
| track_stub!(TODO("https://fxbug.dev/322875124"), "futex unknown command", cmd); |
| error!(ENOSYS) |
| } |
| } |
| } |
| |
| fn do_futex_wait_with_restart<Key: FutexKey>( |
| locked: &mut Locked<Unlocked>, |
| current_task: &mut CurrentTask, |
| addr: UserAddress, |
| value: u32, |
| mask: u32, |
| deadline: TargetTime, |
| ) -> Result<(), Errno> { |
| let futexes = Key::get_table_from_task(current_task)?; |
| let result = match deadline { |
| TargetTime::Monotonic(mono_deadline) => { |
| futexes.wait(locked, current_task, addr, value, mask, mono_deadline) |
| } |
| TargetTime::BootInstant(boot_deadline) => { |
| let timer_slack = current_task.read().get_timerslack(); |
| futexes.wait_boot(locked, current_task, addr, value, mask, boot_deadline, timer_slack) |
| } |
| TargetTime::RealTime(utc_deadline) => { |
| // We convert real time deadlines to boot time deadlines since we cannot wait using a UTC deadline. |
| let (boot_deadline, _) = estimate_boot_deadline_from_utc(utc_deadline); |
| let timer_slack = current_task.read().get_timerslack(); |
| futexes.wait_boot(locked, current_task, addr, value, mask, boot_deadline, timer_slack) |
| } |
| }; |
| match result { |
| Err(err) if err == EINTR => { |
| current_task.set_syscall_restart_func(move |locked, current_task| { |
| do_futex_wait_with_restart::<Key>(locked, current_task, addr, value, mask, deadline) |
| }); |
| error!(ERESTART_RESTARTBLOCK) |
| } |
| result => result, |
| } |
| } |
| |
| pub fn sys_get_robust_list( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| tid: tid_t, |
| user_head_ptr: UserRef<UserAddress>, |
| user_len_ptr: UserRef<usize>, |
| ) -> Result<(), Errno> { |
| if tid < 0 { |
| return error!(EINVAL); |
| } |
| if user_head_ptr.is_null() || user_len_ptr.is_null() { |
| return error!(EFAULT); |
| } |
| if tid != 0 { |
| security::check_task_capable(current_task, CAP_SYS_PTRACE)?; |
| } |
| let task = if tid == 0 { current_task.weak_task() } else { current_task.get_task(tid) }; |
| let task = Task::from_weak(&task)?; |
| current_task.write_object(user_head_ptr, &task.read().robust_list_head.addr())?; |
| current_task.write_object(user_len_ptr, &std::mem::size_of::<robust_list_head>())?; |
| Ok(()) |
| } |
| |
| pub fn sys_set_robust_list( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| user_head: UserRef<robust_list_head>, |
| len: usize, |
| ) -> Result<(), Errno> { |
| if len != std::mem::size_of::<robust_list_head>() { |
| return error!(EINVAL); |
| } |
| current_task.write().robust_list_head = user_head.into(); |
| Ok(()) |
| } |
| |
| pub fn sys_mlock( |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| ) -> Result<(), Errno> { |
| // If flags is 0, mlock2() behaves exactly the same as mlock(). |
| sys_mlock2(locked, current_task, addr, length, 0) |
| } |
| |
| pub fn sys_mlock2( |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| flags: u64, |
| ) -> Result<(), Errno> { |
| const KNOWN_FLAGS: u64 = MLOCK_ONFAULT as u64; |
| if (flags & !KNOWN_FLAGS) != 0 { |
| return error!(EINVAL); |
| } |
| let on_fault = flags & MLOCK_ONFAULT as u64 != 0; |
| current_task.mm()?.mlock(current_task, locked, addr, length, on_fault) |
| } |
| |
| pub fn sys_munlock( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| ) -> Result<(), Errno> { |
| current_task.mm()?.munlock(current_task, addr, length) |
| } |
| |
| pub fn sys_mlockall( |
| _locked: &mut Locked<Unlocked>, |
| _current_task: &CurrentTask, |
| _flags: u64, |
| ) -> Result<(), Errno> { |
| track_stub!(TODO("https://fxbug.dev/297292097"), "mlockall()"); |
| error!(ENOSYS) |
| } |
| |
| pub fn sys_munlockall( |
| _locked: &mut Locked<Unlocked>, |
| _current_task: &CurrentTask, |
| _flags: u64, |
| ) -> Result<(), Errno> { |
| track_stub!(TODO("https://fxbug.dev/297292097"), "munlockall()"); |
| error!(ENOSYS) |
| } |
| |
| pub fn sys_mincore( |
| _locked: &mut Locked<Unlocked>, |
| _current_task: &CurrentTask, |
| _addr: UserAddress, |
| _length: usize, |
| _out: UserRef<u8>, |
| ) -> Result<(), Errno> { |
| track_stub!(TODO("https://fxbug.dev/297372240"), "mincore()"); |
| error!(ENOSYS) |
| } |
| |
| // Syscalls for arch32 usage |
| #[cfg(target_arch = "aarch64")] |
| mod arch32 { |
| use crate::mm::PAGE_SIZE; |
| use crate::mm::syscalls::{UserAddress, sys_mmap}; |
| use crate::task::{CurrentTask, RobustListHeadPtr}; |
| use crate::vfs::FdNumber; |
| use starnix_sync::{Locked, Unlocked}; |
| use starnix_uapi::errors::Errno; |
| use starnix_uapi::user_address::UserRef; |
| use starnix_uapi::{error, uapi}; |
| |
| pub fn sys_arch32_set_robust_list( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| user_head: UserRef<uapi::arch32::robust_list_head>, |
| len: usize, |
| ) -> Result<(), Errno> { |
| if len != std::mem::size_of::<uapi::arch32::robust_list_head>() { |
| return error!(EINVAL); |
| } |
| current_task.write().robust_list_head = RobustListHeadPtr::from_32(user_head); |
| Ok(()) |
| } |
| |
| pub fn sys_arch32_mmap2( |
| locked: &mut Locked<Unlocked>, |
| current_task: &mut CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| prot: u32, |
| flags: u32, |
| fd: FdNumber, |
| offset: u64, |
| ) -> Result<UserAddress, Errno> { |
| sys_mmap(locked, current_task, addr, length, prot, flags, fd, offset * *PAGE_SIZE) |
| } |
| |
| pub fn sys_arch32_munmap( |
| _locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| addr: UserAddress, |
| length: usize, |
| ) -> Result<(), Errno> { |
| if !addr.is_lower_32bit() || length >= (1 << 32) { |
| return error!(EINVAL); |
| } |
| current_task.mm()?.unmap(addr, length)?; |
| Ok(()) |
| } |
| |
| pub use super::{ |
| sys_futex as sys_arch32_futex, sys_madvise as sys_arch32_madvise, |
| sys_membarrier as sys_arch32_membarrier, sys_mincore as sys_arch32_mincore, |
| sys_mlock as sys_arch32_mlock, sys_mlock2 as sys_arch32_mlock2, |
| sys_mlockall as sys_arch32_mlockall, sys_mremap as sys_arch32_mremap, |
| sys_msync as sys_arch32_msync, sys_munlock as sys_arch32_munlock, |
| sys_munlockall as sys_arch32_munlockall, |
| sys_process_mrelease as sys_arch32_process_mrelease, |
| sys_process_vm_readv as sys_arch32_process_vm_readv, |
| sys_userfaultfd as sys_arch32_userfaultfd, |
| }; |
| } |
| |
| #[cfg(target_arch = "aarch64")] |
| pub use arch32::*; |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::mm::memory::MemoryObject; |
| use crate::testing::*; |
| use starnix_uapi::errors::EEXIST; |
| use starnix_uapi::file_mode::Access; |
| use starnix_uapi::{MREMAP_FIXED, MREMAP_MAYMOVE, PROT_READ}; |
| |
| #[::fuchsia::test] |
| async fn test_mmap_with_colliding_hint() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let page_size = *PAGE_SIZE; |
| |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), page_size); |
| match do_mmap( |
| locked, |
| ¤t_task, |
| mapped_address, |
| page_size as usize, |
| PROT_READ, |
| MAP_PRIVATE | MAP_ANONYMOUS, |
| FdNumber::from_raw(-1), |
| 0, |
| ) { |
| Ok(address) => { |
| assert_ne!(address, mapped_address); |
| } |
| error => { |
| panic!("mmap with colliding hint failed: {error:?}"); |
| } |
| } |
| }) |
| .await; |
| } |
| |
| #[::fuchsia::test] |
| async fn test_mmap_with_fixed_collision() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let page_size = *PAGE_SIZE; |
| |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), page_size); |
| match do_mmap( |
| locked, |
| ¤t_task, |
| mapped_address, |
| page_size as usize, |
| PROT_READ, |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, |
| FdNumber::from_raw(-1), |
| 0, |
| ) { |
| Ok(address) => { |
| assert_eq!(address, mapped_address); |
| } |
| error => { |
| panic!("mmap with fixed collision failed: {error:?}"); |
| } |
| } |
| }) |
| .await; |
| } |
| |
| #[::fuchsia::test] |
| async fn test_mmap_with_fixed_noreplace_collision() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let page_size = *PAGE_SIZE; |
| |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), page_size); |
| match do_mmap( |
| locked, |
| ¤t_task, |
| mapped_address, |
| page_size as usize, |
| PROT_READ, |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, |
| FdNumber::from_raw(-1), |
| 0, |
| ) { |
| Err(errno) => { |
| assert_eq!(errno, EEXIST); |
| } |
| result => { |
| panic!("mmap with fixed_noreplace collision failed: {result:?}"); |
| } |
| } |
| }) |
| .await; |
| } |
| |
| /// It is ok to call munmap with an address that is a multiple of the page size, and |
| /// a non-zero length. |
| #[::fuchsia::test] |
| async fn test_munmap() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE); |
| assert_eq!( |
| sys_munmap(locked, ¤t_task, mapped_address, *PAGE_SIZE as usize), |
| Ok(()) |
| ); |
| |
| // Verify that the memory is no longer readable. |
| assert_eq!(current_task.read_memory_to_array::<5>(mapped_address), error!(EFAULT)); |
| }) |
| .await; |
| } |
| |
| /// It is ok to call munmap on an unmapped range. |
| #[::fuchsia::test] |
| async fn test_munmap_not_mapped() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE); |
| assert_eq!( |
| sys_munmap(locked, ¤t_task, mapped_address, *PAGE_SIZE as usize), |
| Ok(()) |
| ); |
| assert_eq!( |
| sys_munmap(locked, ¤t_task, mapped_address, *PAGE_SIZE as usize), |
| Ok(()) |
| ); |
| }) |
| .await; |
| } |
| |
| /// It is an error to call munmap with a length of 0. |
| #[::fuchsia::test] |
| async fn test_munmap_0_length() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE); |
| assert_eq!(sys_munmap(locked, ¤t_task, mapped_address, 0), error!(EINVAL)); |
| }) |
| .await; |
| } |
| |
| /// It is an error to call munmap with an address that is not a multiple of the page size. |
| #[::fuchsia::test] |
| async fn test_munmap_not_aligned() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE); |
| assert_eq!( |
| sys_munmap( |
| locked, |
| ¤t_task, |
| (mapped_address + 1u64).unwrap(), |
| *PAGE_SIZE as usize |
| ), |
| error!(EINVAL) |
| ); |
| |
| // Verify that the memory is still readable. |
| assert!(current_task.read_memory_to_array::<5>(mapped_address).is_ok()); |
| }) |
| .await; |
| } |
| |
| /// The entire page should be unmapped, not just the range [address, address + length). |
| #[::fuchsia::test] |
| async fn test_munmap_unmap_partial() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE); |
| assert_eq!( |
| sys_munmap(locked, ¤t_task, mapped_address, (*PAGE_SIZE as usize) / 2), |
| Ok(()) |
| ); |
| |
| // Verify that memory can't be read in either half of the page. |
| assert_eq!(current_task.read_memory_to_array::<5>(mapped_address), error!(EFAULT)); |
| assert_eq!( |
| current_task |
| .read_memory_to_array::<5>((mapped_address + (*PAGE_SIZE - 2)).unwrap()), |
| error!(EFAULT) |
| ); |
| }) |
| .await; |
| } |
| |
| /// All pages that intersect the munmap range should be unmapped. |
| #[::fuchsia::test] |
| async fn test_munmap_multiple_pages() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 2); |
| assert_eq!( |
| sys_munmap(locked, ¤t_task, mapped_address, (*PAGE_SIZE as usize) + 1), |
| Ok(()) |
| ); |
| |
| // Verify that neither page is readable. |
| assert_eq!(current_task.read_memory_to_array::<5>(mapped_address), error!(EFAULT)); |
| assert_eq!( |
| current_task |
| .read_memory_to_array::<5>((mapped_address + (*PAGE_SIZE + 1u64)).unwrap()), |
| error!(EFAULT) |
| ); |
| }) |
| .await; |
| } |
| |
| /// Only the pages that intersect the munmap range should be unmapped. |
| #[::fuchsia::test] |
| async fn test_munmap_one_of_many_pages() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 2); |
| assert_eq!( |
| sys_munmap(locked, ¤t_task, mapped_address, (*PAGE_SIZE as usize) - 1), |
| Ok(()) |
| ); |
| |
| // Verify that the second page is still readable. |
| assert_eq!(current_task.read_memory_to_array::<5>(mapped_address), error!(EFAULT)); |
| assert!( |
| current_task |
| .read_memory_to_array::<5>((mapped_address + (*PAGE_SIZE + 1u64)).unwrap()) |
| .is_ok() |
| ); |
| }) |
| .await; |
| } |
| |
| /// Unmap the middle page of a mapping. |
| #[::fuchsia::test] |
| async fn test_munmap_middle_page() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let mapped_address = |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 3); |
| assert_eq!( |
| sys_munmap( |
| locked, |
| ¤t_task, |
| (mapped_address + *PAGE_SIZE).unwrap(), |
| *PAGE_SIZE as usize |
| ), |
| Ok(()) |
| ); |
| |
| // Verify that the first and third pages are still readable. |
| assert!(current_task.read_memory_to_vec(mapped_address, 5).is_ok()); |
| assert_eq!( |
| current_task.read_memory_to_vec((mapped_address + *PAGE_SIZE).unwrap(), 5), |
| error!(EFAULT) |
| ); |
| assert!( |
| current_task |
| .read_memory_to_vec((mapped_address + (*PAGE_SIZE * 2)).unwrap(), 5) |
| .is_ok() |
| ); |
| }) |
| .await; |
| } |
| |
| /// Unmap a range of pages that includes disjoint mappings. |
| #[::fuchsia::test] |
| async fn test_munmap_many_mappings() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let mapped_addresses: Vec<_> = std::iter::repeat_with(|| { |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE) |
| }) |
| .take(3) |
| .collect(); |
| let min_address = *mapped_addresses.iter().min().unwrap(); |
| let max_address = *mapped_addresses.iter().max().unwrap(); |
| let unmap_length = (max_address - min_address) + *PAGE_SIZE as usize; |
| |
| assert_eq!(sys_munmap(locked, ¤t_task, min_address, unmap_length), Ok(())); |
| |
| // Verify that none of the mapped pages are readable. |
| for mapped_address in mapped_addresses { |
| assert_eq!(current_task.read_memory_to_vec(mapped_address, 5), error!(EFAULT)); |
| } |
| }) |
| .await; |
| } |
| |
| #[::fuchsia::test] |
| async fn test_msync_validates_address_range() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| // Map 3 pages and test that ranges covering these pages return no error. |
| let addr = map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 3); |
| assert_eq!(sys_msync(locked, ¤t_task, addr, *PAGE_SIZE as usize * 3, 0), Ok(())); |
| assert_eq!(sys_msync(locked, ¤t_task, addr, *PAGE_SIZE as usize * 2, 0), Ok(())); |
| assert_eq!( |
| sys_msync( |
| locked, |
| ¤t_task, |
| (addr + *PAGE_SIZE).unwrap(), |
| *PAGE_SIZE as usize * 2, |
| 0 |
| ), |
| Ok(()) |
| ); |
| |
| // Unmap the middle page and test that ranges covering that page return ENOMEM. |
| sys_munmap(locked, ¤t_task, (addr + *PAGE_SIZE).unwrap(), *PAGE_SIZE as usize) |
| .expect("unmap middle"); |
| assert_eq!(sys_msync(locked, ¤t_task, addr, *PAGE_SIZE as usize, 0), Ok(())); |
| assert_eq!( |
| sys_msync(locked, ¤t_task, addr, *PAGE_SIZE as usize * 3, 0), |
| error!(ENOMEM) |
| ); |
| assert_eq!( |
| sys_msync(locked, ¤t_task, addr, *PAGE_SIZE as usize * 2, 0), |
| error!(ENOMEM) |
| ); |
| assert_eq!( |
| sys_msync( |
| locked, |
| ¤t_task, |
| (addr + *PAGE_SIZE).unwrap(), |
| *PAGE_SIZE as usize * 2, |
| 0 |
| ), |
| error!(ENOMEM) |
| ); |
| assert_eq!( |
| sys_msync( |
| locked, |
| ¤t_task, |
| (addr + (*PAGE_SIZE * 2)).unwrap(), |
| *PAGE_SIZE as usize, |
| 0 |
| ), |
| Ok(()) |
| ); |
| |
| // Map the middle page back and test that ranges covering the three pages |
| // (spanning multiple ranges) return no error. |
| assert_eq!( |
| map_memory(locked, ¤t_task, (addr + *PAGE_SIZE).unwrap(), *PAGE_SIZE), |
| (addr + *PAGE_SIZE).unwrap() |
| ); |
| assert_eq!(sys_msync(locked, ¤t_task, addr, *PAGE_SIZE as usize * 3, 0), Ok(())); |
| assert_eq!(sys_msync(locked, ¤t_task, addr, *PAGE_SIZE as usize * 2, 0), Ok(())); |
| assert_eq!( |
| sys_msync( |
| locked, |
| ¤t_task, |
| (addr + *PAGE_SIZE).unwrap(), |
| *PAGE_SIZE as usize * 2, |
| 0 |
| ), |
| Ok(()) |
| ); |
| }) |
| .await; |
| } |
| |
| /// Shrinks an entire range. |
| #[::fuchsia::test] |
| async fn test_mremap_shrink_whole_range_from_end() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| // Map 2 pages. |
| let addr = map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 2); |
| fill_page(¤t_task, addr, 'a'); |
| fill_page(¤t_task, (addr + *PAGE_SIZE).unwrap(), 'b'); |
| |
| // Shrink the mapping from 2 to 1 pages. |
| assert_eq!( |
| remap_memory( |
| locked, |
| ¤t_task, |
| addr, |
| *PAGE_SIZE * 2, |
| *PAGE_SIZE, |
| 0, |
| UserAddress::default() |
| ), |
| Ok(addr) |
| ); |
| |
| check_page_eq(¤t_task, addr, 'a'); |
| check_unmapped(¤t_task, (addr + *PAGE_SIZE).unwrap()); |
| }) |
| .await; |
| } |
| |
| /// Shrinks part of a range, introducing a hole in the middle. |
| #[::fuchsia::test] |
| async fn test_mremap_shrink_partial_range() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| // Map 3 pages. |
| let addr = map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 3); |
| fill_page(¤t_task, addr, 'a'); |
| fill_page(¤t_task, (addr + *PAGE_SIZE).unwrap(), 'b'); |
| fill_page(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| |
| // Shrink the first 2 pages down to 1, creating a hole. |
| assert_eq!( |
| remap_memory( |
| locked, |
| ¤t_task, |
| addr, |
| *PAGE_SIZE * 2, |
| *PAGE_SIZE, |
| 0, |
| UserAddress::default() |
| ), |
| Ok(addr) |
| ); |
| |
| check_page_eq(¤t_task, addr, 'a'); |
| check_unmapped(¤t_task, (addr + *PAGE_SIZE).unwrap()); |
| check_page_eq(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| }) |
| .await; |
| } |
| |
| /// Shrinking doesn't care if the range specified spans multiple mappings. |
| #[::fuchsia::test] |
| async fn test_mremap_shrink_across_ranges() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| // Map 3 pages, unmap the middle, then map the middle again. This will leave us with |
| // 3 contiguous mappings. |
| let addr = map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 3); |
| assert_eq!( |
| sys_munmap( |
| locked, |
| ¤t_task, |
| (addr + *PAGE_SIZE).unwrap(), |
| *PAGE_SIZE as usize |
| ), |
| Ok(()) |
| ); |
| assert_eq!( |
| map_memory(locked, ¤t_task, (addr + *PAGE_SIZE).unwrap(), *PAGE_SIZE), |
| (addr + *PAGE_SIZE).unwrap() |
| ); |
| |
| fill_page(¤t_task, addr, 'a'); |
| fill_page(¤t_task, (addr + *PAGE_SIZE).unwrap(), 'b'); |
| fill_page(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| |
| // Remap over all three mappings, shrinking to 1 page. |
| assert_eq!( |
| remap_memory( |
| locked, |
| ¤t_task, |
| addr, |
| *PAGE_SIZE * 3, |
| *PAGE_SIZE, |
| 0, |
| UserAddress::default() |
| ), |
| Ok(addr) |
| ); |
| |
| check_page_eq(¤t_task, addr, 'a'); |
| check_unmapped(¤t_task, (addr + *PAGE_SIZE).unwrap()); |
| check_unmapped(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap()); |
| }) |
| .await; |
| } |
| |
| /// Grows a mapping in-place. |
| #[::fuchsia::test] |
| async fn test_mremap_grow_in_place() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| // Map 3 pages, unmap the middle, leaving a hole. |
| let addr = map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 3); |
| fill_page(¤t_task, addr, 'a'); |
| fill_page(¤t_task, (addr + *PAGE_SIZE).unwrap(), 'b'); |
| fill_page(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| assert_eq!( |
| sys_munmap( |
| locked, |
| ¤t_task, |
| (addr + *PAGE_SIZE).unwrap(), |
| *PAGE_SIZE as usize |
| ), |
| Ok(()) |
| ); |
| |
| // Grow the first page in-place into the middle. |
| assert_eq!( |
| remap_memory( |
| locked, |
| ¤t_task, |
| addr, |
| *PAGE_SIZE, |
| *PAGE_SIZE * 2, |
| 0, |
| UserAddress::default() |
| ), |
| Ok(addr) |
| ); |
| |
| check_page_eq(¤t_task, addr, 'a'); |
| |
| // The middle page should be new, and not just pointing to the original middle page filled |
| // with 'b'. |
| check_page_ne(¤t_task, (addr + *PAGE_SIZE).unwrap(), 'b'); |
| |
| check_page_eq(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| }) |
| .await; |
| } |
| |
| /// Tries to grow a set of pages that cannot fit, and forces a move. |
| #[::fuchsia::test] |
| async fn test_mremap_grow_maymove() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| // Map 3 pages. |
| let addr = map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 3); |
| fill_page(¤t_task, addr, 'a'); |
| fill_page(¤t_task, (addr + *PAGE_SIZE).unwrap(), 'b'); |
| fill_page(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| |
| // Grow the first two pages by 1, forcing a move. |
| let new_addr = remap_memory( |
| locked, |
| ¤t_task, |
| addr, |
| *PAGE_SIZE * 2, |
| *PAGE_SIZE * 3, |
| MREMAP_MAYMOVE, |
| UserAddress::default(), |
| ) |
| .expect("failed to mremap"); |
| |
| assert_ne!(new_addr, addr, "mremap did not move the mapping"); |
| |
| // The first two pages should have been moved. |
| check_unmapped(¤t_task, addr); |
| check_unmapped(¤t_task, (addr + *PAGE_SIZE).unwrap()); |
| |
| // The third page should still be present. |
| check_page_eq(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| |
| // The moved pages should have the same contents. |
| check_page_eq(¤t_task, new_addr, 'a'); |
| check_page_eq(¤t_task, (new_addr + *PAGE_SIZE).unwrap(), 'b'); |
| |
| // The newly grown page should not be the same as the original third page. |
| check_page_ne(¤t_task, (new_addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| }) |
| .await; |
| } |
| |
| /// Shrinks a set of pages and move them to a fixed location. |
| #[::fuchsia::test] |
| async fn test_mremap_shrink_fixed() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| // Map 2 pages which will act as the destination. |
| let dst_addr = |
| map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 2); |
| fill_page(¤t_task, dst_addr, 'y'); |
| fill_page(¤t_task, (dst_addr + *PAGE_SIZE).unwrap(), 'z'); |
| |
| // Map 3 pages. |
| let addr = map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 3); |
| fill_page(¤t_task, addr, 'a'); |
| fill_page(¤t_task, (addr + *PAGE_SIZE).unwrap(), 'b'); |
| fill_page(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| |
| // Shrink the first two pages and move them to overwrite the mappings at `dst_addr`. |
| let new_addr = remap_memory( |
| locked, |
| ¤t_task, |
| addr, |
| *PAGE_SIZE * 2, |
| *PAGE_SIZE, |
| MREMAP_MAYMOVE | MREMAP_FIXED, |
| dst_addr, |
| ) |
| .expect("failed to mremap"); |
| |
| assert_eq!(new_addr, dst_addr, "mremap did not move the mapping"); |
| |
| // The first two pages should have been moved. |
| check_unmapped(¤t_task, addr); |
| check_unmapped(¤t_task, (addr + *PAGE_SIZE).unwrap()); |
| |
| // The third page should still be present. |
| check_page_eq(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| |
| // The first moved page should have the same contents. |
| check_page_eq(¤t_task, new_addr, 'a'); |
| |
| // The second page should be part of the original dst mapping. |
| check_page_eq(¤t_task, (new_addr + *PAGE_SIZE).unwrap(), 'z'); |
| }) |
| .await; |
| } |
| |
| /// Clobbers the middle of an existing mapping with mremap to a fixed location. |
| #[::fuchsia::test] |
| async fn test_mremap_clobber_memory_mapping() { |
| spawn_kernel_and_run(async |locked, current_task| { |
| let dst_memory = MemoryObject::from(zx::Vmo::create(2 * *PAGE_SIZE).unwrap()); |
| dst_memory.write(&['x' as u8].repeat(*PAGE_SIZE as usize), 0).unwrap(); |
| dst_memory.write(&['y' as u8].repeat(*PAGE_SIZE as usize), *PAGE_SIZE).unwrap(); |
| |
| let dst_addr = current_task |
| .mm() |
| .unwrap() |
| .map_memory( |
| DesiredAddress::Any, |
| dst_memory.into(), |
| 0, |
| 2 * (*PAGE_SIZE as usize), |
| ProtectionFlags::READ, |
| Access::rwx(), |
| MappingOptions::empty(), |
| MappingName::None, |
| ) |
| .unwrap(); |
| |
| // Map 3 pages. |
| let addr = map_memory(locked, ¤t_task, UserAddress::default(), *PAGE_SIZE * 3); |
| fill_page(¤t_task, addr, 'a'); |
| fill_page(¤t_task, (addr + *PAGE_SIZE).unwrap(), 'b'); |
| fill_page(¤t_task, (addr + (*PAGE_SIZE * 2)).unwrap(), 'c'); |
| |
| // Overwrite the second page of the mapping with the second page of the anonymous mapping. |
| let remapped_addr = sys_mremap( |
| locked, |
| &*current_task, |
| (addr + *PAGE_SIZE).unwrap(), |
| *PAGE_SIZE as usize, |
| *PAGE_SIZE as usize, |
| MREMAP_FIXED | MREMAP_MAYMOVE, |
| (dst_addr + *PAGE_SIZE).unwrap(), |
| ) |
| .unwrap(); |
| |
| assert_eq!(remapped_addr, (dst_addr + *PAGE_SIZE).unwrap()); |
| |
| check_page_eq(¤t_task, addr, 'a'); |
| check_unmapped(¤t_task, (addr + *PAGE_SIZE).unwrap()); |
| check_page_eq(¤t_task, (addr + (2 * *PAGE_SIZE)).unwrap(), 'c'); |
| |
| check_page_eq(¤t_task, dst_addr, 'x'); |
| check_page_eq(¤t_task, (dst_addr + *PAGE_SIZE).unwrap(), 'b'); |
| }) |
| .await; |
| } |
| |
| #[cfg(target_arch = "x86_64")] |
| #[::fuchsia::test] |
| async fn test_map_32_bit() { |
| use starnix_uapi::PROT_WRITE; |
| |
| spawn_kernel_and_run(async |locked, current_task| { |
| let page_size = *PAGE_SIZE; |
| |
| for _i in 0..256 { |
| match do_mmap( |
| locked, |
| ¤t_task, |
| UserAddress::from(0), |
| page_size as usize, |
| PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, |
| FdNumber::from_raw(-1), |
| 0, |
| ) { |
| Ok(address) => { |
| let memory_end = address.ptr() + page_size as usize; |
| assert!(memory_end <= 0x80000000); |
| } |
| error => { |
| panic!("mmap with MAP_32BIT failed: {error:?}"); |
| } |
| } |
| } |
| }) |
| .await; |
| } |
| } |