use core::ffi::c_char;
use ax_errno::{AxError, AxResult};
use ax_task::current;
use linux_raw_sys::general::{__user_cap_data_struct, __user_cap_header_struct, CAP_LAST_CAP};
use starry_vm::{VmMutPtr, VmPtr, vm_write_slice};
use crate::{
mm::vm_load_string,
task::{AsThread, Cred, get_process_data, get_task},
};
const CAPABILITY_VERSION_3: u32 = 0x20080522;
const PERSONALITY_GET: u32 = 0xffff_ffff;
const PR_THP_DISABLE_EXCEPT_ADVISED: usize = 1 << 1;
fn validate_cap_header(header_ptr: *mut __user_cap_header_struct) -> AxResult<u32> {
let mut header = unsafe { header_ptr.vm_read_uninit()?.assume_init() };
if header.version != CAPABILITY_VERSION_3 {
header.version = CAPABILITY_VERSION_3;
header_ptr.vm_write(header)?;
return Err(AxError::InvalidInput);
}
let pid = header.pid as u32;
let _ = get_process_data(pid)?;
Ok(pid)
}
fn cred_for_pid(pid: u32) -> AxResult<alloc::sync::Arc<Cred>> {
if pid == 0 {
return Ok(current().as_thread().cred());
}
let task = get_task(pid).map_err(|_| AxError::NoSuchProcess)?;
task.try_as_thread()
.map(|t| t.cred())
.ok_or(AxError::NoSuchProcess)
}
pub fn sys_capget(
header: *mut __user_cap_header_struct,
data: *mut __user_cap_data_struct,
) -> AxResult<isize> {
let pid = validate_cap_header(header)?;
if data.is_null() {
return Ok(0);
}
let cred = cred_for_pid(pid)?;
let caps = if cred.euid == 0 { u32::MAX } else { 0 };
let data_struct = __user_cap_data_struct {
effective: caps,
permitted: caps,
inheritable: caps,
};
unsafe {
data.vm_write(data_struct)?;
data.add(1).vm_write(data_struct)?;
}
Ok(0)
}
pub fn sys_capset(
header: *mut __user_cap_header_struct,
_data: *mut __user_cap_data_struct,
) -> AxResult<isize> {
let _ = validate_cap_header(header)?;
let cred = current().as_thread().cred();
if cred.euid != 0 {
return Err(AxError::OperationNotPermitted);
}
Ok(0)
}
pub fn sys_umask(mask: u32) -> AxResult<isize> {
let curr = current();
let old = curr.as_thread().proc_data.replace_umask(mask & 0o777);
Ok(old as isize)
}
pub fn sys_personality(persona: usize) -> AxResult<isize> {
let curr = current();
let proc_data = &curr.as_thread().proc_data;
let old = proc_data.personality();
if persona as u32 != PERSONALITY_GET {
proc_data.replace_personality(persona);
}
Ok(old as isize)
}
pub fn sys_get_mempolicy(
_policy: *mut i32,
_nodemask: *mut usize,
_maxnode: usize,
_addr: usize,
_flags: usize,
) -> AxResult<isize> {
warn!("Dummy get_mempolicy called");
Ok(0)
}
pub fn sys_prctl(
option: u32,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
) -> AxResult<isize> {
use linux_raw_sys::prctl::*;
debug!("sys_prctl <= option: {option}, args: {arg2}, {arg3}, {arg4}, {arg5}");
match option {
PR_SET_NAME => {
let s = vm_load_string(arg2 as *const c_char)?;
current().set_name(&s);
}
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(arg2 as _, &buf)?;
}
PR_SET_PDEATHSIG => {
let sig = arg2 as u32;
if sig > 64 {
return Err(AxError::InvalidInput);
}
current().as_thread().set_pdeathsig(sig);
}
PR_GET_PDEATHSIG => {
let sig = current().as_thread().pdeathsig() as i32;
(arg2 as *mut i32).vm_write(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(enabled)?;
}
PR_CAPBSET_READ => {
if arg2 > CAP_LAST_CAP as usize {
return Err(AxError::InvalidInput);
}
return Ok(1);
}
PR_GET_DUMPABLE => {
return Ok(current().as_thread().proc_data.dumpable() as isize);
}
PR_SET_DUMPABLE => {
if arg2 != 0 && arg2 != 1 {
return Err(AxError::InvalidInput);
}
current().as_thread().proc_data.set_dumpable(arg2 as i32);
}
PR_SET_SECCOMP => {}
PR_MCE_KILL => {}
PR_SET_NO_NEW_PRIVS => {
if arg2 != 1 || arg3 != 0 || arg4 != 0 || arg5 != 0 {
return Err(AxError::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(AxError::InvalidInput);
}
let thp_disable = match (arg2, arg3) {
(0, 0) => 0,
(0, _) => return Err(AxError::InvalidInput),
(_, 0) => 1,
(_, PR_THP_DISABLE_EXCEPT_ADVISED) => 1 | PR_THP_DISABLE_EXCEPT_ADVISED,
_ => return Err(AxError::InvalidInput),
};
current()
.as_thread()
.proc_data
.set_thp_disable(thp_disable as u32);
}
PR_GET_THP_DISABLE => {
if arg2 != 0 || arg3 != 0 || arg4 != 0 || arg5 != 0 {
return Err(AxError::InvalidInput);
}
return Ok(current().as_thread().proc_data.thp_disable() as isize);
}
PR_SET_MM => {
return Err(AxError::InvalidInput);
}
PR_SET_VMA => {
if arg2 == PR_SET_VMA_ANON_NAME as usize {
return Ok(0);
}
return Err(AxError::InvalidInput);
}
_ => {
warn!("sys_prctl: unsupported option {option}");
return Err(AxError::InvalidInput);
}
}
Ok(0)
}