This commit is contained in:
vvsv 2026-02-05 17:57:35 +08:00 committed by GitHub
commit eb62c5c812
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 328 additions and 72 deletions

View File

@ -152,10 +152,7 @@ impl TryFrom<&CpuExceptionInfo> for PageFaultInfo {
_ => return Err(()),
};
Ok(PageFaultInfo {
address: value.page_fault_addr,
required_perms,
})
Ok(PageFaultInfo::new(value.page_fault_addr, required_perms))
}
}

View File

@ -150,10 +150,7 @@ impl TryFrom<&CpuException> for PageFaultInfo {
_ => return Err(()),
};
Ok(PageFaultInfo {
address: *fault_addr,
required_perms,
})
Ok(PageFaultInfo::new(*fault_addr, required_perms))
}
}

View File

@ -145,10 +145,7 @@ impl From<&RawPageFaultInfo> for PageFaultInfo {
VmPerms::READ
};
PageFaultInfo {
address: raw_info.addr,
required_perms,
}
PageFaultInfo::new(raw_info.addr, required_perms)
}
}

View File

@ -4,12 +4,21 @@ use aster_util::printer::VmPrinter;
use super::TidDirOps;
use crate::{
events::IoEvents,
fs::{
inode_handle::FileIo,
procfs::template::{FileOps, ProcFileBuilder},
utils::{Inode, mkmod},
utils::{AccessMode, Inode, InodeIo, StatusFlags, mkmod},
},
prelude::*,
process::{Process, posix_thread::AsPosixThread},
process::{
Process,
posix_thread::{
AsPosixThread,
ptrace::{PtraceMode, check_may_access},
},
signal::{PollHandle, Pollable},
},
vm::vmar::{VMAR_CAP_ADDR, VMAR_LOWEST_ADDR},
};
@ -28,12 +37,44 @@ impl MapsFileOps {
}
impl FileOps for MapsFileOps {
fn read_at(&self, offset: usize, writer: &mut VmWriter) -> Result<usize> {
fn read_at(&self, _offset: usize, _writer: &mut VmWriter) -> Result<usize> {
unreachable!("should read via opened `MapsFileHandle`")
}
fn write_at(&self, _offset: usize, _reader: &mut VmReader) -> Result<usize> {
unreachable!("should write via opened `MapsFileHandle`")
}
fn open(
&self,
_access_mode: AccessMode,
_status_flags: StatusFlags,
) -> Option<Result<Box<dyn FileIo>>> {
let handle = check_may_access(
current_thread!().as_posix_thread().unwrap(),
self.0.main_thread().as_posix_thread().unwrap(),
PtraceMode::READ_FSCREDS,
)
.map(|_| Box::new(MapsFileHandle(self.0.clone())) as Box<dyn FileIo>);
Some(handle)
}
}
struct MapsFileHandle(Arc<Process>);
impl InodeIo for MapsFileHandle {
fn read_at(
&self,
offset: usize,
writer: &mut VmWriter,
_status_flags: StatusFlags,
) -> Result<usize> {
let mut printer = VmPrinter::new_skip(writer, offset);
let vmar_guard = self.0.lock_vmar();
let Some(vmar) = vmar_guard.as_ref() else {
return_errno_with_message!(Errno::ESRCH, "the process has exited");
return Ok(0);
};
let current = current_thread!();
@ -50,4 +91,30 @@ impl FileOps for MapsFileOps {
Ok(printer.bytes_written())
}
fn write_at(
&self,
_offset: usize,
_reader: &mut VmReader,
_status_flags: StatusFlags,
) -> Result<usize> {
return_errno_with_message!(Errno::EACCES, "`/proc/[pid]/maps` is read-only");
}
}
impl Pollable for MapsFileHandle {
fn poll(&self, mask: IoEvents, _poller: Option<&mut PollHandle>) -> IoEvents {
let events = IoEvents::IN | IoEvents::OUT;
events & mask
}
}
impl FileIo for MapsFileHandle {
fn check_seekable(&self) -> Result<()> {
Ok(())
}
fn is_offset_aware(&self) -> bool {
true
}
}

View File

@ -2,12 +2,21 @@
use super::TidDirOps;
use crate::{
events::IoEvents,
fs::{
inode_handle::FileIo,
procfs::template::{FileOps, ProcFileBuilder},
utils::{Inode, mkmod},
utils::{AccessMode, Inode, InodeIo, StatusFlags, mkmod},
},
prelude::*,
process::Process,
process::{
Process,
posix_thread::{
AsPosixThread,
ptrace::{PtraceMode, check_may_access},
},
signal::{PollHandle, Pollable},
},
};
/// Represents the inode at `/proc/[pid]/task/[tid]/mem` (and also `/proc/[pid]/mem`).
@ -25,10 +34,49 @@ impl MemFileOps {
}
impl FileOps for MemFileOps {
fn read_at(&self, offset: usize, writer: &mut VmWriter) -> Result<usize> {
fn read_at(&self, _offset: usize, _writer: &mut VmWriter) -> Result<usize> {
unreachable!("should read via opened `MemFileHandle`")
}
fn write_at(&self, _offset: usize, _reader: &mut VmReader) -> Result<usize> {
unreachable!("should write via opened `MemFileHandle`")
}
fn open(
&self,
_access_mode: AccessMode,
_status_flags: StatusFlags,
) -> Option<Result<Box<dyn FileIo>>> {
if self.0.lock_vmar().as_ref().is_none() {
return Some(Err(Error::with_message(
Errno::EACCES,
"the process has exited",
)));
}
let handle = check_may_access(
current_thread!().as_posix_thread().unwrap(),
self.0.main_thread().as_posix_thread().unwrap(),
PtraceMode::ATTACH_FSCREDS,
)
.map(|_| Box::new(MemFileHandle(self.0.clone())) as Box<dyn FileIo>);
Some(handle)
}
}
struct MemFileHandle(Arc<Process>);
impl InodeIo for MemFileHandle {
fn read_at(
&self,
offset: usize,
writer: &mut VmWriter,
_status_flags: StatusFlags,
) -> Result<usize> {
let vmar_guard = self.0.lock_vmar();
let Some(vmar) = vmar_guard.as_ref() else {
return_errno_with_message!(Errno::ESRCH, "the process has exited");
return Ok(0);
};
match vmar.read_remote(offset, writer) {
Ok(bytes) => Ok(bytes),
@ -37,10 +85,15 @@ impl FileOps for MemFileOps {
}
}
fn write_at(&self, offset: usize, reader: &mut VmReader) -> Result<usize> {
fn write_at(
&self,
offset: usize,
reader: &mut VmReader,
_status_flags: StatusFlags,
) -> Result<usize> {
let vmar_guard = self.0.lock_vmar();
let Some(vmar) = vmar_guard.as_ref() else {
return_errno_with_message!(Errno::ESRCH, "the process has exited");
return Ok(0);
};
match vmar.write_remote(offset, reader) {
Ok(bytes) => Ok(bytes),
@ -49,3 +102,20 @@ impl FileOps for MemFileOps {
}
}
}
impl Pollable for MemFileHandle {
fn poll(&self, mask: IoEvents, _poller: Option<&mut PollHandle>) -> IoEvents {
let events = IoEvents::IN | IoEvents::OUT;
events & mask
}
}
impl FileIo for MemFileHandle {
fn check_seekable(&self) -> Result<()> {
Ok(())
}
fn is_offset_aware(&self) -> bool {
true
}
}

View File

@ -6,9 +6,12 @@ use inherit_methods_macro::inherit_methods;
use super::{Common, ProcFs};
use crate::{
fs::utils::{
Extension, FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata, StatusFlags,
SymbolicLink,
fs::{
inode_handle::FileIo,
utils::{
AccessMode, Extension, FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata,
StatusFlags, SymbolicLink,
},
},
prelude::*,
process::{Gid, Uid},
@ -108,6 +111,14 @@ impl<F: FileOps + 'static> Inode for ProcFile<F> {
// Seeking regular files under `/proc` with `SEEK_END` will fail.
None
}
fn open(
&self,
access_mode: AccessMode,
status_flags: StatusFlags,
) -> Option<Result<Box<dyn FileIo>>> {
self.inner.open(access_mode, status_flags)
}
}
pub trait FileOps: Sync + Send {
@ -116,4 +127,12 @@ pub trait FileOps: Sync + Send {
fn write_at(&self, _offset: usize, _reader: &mut VmReader) -> Result<usize> {
return_errno_with_message!(Errno::EPERM, "the file is not writable");
}
fn open(
&self,
_access_mode: AccessMode,
_status_flags: StatusFlags,
) -> Option<Result<Box<dyn FileIo>>> {
None
}
}

View File

@ -391,7 +391,7 @@ fn clone_child_task(
let mut thread_builder =
PosixThreadBuilder::new(child_tid, thread_name, child_user_ctx, credentials)
.process(posix_thread.weak_process())
.process(posix_thread.weak_process().clone())
.sig_mask(sig_mask)
.file_table(child_file_table)
.fs(child_fs)

View File

@ -72,10 +72,7 @@ pub fn futex_wait_bitset(
// The futex word is aligned on a 4-byte boundary, so it cannot cross the page boundary.
user_space
.vmar()
.handle_page_fault(&PageFaultInfo {
address: futex_addr,
required_perms: VmPerms::READ,
})
.handle_page_fault(&PageFaultInfo::new(futex_addr, VmPerms::READ))
.map_err(|_| {
Error::with_message(
Errno::EFAULT,
@ -288,10 +285,10 @@ pub fn futex_wake_op(
// The futex word is aligned on a 4-byte boundary, so it cannot cross the page boundary.
user_space
.vmar()
.handle_page_fault(&PageFaultInfo {
address: futex_addr_2,
required_perms: VmPerms::READ | VmPerms::WRITE,
})
.handle_page_fault(&PageFaultInfo::new(
futex_addr_2,
VmPerms::READ | VmPerms::WRITE,
))
.map_err(|_| {
Error::with_message(
Errno::EFAULT,

View File

@ -30,6 +30,7 @@ mod exit;
pub mod futex;
mod name;
mod posix_thread_ext;
pub mod ptrace;
mod robust_list;
mod thread_local;
pub mod thread_table;
@ -97,8 +98,8 @@ impl PosixThread {
self.process.upgrade().unwrap()
}
pub fn weak_process(&self) -> Weak<Process> {
Weak::clone(&self.process)
pub fn weak_process(&self) -> &Weak<Process> {
&self.process
}
/// Returns the thread id

View File

@ -0,0 +1,76 @@
// SPDX-License-Identifier: MPL-2.0
use bitflags::bitflags;
use crate::{
prelude::*,
process::{credentials::capabilities::CapSet, posix_thread::PosixThread},
};
/// Checks whether the current `PosixThread` may access the given target `PosixThread`
// Reference: <https://elixir.bootlin.com/linux/v6.16.5/source/kernel/ptrace.c#L276>
pub fn check_may_access(
current_pthread: &PosixThread,
target_pthread: &PosixThread,
mode: PtraceMode,
) -> Result<()> {
if mode.contains(PtraceMode::FSCREDS) == mode.contains(PtraceMode::REALCREDS) {
return_errno_with_message!(
Errno::EPERM,
"should specify exactly one of FSCREDS and REALCREDS"
);
}
if Weak::ptr_eq(
current_pthread.weak_process(),
target_pthread.weak_process(),
) {
return Ok(());
}
let cred = current_pthread.credentials();
let (caller_uid, caller_gid) = if mode.contains(PtraceMode::FSCREDS) {
(cred.fsuid(), cred.fsgid())
} else {
(cred.ruid(), cred.rgid())
};
let tcred = target_pthread.credentials();
let caller_is_same = caller_uid == tcred.euid()
&& caller_uid == tcred.suid()
&& caller_uid == tcred.ruid()
&& caller_gid == tcred.egid()
&& caller_gid == tcred.sgid()
&& caller_gid == tcred.rgid();
let caller_has_cap = target_pthread
.process()
.user_ns()
.lock()
.check_cap(CapSet::SYS_PTRACE, current_pthread)
.is_ok();
if !caller_is_same && !caller_has_cap {
return_errno_with_message!(
Errno::EPERM,
"the calling process does not have the required permissions"
);
}
// TODO: Add further security checks (e.g., YAMA LSM).
Ok(())
}
bitflags! {
pub struct PtraceMode: u32 {
const READ = 0x01;
const ATTACH = 0x02;
const NOAUDIT = 0x04;
const FSCREDS = 0x08;
const REALCREDS = 0x10;
const READ_FSCREDS = Self::READ.bits() | Self::FSCREDS.bits();
const READ_REALCREDS = Self::READ.bits() | Self::REALCREDS.bits();
const ATTACH_FSCREDS = Self::ATTACH.bits() | Self::FSCREDS.bits();
const ATTACH_REALCREDS = Self::ATTACH.bits() | Self::REALCREDS.bits();
}
}

View File

@ -3,7 +3,13 @@
use crate::{
fs::file_table::{FdFlags, FileDesc, get_file_fast},
prelude::*,
process::{PidFile, credentials::capabilities::CapSet, posix_thread::AsPosixThread},
process::{
PidFile,
posix_thread::{
AsPosixThread,
ptrace::{PtraceMode, check_may_access},
},
},
syscall::SyscallReturn,
};
@ -32,22 +38,11 @@ pub fn sys_pidfd_getfd(
.process_opt()
.ok_or_else(|| Error::with_message(Errno::ESRCH, "the target process has been reaped"))?;
// The calling process should have PTRACE_MODE_ATTACH_REALCREDS permissions (see ptrace(2))
// over the process referred to by `pidfd`.
// Currently, this is implemented as requiring the calling process to have the
// CAP_SYS_PTRACE capability, which is stricter.
// TODO: Implement appropriate PTRACE_MODE_ATTACH_REALCREDS permission check.
if process
.user_ns()
.lock()
.check_cap(CapSet::SYS_PTRACE, ctx.posix_thread)
.is_err()
{
return_errno_with_message!(
Errno::EPERM,
"the calling process does not have the required permissions"
);
}
check_may_access(
ctx.posix_thread,
process.main_thread().as_posix_thread().unwrap(),
PtraceMode::ATTACH_REALCREDS,
)?;
let main_thread = process.main_thread();

View File

@ -28,6 +28,30 @@ pub struct PageFaultInfo {
/// The [`VmPerms`] required by the memory operation that causes page fault.
/// For example, a "store" operation may require `VmPerms::WRITE`.
pub required_perms: VmPerms,
/// Whether this page fault is forced (e.g., manually triggered by `ptrace`).
/// A forced page fault may bypass some permission checks.
pub is_forced: bool,
_private: (),
}
impl PageFaultInfo {
/// Creates a new `PageFaultInfo`.
pub fn new(address: Vaddr, required_perms: VmPerms) -> Self {
Self {
address,
required_perms,
is_forced: false,
_private: (),
}
}
/// Marks this page fault as forced.
pub fn force(mut self) -> Self {
self.is_forced = true;
self
}
}
/// We can't handle most exceptions, just send self a fault signal before return to user space.

View File

@ -318,15 +318,7 @@ impl VmMapping {
page_fault_info: &PageFaultInfo,
rss_delta: &mut RssDelta,
) -> Result<()> {
if !self.perms.contains(page_fault_info.required_perms) {
trace!(
"self.perms {:?}, page_fault_info.required_perms {:?}, self.range {:?}",
self.perms,
page_fault_info.required_perms,
self.range()
);
return_errno_with_message!(Errno::EACCES, "perm check fails");
}
self.check_perms_for_page_fault(page_fault_info)?;
let page_aligned_addr = page_fault_info.address.align_down(PAGE_SIZE);
let is_write = page_fault_info.required_perms.contains(VmPerms::WRITE);
@ -366,6 +358,33 @@ impl VmMapping {
)
}
fn check_perms_for_page_fault(&self, page_fault_info: &PageFaultInfo) -> Result<()> {
trace!(
"self.perms {:?}, page_fault_info.required_perms {:?}, self.range {:?}",
self.perms,
page_fault_info.required_perms,
self.range()
);
let mut perms = self.perms;
// Reference: <https://elixir.bootlin.com/linux/v6.16.5/source/mm/gup.c#L1282-L1311>
if page_fault_info.is_forced {
if perms.contains(VmPerms::MAY_READ) {
perms.insert(VmPerms::READ);
}
if self.is_cow() {
perms.insert(VmPerms::WRITE);
}
}
if !perms.contains(page_fault_info.required_perms) {
return_errno_with_message!(Errno::EACCES, "perm check fails");
}
Ok(())
}
fn handle_single_page_fault(
&self,
vm_space: &VmSpace,
@ -417,7 +436,6 @@ impl VmMapping {
let new_frame = duplicate_frame(&frame)?;
prop.flags |= new_flags;
cursor.map(new_frame.into(), prop);
rss_delta.add(self.rss_type(), 1);
}
cursor.flusher().sync_tlb_flush();
}

View File

@ -150,10 +150,7 @@ impl Vmar {
Some(_) | None => (),
}
let page_fault_info = PageFaultInfo {
address: vaddr,
required_perms: required_page_flags.into(),
};
let page_fault_info = PageFaultInfo::new(vaddr, required_page_flags.into()).force();
self.handle_page_fault(&page_fault_info)?;
item = self.query_page(vaddr)?;

View File

@ -37,10 +37,11 @@ FN_TEST(proc_mem_remote)
CHECK(close(pipe_p2c[1]));
int fd = CHECK(open(FILE_NAME, O_RDONLY));
void *addr =
CHECK_WITH(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0),
_ret != MAP_FAILED);
// The parent should successfully read from and (force) write to this
// memory region via `/proc/pid/mem`, although it isn't `PROT_WRITE`.
void *addr = CHECK_WITH(mmap(NULL, PAGE_SIZE, PROT_READ,
MAP_PRIVATE, fd, 0),
_ret != MAP_FAILED);
CHECK(write(pipe_c2p[1], &addr, sizeof(addr)));
// Wait for the parent to read and write.

View File

@ -11,7 +11,7 @@ ProcPid.AccessDeny
ProcPidCwd.Subprocess
ProcPidRoot.Subprocess
ProcPidEnviron.MatchesEnviron
ProcPidMem.AfterExit
# TODO: support `ptrace`.
ProcPidMem.DifferentUserAttached
ProcPidFile.SubprocessRunning
ProcPidFile.SubprocessZombie