use core::mem::{offset_of, size_of};
use ax_io::Read;
use linux_raw_sys::general::{__user_cap_data_struct, __user_cap_header_struct, CAP_LAST_CAP};
use crate::{
StarryError, StarryResult,
mm::{TransparentHugePageMode, UserPtr, VmBytes, VmMutPtr, VmPtr, vm_write_slice},
task::{Cred, TidNumber, get_user_task_by_number},
};
const CAPABILITY_VERSION_3: u32 = 0x20080522;
const CAP_U32S_3: usize = 2;
const PERSONALITY_GET: u32 = 0xffff_ffff;
const PR_THP_DISABLE_EXCEPT_ADVISED: usize = 1 << 1;
const SECUREBITS_VALID_MASK: u32 = 0xff;
const SECUREBITS_LOCK_MASK: u32 = 0xaa;
const SECBIT_NO_CAP_AMBIENT_RAISE: u32 = 1 << 6;
const MPOL_DEFAULT: i32 = 0;
const MPOL_PREFERRED: i32 = 1;
const MPOL_BIND: i32 = 2;
const MPOL_INTERLEAVE: i32 = 3;
const MPOL_LOCAL: i32 = 4;
const MPOL_PREFERRED_MANY: i32 = 5;
const MPOL_WEIGHTED_INTERLEAVE: i32 = 6;
const MPOL_F_NODE: usize = 1 << 0;
const MPOL_F_ADDR: usize = 1 << 1;
const MPOL_F_MEMS_ALLOWED: usize = 1 << 2;
const MPOL_F_STATIC_NODES: i32 = 1 << 15;
const MPOL_F_RELATIVE_NODES: i32 = 1 << 14;
const MPOL_MODE_FLAGS: i32 = MPOL_F_STATIC_NODES | MPOL_F_RELATIVE_NODES;
const MPOL_MF_STRICT: u32 = 1 << 0;
const MPOL_MF_MOVE: u32 = 1 << 1;
const MPOL_MF_MOVE_ALL: u32 = 1 << 2;
const MPOL_MF_VALID: u32 = MPOL_MF_STRICT | MPOL_MF_MOVE | MPOL_MF_MOVE_ALL;
fn parse_mempolicy_mode(mode: i32) -> StarryResult<i32> {
if mode < 0 {
return Err(StarryError::InvalidInput);
}
let policy = mode & !MPOL_MODE_FLAGS;
match policy {
MPOL_DEFAULT
| MPOL_PREFERRED
| MPOL_BIND
| MPOL_INTERLEAVE
| MPOL_LOCAL
| MPOL_PREFERRED_MANY
| MPOL_WEIGHTED_INTERLEAVE => Ok(policy),
_ => Err(StarryError::InvalidInput),
}
}
fn nodemask_requires_access(nodemask: *const usize, maxnode: usize) -> bool {
!nodemask.is_null() && maxnode > 0
}
fn check_nodemask(
current: &crate::task::UserTaskRef,
nodemask: *const usize,
maxnode: usize,
) -> crate::StarryResult<()> {
if nodemask_requires_access(nodemask, maxnode) {
nodemask.vm_read(current)?;
}
Ok(())
}
fn validate_mbind_request(
addr: usize,
len: usize,
mode: i32,
flags: u32,
) -> crate::StarryResult<i32> {
let policy = parse_mempolicy_mode(mode)?;
if addr & 0xfff != 0 || len == 0 || flags & !MPOL_MF_VALID != 0 {
return Err(crate::StarryError::InvalidInput);
}
Ok(policy)
}
#[derive(Clone, Copy)]
enum CapabilityTarget {
Current,
Thread(TidNumber),
}
fn validate_cap_header(
current: &crate::task::UserTaskRef,
header_ptr: *mut __user_cap_header_struct,
) -> StarryResult<CapabilityTarget> {
let mut header = unsafe { header_ptr.vm_read_uninit(current)?.assume_init() };
if header.version != CAPABILITY_VERSION_3 {
header.version = CAPABILITY_VERSION_3;
UserPtr::<__user_cap_header_struct>::from(header_ptr).write_field(
current,
offset_of!(__user_cap_header_struct, version),
header.version,
)?;
return Err(crate::StarryError::InvalidInput);
}
if header.pid < 0 {
return Err(StarryError::InvalidInput);
}
if header.pid == 0 {
Ok(CapabilityTarget::Current)
} else {
let tid = TidNumber::try_from(header.pid as u32)?;
let _ = get_user_task_by_number(tid)?;
Ok(CapabilityTarget::Thread(tid))
}
}
fn cred_for_target(
current: &crate::task::UserTaskRef,
target: CapabilityTarget,
) -> StarryResult<alloc::sync::Arc<Cred>> {
let CapabilityTarget::Thread(tid) = target else {
return Ok(current.as_thread().cred());
};
let task = get_user_task_by_number(tid).map_err(|_| StarryError::NoSuchProcess)?;
Ok(task.as_thread().cred())
}
fn cap_bit(cap: u32) -> StarryResult<u64> {
if cap > CAP_LAST_CAP {
return Err(StarryError::InvalidInput);
}
Ok(1u64 << cap)
}
fn data_to_mask(
data: &[__user_cap_data_struct; CAP_U32S_3],
f: fn(&__user_cap_data_struct) -> u32,
) -> u64 {
u64::from(f(&data[0])) | (u64::from(f(&data[1])) << 32)
}
fn cap_data_from_cred(cred: &Cred) -> [__user_cap_data_struct; CAP_U32S_3] {
[
__user_cap_data_struct {
effective: cred.cap_effective as u32,
permitted: cred.cap_permitted as u32,
inheritable: cred.cap_inheritable as u32,
},
__user_cap_data_struct {
effective: (cred.cap_effective >> 32) as u32,
permitted: (cred.cap_permitted >> 32) as u32,
inheritable: (cred.cap_inheritable >> 32) as u32,
},
]
}
fn write_cap_data(
current: &crate::task::UserTaskRef,
user: UserPtr<__user_cap_data_struct>,
value: __user_cap_data_struct,
) -> crate::StarryResult<()> {
user.write_field(
current,
offset_of!(__user_cap_data_struct, effective),
value.effective,
)?;
user.write_field(
current,
offset_of!(__user_cap_data_struct, permitted),
value.permitted,
)?;
user.write_field(
current,
offset_of!(__user_cap_data_struct, inheritable),
value.inheritable,
)
}
pub fn sys_capget(
current: &crate::task::UserTaskRef,
header: *mut __user_cap_header_struct,
data: *mut __user_cap_data_struct,
) -> StarryResult<isize> {
let target = validate_cap_header(current, header)?;
if data.is_null() {
return Ok(0);
}
let cred = cred_for_target(current, target)?;
let cap_data = cap_data_from_cred(&cred);
let first = UserPtr::from(data);
let second_address = data
.addr()
.checked_add(size_of::<__user_cap_data_struct>())
.ok_or(crate::StarryError::BadAddress)?;
write_cap_data(current, first, cap_data[0])?;
write_cap_data(current, UserPtr::from(second_address), cap_data[1])?;
Ok(0)
}
pub fn sys_capset(
current: &crate::task::UserTaskRef,
header: *mut __user_cap_header_struct,
data: *mut __user_cap_data_struct,
) -> StarryResult<isize> {
let target = validate_cap_header(current, header)?;
if data.is_null() {
return Err(StarryError::BadAddress);
}
let thread_ref = current;
let thread = thread_ref.as_thread();
if matches!(target, CapabilityTarget::Thread(tid) if tid != thread.tid_number()) {
return Err(StarryError::OperationNotPermitted);
}
let requested = unsafe {
[
data.vm_read_uninit(current)?.assume_init(),
data.add(1).vm_read_uninit(current)?.assume_init(),
]
};
let old = thread.cred();
let cap_mask = Cred::cap_mask();
let effective = data_to_mask(&requested, |d| d.effective) & cap_mask;
let permitted = data_to_mask(&requested, |d| d.permitted) & cap_mask;
let inheritable = data_to_mask(&requested, |d| d.inheritable) & cap_mask;
if effective & !permitted != 0 {
return Err(StarryError::OperationNotPermitted);
}
let adds_permitted = permitted & !old.cap_permitted;
let adds_inheritable = inheritable & !old.cap_inheritable;
let may_expand = old.has_cap_setpcap();
if adds_permitted != 0 {
return Err(StarryError::OperationNotPermitted);
}
if may_expand {
if adds_inheritable & !old.cap_bounding != 0 {
return Err(StarryError::OperationNotPermitted);
}
} else if adds_inheritable & !(old.cap_inheritable | old.cap_permitted) != 0 {
return Err(StarryError::OperationNotPermitted);
}
let mut new = (*old).clone();
new.cap_effective = effective;
new.cap_permitted = permitted;
new.cap_inheritable = inheritable;
new.sanitize_capabilities();
thread.set_cred(new);
Ok(0)
}
pub fn sys_umask(current: &crate::task::UserTaskRef, mask: u32) -> crate::StarryResult<isize> {
let curr = current;
let old = curr.as_thread().proc_data.replace_umask(mask & 0o777);
Ok(old as isize)
}
pub fn sys_personality(
current: &crate::task::UserTaskRef,
persona: u32,
) -> crate::StarryResult<isize> {
let curr = current;
let proc_data = &curr.as_thread().proc_data;
let old = proc_data.personality();
if persona != PERSONALITY_GET {
proc_data.replace_personality(persona as usize);
}
Ok(old as isize)
}
pub fn sys_get_mempolicy(
current: &crate::task::UserTaskRef,
policy: *mut i32,
nodemask: *mut usize,
maxnode: usize,
_addr: usize,
flags: usize,
) -> StarryResult<isize> {
debug!(
"sys_get_mempolicy <= policy: {:?}, nodemask: {:?}, maxnode: {}, flags: {:#x}",
policy, nodemask, maxnode, flags
);
if flags & !(MPOL_F_NODE | MPOL_F_ADDR | MPOL_F_MEMS_ALLOWED) != 0 {
return Err(StarryError::InvalidInput);
}
if flags & MPOL_F_MEMS_ALLOWED != 0 && flags != MPOL_F_MEMS_ALLOWED {
return Err(StarryError::InvalidInput);
}
if flags & MPOL_F_NODE != 0 && flags & MPOL_F_ADDR == 0 {
return Err(StarryError::InvalidInput);
}
if flags & MPOL_F_MEMS_ALLOWED != 0 {
if !nodemask.is_null() && maxnode > 0 {
nodemask.vm_write(current, 1usize)?;
}
return Ok(0);
}
if flags & MPOL_F_NODE != 0 {
if !policy.is_null() {
policy.vm_write(current, 0i32)?;
}
return Ok(0);
}
if !policy.is_null() {
policy.vm_write(current, MPOL_DEFAULT)?;
}
if !nodemask.is_null() && maxnode > 0 {
nodemask.vm_write(current, 1usize)?;
}
Ok(0)
}
pub fn sys_set_mempolicy(
current: &crate::task::UserTaskRef,
mode: i32,
nodemask: *const usize,
maxnode: usize,
) -> crate::StarryResult<isize> {
debug!("sys_set_mempolicy <= mode: {}", mode);
let policy = parse_mempolicy_mode(mode)?;
if policy != MPOL_DEFAULT {
check_nodemask(current, nodemask, maxnode)?;
}
Ok(0)
}
pub fn sys_mbind(
current: &crate::task::UserTaskRef,
addr: usize,
len: usize,
mode: i32,
nodemask: *const usize,
maxnode: usize,
flags: u32,
) -> StarryResult<isize> {
debug!("sys_mbind <= mode: {}", mode);
let policy = validate_mbind_request(addr, len, mode, flags)?;
if policy != MPOL_DEFAULT {
check_nodemask(current, nodemask, maxnode)?;
}
Ok(0)
}
pub fn sys_prctl(
current: &crate::task::UserTaskRef,
option: u32,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
) -> StarryResult<isize> {
use linux_raw_sys::prctl::*;
debug!("sys_prctl <= option: {option}, args: {arg2}, {arg3}, {arg4}, {arg5}");
match option {
PR_SET_NAME => {
let mut name = [0u8; 15];
let mut user_name = VmBytes::new(current, arg2 as *const u8, name.len());
let mut len = 0;
while len < name.len() {
user_name.read_exact(&mut name[len..=len])?;
if name[len] == 0 {
break;
}
len += 1;
}
let name = core::str::from_utf8(&name[..len]).map_err(|_| StarryError::IllegalBytes)?;
current.set_name(name);
}
PR_GET_NAME => {
let name = current.name();
let len = name.len().min(15);
let mut buf = [0; 16];
buf[..len].copy_from_slice(&name.as_bytes()[..len]);
vm_write_slice(current, arg2 as _, &buf)?;
}
PR_SET_PDEATHSIG => {
let sig = arg2 as u32;
if sig > 64 {
return Err(StarryError::InvalidInput);
}
current.as_thread().set_pdeathsig(sig);
}
PR_GET_PDEATHSIG => {
let sig = current.as_thread().pdeathsig() as i32;
(arg2 as *mut i32).vm_write(current, sig)?;
}
PR_SET_CHILD_SUBREAPER => {
current
.as_thread()
.proc_data
.proc
.set_child_subreaper(arg2 != 0);
}
PR_GET_CHILD_SUBREAPER => {
let enabled = if current.as_thread().proc_data.proc.is_child_subreaper() {
1
} else {
0
};
(arg2 as *mut i32).vm_write(current, enabled)?;
}
PR_GET_KEEPCAPS => {
return Ok(current.as_thread().cred().keep_capabilities() as isize);
}
PR_SET_KEEPCAPS => {
if arg2 > 1 {
return Err(StarryError::InvalidInput);
}
let thread = current.as_thread();
let mut new = (*thread.cred()).clone();
new.set_keep_capabilities(arg2 != 0);
thread.set_thread_cred(new);
}
PR_CAPBSET_READ => {
if arg2 > CAP_LAST_CAP as usize {
return Err(StarryError::InvalidInput);
}
let bit = cap_bit(arg2 as u32)?;
let cred = current.as_thread().cred();
return Ok(((cred.cap_bounding & bit) != 0) as isize);
}
PR_CAPBSET_DROP => {
if arg2 > CAP_LAST_CAP as usize {
return Err(StarryError::InvalidInput);
}
let thread_ref = current;
let thread = thread_ref.as_thread();
let old = thread.cred();
if !old.has_cap_setpcap() {
return Err(StarryError::OperationNotPermitted);
}
let bit = cap_bit(arg2 as u32)?;
let mut new = (*old).clone();
new.cap_bounding &= !bit;
new.cap_ambient &= !bit;
new.sanitize_capabilities();
thread.set_cred(new);
}
PR_CAP_AMBIENT => {
let thread_ref = current;
let thread = thread_ref.as_thread();
let old = thread.cred();
match arg2 as u32 {
PR_CAP_AMBIENT_IS_SET => {
if arg3 > CAP_LAST_CAP as usize || arg4 != 0 || arg5 != 0 {
return Err(StarryError::InvalidInput);
}
let bit = cap_bit(arg3 as u32)?;
return Ok(((old.cap_ambient & bit) != 0) as isize);
}
PR_CAP_AMBIENT_RAISE => {
if arg3 > CAP_LAST_CAP as usize || arg4 != 0 || arg5 != 0 {
return Err(StarryError::InvalidInput);
}
let bit = cap_bit(arg3 as u32)?;
if old.securebits & SECBIT_NO_CAP_AMBIENT_RAISE != 0
|| old.cap_permitted & bit == 0
|| old.cap_inheritable & bit == 0
{
return Err(StarryError::OperationNotPermitted);
}
let mut new = (*old).clone();
new.cap_ambient |= bit;
new.sanitize_capabilities();
thread.set_cred(new);
}
PR_CAP_AMBIENT_LOWER => {
if arg3 > CAP_LAST_CAP as usize || arg4 != 0 || arg5 != 0 {
return Err(StarryError::InvalidInput);
}
let bit = cap_bit(arg3 as u32)?;
let mut new = (*old).clone();
new.cap_ambient &= !bit;
thread.set_cred(new);
}
PR_CAP_AMBIENT_CLEAR_ALL => {
if arg3 != 0 || arg4 != 0 || arg5 != 0 {
return Err(StarryError::InvalidInput);
}
let mut new = (*old).clone();
new.cap_ambient = 0;
thread.set_cred(new);
}
_ => return Err(StarryError::InvalidInput),
}
}
PR_GET_SECUREBITS => {
return Ok(current.as_thread().cred().securebits as isize);
}
PR_SET_SECUREBITS => {
if arg2 > SECUREBITS_VALID_MASK as usize {
return Err(StarryError::InvalidInput);
}
let thread = current.as_thread();
let old = thread.cred();
if !old.has_cap_setpcap() {
return Err(StarryError::OperationNotPermitted);
}
let requested = arg2 as u32;
let locked = old.securebits & SECUREBITS_LOCK_MASK;
let locked_values = locked >> 1;
if requested & locked != locked || (requested ^ old.securebits) & locked_values != 0 {
return Err(StarryError::OperationNotPermitted);
}
let mut new = (*old).clone();
new.securebits = requested;
thread.set_cred(new);
}
PR_GET_DUMPABLE => {
return Ok(current.as_thread().proc_data.dumpable() as isize);
}
PR_SET_DUMPABLE => {
if arg2 != 0 && arg2 != 1 {
return Err(StarryError::InvalidInput);
}
current.as_thread().proc_data.set_dumpable(arg2 as i32);
}
PR_SET_SECCOMP => {
if arg4 != 0 || arg5 != 0 {
return Err(StarryError::InvalidInput);
}
crate::syscall::sys_seccomp(current, arg2 as u32, 0, arg3 as *const ())?;
}
PR_MCE_KILL => {}
PR_SET_NO_NEW_PRIVS => {
if arg2 != 1 || arg3 != 0 || arg4 != 0 || arg5 != 0 {
return Err(StarryError::InvalidInput);
}
current.as_thread().set_no_new_privs();
}
PR_GET_NO_NEW_PRIVS => {
return Ok(current.as_thread().no_new_privs() as isize);
}
PR_SET_THP_DISABLE => {
if arg4 != 0 || arg5 != 0 {
return Err(StarryError::InvalidInput);
}
let mode = match (arg2, arg3) {
(0, 0) => TransparentHugePageMode::Enabled,
(0, _) => return Err(StarryError::InvalidInput),
(_, 0) => TransparentHugePageMode::Disabled,
(_, PR_THP_DISABLE_EXCEPT_ADVISED) => TransparentHugePageMode::ExceptAdvised,
_ => return Err(StarryError::InvalidInput),
};
current
.as_thread()
.proc_data
.set_transparent_huge_page_mode(mode)?;
}
PR_GET_THP_DISABLE => {
if arg2 != 0 || arg3 != 0 || arg4 != 0 || arg5 != 0 {
return Err(StarryError::InvalidInput);
}
return Ok(current
.as_thread()
.proc_data
.transparent_huge_page_mode()
.prctl_value() as isize);
}
PR_SET_MM => {
return Err(StarryError::InvalidInput);
}
PR_SET_VMA => {
if arg2 == PR_SET_VMA_ANON_NAME as usize {
return Ok(0);
}
return Err(StarryError::InvalidInput);
}
_ => {
warn!("sys_prctl: unsupported option {option}");
return Err(StarryError::InvalidInput);
}
}
Ok(0)
}
#[cfg(all(test, not(axtest)))]
fn mempolicy_validation_rules_hold_for_test() -> bool {
matches!(parse_mempolicy_mode(MPOL_DEFAULT), Ok(MPOL_DEFAULT))
&& matches!(
parse_mempolicy_mode(MPOL_BIND | MPOL_F_STATIC_NODES),
Ok(MPOL_BIND)
)
&& matches!(
parse_mempolicy_mode(MPOL_INTERLEAVE | MPOL_F_RELATIVE_NODES),
Ok(MPOL_INTERLEAVE)
)
&& parse_mempolicy_mode(-1).is_err()
&& parse_mempolicy_mode(99).is_err()
&& matches!(parse_mempolicy_mode(MPOL_PREFERRED), Ok(MPOL_PREFERRED))
&& matches!(parse_mempolicy_mode(MPOL_LOCAL), Ok(MPOL_LOCAL))
&& matches!(
parse_mempolicy_mode(MPOL_PREFERRED_MANY),
Ok(MPOL_PREFERRED_MANY)
)
&& matches!(
parse_mempolicy_mode(MPOL_WEIGHTED_INTERLEAVE),
Ok(MPOL_WEIGHTED_INTERLEAVE)
)
&& matches!(
parse_mempolicy_mode(MPOL_BIND | MPOL_F_RELATIVE_NODES | MPOL_F_STATIC_NODES),
Ok(MPOL_BIND)
)
&& matches!(
parse_mempolicy_mode(MPOL_PREFERRED | MPOL_F_RELATIVE_NODES),
Ok(MPOL_PREFERRED)
)
&& parse_mempolicy_mode(7).is_err()
&& !nodemask_requires_access(core::ptr::null(), 0)
&& !nodemask_requires_access(core::ptr::null(), 64)
&& !nodemask_requires_access(core::ptr::dangling(), 0)
&& matches!(
validate_mbind_request(0x1000, 4096, MPOL_DEFAULT, 0),
Ok(MPOL_DEFAULT)
)
&& validate_mbind_request(0x1001, 4096, MPOL_DEFAULT, 0).is_err()
&& validate_mbind_request(0x1000, 0, MPOL_DEFAULT, 0).is_err()
&& validate_mbind_request(0x1000, 4096, MPOL_DEFAULT, !MPOL_MF_VALID).is_err()
&& matches!(
validate_mbind_request(0x1000, 4096, MPOL_DEFAULT, MPOL_MF_STRICT),
Ok(MPOL_DEFAULT)
)
&& matches!(
validate_mbind_request(0x1000, 4096, MPOL_DEFAULT, MPOL_MF_MOVE),
Ok(MPOL_DEFAULT)
)
&& matches!(
validate_mbind_request(0x1000, 4096, MPOL_DEFAULT, MPOL_MF_MOVE_ALL),
Ok(MPOL_DEFAULT)
)
&& validate_mbind_request(0x1000, 4096, 99, 0).is_err()
}
#[cfg(all(test, not(axtest)))]
fn capability_data_conversion_rules_hold_for_test() -> bool {
use alloc::sync::Arc;
matches!(cap_bit(0), Ok(value) if value == 1u64 << 0)
&& matches!(cap_bit(1), Ok(value) if value == 1u64 << 1)
&& matches!(cap_bit(CAP_LAST_CAP), Ok(value) if value == 1u64 << CAP_LAST_CAP)
&& matches!(cap_bit(CAP_LAST_CAP + 1), Err(StarryError::InvalidInput))
&& data_to_mask(
&[
__user_cap_data_struct {
effective: 0x1111_1111,
permitted: 0x2222_2222,
inheritable: 0x3333_3333,
},
__user_cap_data_struct {
effective: 0x4444_4444,
permitted: 0x5555_5555,
inheritable: 0x6666_6666,
},
],
|d| d.effective,
) == (0x1111_1111u64 | ((0x4444_4444u64) << 32))
&& {
let mut cred = Cred::root();
cred.groups = Arc::from([].as_slice());
cred.cap_inheritable = 0x1234_5678_9abc_def0;
cred.cap_permitted = 0xfedc_ba98_7654_3210;
cred.cap_effective = 0x0fed_cba9_8765_4321;
cred.cap_bounding = u64::MAX;
cred.cap_ambient = 0;
let data = cap_data_from_cred(&cred);
data[0].effective == 0x8765_4321
&& data[1].effective == 0x0fed_cba9
&& data[0].permitted == 0x7654_3210
&& data[1].permitted == 0xfedc_ba98
&& data[0].inheritable == 0x9abc_def0
&& data[1].inheritable == 0x1234_5678
}
}
#[cfg(all(test, not(axtest)))]
mod tests {
#[test]
fn mempolicy_validation_rules_hold() {
assert!(super::mempolicy_validation_rules_hold_for_test());
}
#[test]
fn capability_data_conversion_rules_hold() {
assert!(super::capability_data_conversion_rules_hold_for_test());
}
}