Add ptrace access mode checks
This commit is contained in:
parent
bf7709f345
commit
a6abae2fbe
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue