Add ptrace access mode checks

This commit is contained in:
Wang Siyuan 2026-01-20 08:14:06 +00:00
parent bf7709f345
commit a6abae2fbe
8 changed files with 262 additions and 34 deletions

View File

@ -4,12 +4,21 @@ use aster_util::printer::VmPrinter;
use super::TidDirOps; use super::TidDirOps;
use crate::{ use crate::{
events::IoEvents,
fs::{ fs::{
inode_handle::FileIo,
procfs::template::{FileOps, ProcFileBuilder}, procfs::template::{FileOps, ProcFileBuilder},
utils::{Inode, mkmod}, utils::{AccessMode, Inode, InodeIo, StatusFlags, mkmod},
}, },
prelude::*, 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}, vm::vmar::{VMAR_CAP_ADDR, VMAR_LOWEST_ADDR},
}; };
@ -28,12 +37,44 @@ impl MapsFileOps {
} }
impl FileOps for 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 mut printer = VmPrinter::new_skip(writer, offset);
let vmar_guard = self.0.lock_vmar(); let vmar_guard = self.0.lock_vmar();
let Some(vmar) = vmar_guard.as_ref() else { 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!(); let current = current_thread!();
@ -47,4 +88,30 @@ impl FileOps for MapsFileOps {
Ok(printer.bytes_written()) 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 super::TidDirOps;
use crate::{ use crate::{
events::IoEvents,
fs::{ fs::{
inode_handle::FileIo,
procfs::template::{FileOps, ProcFileBuilder}, procfs::template::{FileOps, ProcFileBuilder},
utils::{Inode, mkmod}, utils::{AccessMode, Inode, InodeIo, StatusFlags, mkmod},
}, },
prelude::*, 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`). /// Represents the inode at `/proc/[pid]/task/[tid]/mem` (and also `/proc/[pid]/mem`).
@ -25,10 +34,49 @@ impl MemFileOps {
} }
impl FileOps for 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 vmar_guard = self.0.lock_vmar();
let Some(vmar) = vmar_guard.as_ref() else { 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) { match vmar.read_remote(offset, writer) {
Ok(bytes) => Ok(bytes), 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 vmar_guard = self.0.lock_vmar();
let Some(vmar) = vmar_guard.as_ref() else { 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) { match vmar.write_remote(offset, reader) {
Ok(bytes) => Ok(bytes), 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 super::{Common, ProcFs};
use crate::{ use crate::{
fs::utils::{ fs::{
Extension, FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata, StatusFlags, inode_handle::FileIo,
SymbolicLink, utils::{
AccessMode, Extension, FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata,
StatusFlags, SymbolicLink,
},
}, },
prelude::*, prelude::*,
process::{Gid, Uid}, 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. // Seeking regular files under `/proc` with `SEEK_END` will fail.
None 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 { 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> { fn write_at(&self, _offset: usize, _reader: &mut VmReader) -> Result<usize> {
return_errno_with_message!(Errno::EPERM, "the file is not writable"); 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 = let mut thread_builder =
PosixThreadBuilder::new(child_tid, thread_name, child_user_ctx, credentials) 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) .sig_mask(sig_mask)
.file_table(child_file_table) .file_table(child_file_table)
.fs(child_fs) .fs(child_fs)

View File

@ -30,6 +30,7 @@ mod exit;
pub mod futex; pub mod futex;
mod name; mod name;
mod posix_thread_ext; mod posix_thread_ext;
pub mod ptrace;
mod robust_list; mod robust_list;
mod thread_local; mod thread_local;
pub mod thread_table; pub mod thread_table;
@ -97,8 +98,8 @@ impl PosixThread {
self.process.upgrade().unwrap() self.process.upgrade().unwrap()
} }
pub fn weak_process(&self) -> Weak<Process> { pub fn weak_process(&self) -> &Weak<Process> {
Weak::clone(&self.process) &self.process
} }
/// Returns the thread id /// 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::{ use crate::{
fs::file_table::{FdFlags, FileDesc, get_file_fast}, fs::file_table::{FdFlags, FileDesc, get_file_fast},
prelude::*, prelude::*,
process::{PidFile, credentials::capabilities::CapSet, posix_thread::AsPosixThread}, process::{
PidFile,
posix_thread::{
AsPosixThread,
ptrace::{PtraceMode, check_may_access},
},
},
syscall::SyscallReturn, syscall::SyscallReturn,
}; };
@ -32,22 +38,11 @@ pub fn sys_pidfd_getfd(
.process_opt() .process_opt()
.ok_or_else(|| Error::with_message(Errno::ESRCH, "the target process has been reaped"))?; .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)) check_may_access(
// over the process referred to by `pidfd`. ctx.posix_thread,
// Currently, this is implemented as requiring the calling process to have the process.main_thread().as_posix_thread().unwrap(),
// CAP_SYS_PTRACE capability, which is stricter. PtraceMode::ATTACH_REALCREDS,
// 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"
);
}
let main_thread = process.main_thread(); let main_thread = process.main_thread();

View File

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