use alloc::{sync::Arc, vec, vec::Vec};
use core::{
ffi::c_char,
mem::{MaybeUninit, offset_of},
};
use ax_hal::mem::PAGE_SIZE_4K;
use ax_lazyinit::LazyLock;
use linux_raw_sys::{
general::{GRND_INSECURE, GRND_NONBLOCK, GRND_RANDOM},
system::{new_utsname, sysinfo},
};
use ringbuf::{
HeapRb,
traits::{Consumer, Observer, Producer},
};
use crate::{
Errno, StarryError, StarryResult,
mm::{UserPtr, VmMutPtr, VmPtr, vm_read_slice, vm_write_slice},
sync::Mutex,
task::{SockFilter, SockFprog, get_task_by_number, processes},
};
const NOCHG: u32 = u32::MAX;
const SYSLOG_ACTION_CLOSE: i32 = 0;
const SYSLOG_ACTION_OPEN: i32 = 1;
const SYSLOG_ACTION_READ: i32 = 2;
const SYSLOG_ACTION_READ_ALL: i32 = 3;
const SYSLOG_ACTION_READ_CLEAR: i32 = 4;
const SYSLOG_ACTION_CLEAR: i32 = 5;
const SYSLOG_ACTION_CONSOLE_OFF: i32 = 6;
const SYSLOG_ACTION_CONSOLE_ON: i32 = 7;
const SYSLOG_ACTION_CONSOLE_LEVEL: i32 = 8;
const SYSLOG_ACTION_SIZE_UNREAD: i32 = 9;
const SYSLOG_ACTION_SIZE_BUFFER: i32 = 10;
const SYSLOG_BUFFER_CAPACITY: usize = 4096;
const SYSLOG_SEED_MESSAGE: &[u8] = b"StarryOS kernel log buffer initialized\n";
const GETRANDOM_MAX_LEN: usize = (i32::MAX as usize) & !(PAGE_SIZE_4K - 1);
const GETRANDOM_CHUNK_SIZE: usize = 256;
const SECCOMP_SET_MODE_STRICT: u32 = 0;
const SECCOMP_SET_MODE_FILTER: u32 = 1;
const SECCOMP_GET_ACTION_AVAIL: u32 = 2;
const SECCOMP_FILTER_FLAG_TSYNC: u32 = 1 << 0;
const SECCOMP_FILTER_FLAG_LOG: u32 = 1 << 1;
const SECCOMP_FILTER_FLAG_SPEC_ALLOW: u32 = 1 << 2;
const SECCOMP_FILTER_FLAG_TSYNC_ESRCH: u32 = 1 << 4;
const SECCOMP_ALLOWED_FLAGS: u32 = SECCOMP_FILTER_FLAG_TSYNC
| SECCOMP_FILTER_FLAG_LOG
| SECCOMP_FILTER_FLAG_SPEC_ALLOW
| SECCOMP_FILTER_FLAG_TSYNC_ESRCH;
const SECCOMP_RET_KILL_PROCESS: u32 = 0x8000_0000;
const SECCOMP_RET_KILL_THREAD: u32 = 0x0000_0000;
const SECCOMP_RET_ERRNO: u32 = 0x0005_0000;
const SECCOMP_RET_LOG: u32 = 0x7ffc_0000;
const SECCOMP_RET_ALLOW: u32 = 0x7fff_0000;
const LINUX_REBOOT_MAGIC1: u32 = 0xfee1_dead;
const LINUX_REBOOT_MAGIC2: u32 = 0x2812_1969;
const LINUX_REBOOT_MAGIC2A: u32 = 0x0512_1996;
const LINUX_REBOOT_MAGIC2B: u32 = 0x1604_1998;
const LINUX_REBOOT_MAGIC2C: u32 = 0x2011_2000;
const LINUX_REBOOT_CMD_RESTART: u32 = 0x0123_4567;
const LINUX_REBOOT_CMD_RESTART2: u32 = 0xa1b2_c3d4;
const LINUX_REBOOT_CMD_CAD_ON: u32 = 0x89ab_cdef;
const LINUX_REBOOT_CMD_CAD_OFF: u32 = 0x0000_0000;
const LINUX_REBOOT_CMD_HALT: u32 = 0xcdef_0123;
const LINUX_REBOOT_CMD_POWER_OFF: u32 = 0x4321_fedc;
struct SyslogState {
buffer: HeapRb<u8>,
console_enabled: bool,
console_level: usize,
}
impl SyslogState {
fn new() -> Self {
let mut buffer = HeapRb::new(SYSLOG_BUFFER_CAPACITY);
buffer.push_slice(SYSLOG_SEED_MESSAGE);
Self {
buffer,
console_enabled: true,
console_level: 7,
}
}
fn unread_len(&self) -> usize {
self.buffer.occupied_len()
}
fn buffer_len(&self) -> usize {
self.buffer.capacity().get()
}
fn read(&mut self, len: usize) -> Vec<u8> {
let available = len.min(self.buffer.occupied_len());
let (left, right) = self.buffer.as_slices();
let mut out = Vec::with_capacity(available);
let first = left.len().min(available);
out.extend_from_slice(&left[..first]);
if first < available {
out.extend_from_slice(&right[..available - first]);
}
unsafe { self.buffer.advance_read_index(available) };
out
}
fn read_all(&self, len: usize) -> Vec<u8> {
let available = len.min(self.buffer.occupied_len());
let (left, right) = self.buffer.as_slices();
let mut out = Vec::with_capacity(available);
let first = left.len().min(available);
out.extend_from_slice(&left[..first]);
if first < available {
out.extend_from_slice(&right[..available - first]);
}
out
}
fn clear(&mut self) {
let len = self.buffer.occupied_len();
unsafe { self.buffer.advance_read_index(len) };
}
}
static SYSLOG_STATE: LazyLock<Mutex<SyslogState>> =
LazyLock::new(|| Mutex::new(SyslogState::new()));
pub fn sys_reboot(
current: &crate::task::UserTaskRef,
magic: u32,
magic2: u32,
cmd: u32,
_arg: usize,
) -> crate::StarryResult<isize> {
if !current.as_thread().cred().has_cap_sys_boot() {
return Err(crate::StarryError::from(crate::Errno::EPERM));
}
if magic != LINUX_REBOOT_MAGIC1
|| !matches!(
magic2,
LINUX_REBOOT_MAGIC2
| LINUX_REBOOT_MAGIC2A
| LINUX_REBOOT_MAGIC2B
| LINUX_REBOOT_MAGIC2C
)
{
return Err(StarryError::from(Errno::EINVAL));
}
match cmd {
LINUX_REBOOT_CMD_CAD_ON | LINUX_REBOOT_CMD_CAD_OFF => Ok(0),
LINUX_REBOOT_CMD_RESTART | LINUX_REBOOT_CMD_RESTART2 => {
ax_runtime::hal::power::system_reset()
}
LINUX_REBOOT_CMD_HALT | LINUX_REBOOT_CMD_POWER_OFF => ax_runtime::hal::power::system_off(),
_ => Err(StarryError::from(Errno::EINVAL)),
}
}
fn uid_valid(id: u32) -> bool {
id != NOCHG
}
#[inline]
fn dumpable_should_reset(old: &crate::task::Cred, new: &crate::task::Cred) -> bool {
old.euid != new.euid || old.egid != new.egid || old.fsuid != new.fsuid || old.fsgid != new.fsgid
}
fn commit_cred_with_id_rules(thread: &crate::task::Thread, new: crate::task::Cred) {
thread.update_process_creds(|old| {
let mut target = old.clone();
target.uid = new.uid;
target.gid = new.gid;
target.euid = new.euid;
target.egid = new.egid;
target.suid = new.suid;
target.sgid = new.sgid;
target.fsuid = new.fsuid;
target.fsgid = new.fsgid;
target.apply_id_change_capability_rules(old);
target
});
}
fn user_ns_overflow_uid(current: &crate::task::UserTaskRef) -> u32 {
let curr = current;
let nsproxy = curr.as_thread().proc_data.namespace_snapshot();
let ns = nsproxy.user_ns.lock();
if ns.is_root || ns.uid_mapped {
return 0;
}
65534
}
fn user_ns_overflow_gid(current: &crate::task::UserTaskRef) -> u32 {
let curr = current;
let nsproxy = curr.as_thread().proc_data.namespace_snapshot();
let ns = nsproxy.user_ns.lock();
if ns.is_root || ns.gid_mapped {
return 0;
}
65534
}
pub fn sys_getuid(current: &crate::task::UserTaskRef) -> crate::StarryResult<isize> {
let overflow = user_ns_overflow_uid(current);
if overflow != 0 {
return Ok(overflow as isize);
}
let cred = current.as_thread().cred();
Ok(cred.uid as isize)
}
pub fn sys_geteuid(current: &crate::task::UserTaskRef) -> crate::StarryResult<isize> {
let overflow = user_ns_overflow_uid(current);
if overflow != 0 {
return Ok(overflow as isize);
}
let cred = current.as_thread().cred();
Ok(cred.euid as isize)
}
pub fn sys_getgid(current: &crate::task::UserTaskRef) -> crate::StarryResult<isize> {
let overflow = user_ns_overflow_gid(current);
if overflow != 0 {
return Ok(overflow as isize);
}
let cred = current.as_thread().cred();
Ok(cred.gid as isize)
}
pub fn sys_getegid(current: &crate::task::UserTaskRef) -> crate::StarryResult<isize> {
let overflow = user_ns_overflow_gid(current);
if overflow != 0 {
return Ok(overflow as isize);
}
let cred = current.as_thread().cred();
Ok(cred.egid as isize)
}
pub fn sys_getresuid(
current: &crate::task::UserTaskRef,
ruid: *mut u32,
euid: *mut u32,
suid: *mut u32,
) -> crate::StarryResult<isize> {
let overflow = user_ns_overflow_uid(current);
if overflow != 0 {
ruid.vm_write(current, overflow)?;
euid.vm_write(current, overflow)?;
suid.vm_write(current, overflow)?;
return Ok(0);
}
let cred = current.as_thread().cred();
ruid.vm_write(current, cred.uid)?;
euid.vm_write(current, cred.euid)?;
suid.vm_write(current, cred.suid)?;
Ok(0)
}
pub fn sys_getresgid(
current: &crate::task::UserTaskRef,
rgid: *mut u32,
egid: *mut u32,
sgid: *mut u32,
) -> crate::StarryResult<isize> {
let overflow = user_ns_overflow_gid(current);
if overflow != 0 {
rgid.vm_write(current, overflow)?;
egid.vm_write(current, overflow)?;
sgid.vm_write(current, overflow)?;
return Ok(0);
}
let cred = current.as_thread().cred();
rgid.vm_write(current, cred.gid)?;
egid.vm_write(current, cred.egid)?;
sgid.vm_write(current, cred.sgid)?;
Ok(0)
}
pub fn sys_setresuid(
current: &crate::task::UserTaskRef,
ruid: u32,
euid: u32,
suid: u32,
) -> crate::StarryResult<isize> {
debug!("sys_setresuid <= ruid: {ruid}, euid: {euid}, suid: {suid}");
let thread = current;
let thread = thread.as_thread();
let old = thread.cred();
let mut new = (*old).clone();
if old.has_cap_setuid() {
if ruid != NOCHG {
new.uid = ruid;
}
if euid != NOCHG {
new.euid = euid;
}
if suid != NOCHG {
new.suid = suid;
}
} else {
let allowed = [old.uid, old.euid, old.suid];
if ruid != NOCHG {
if !allowed.contains(&ruid) {
return Err(StarryError::OperationNotPermitted);
}
new.uid = ruid;
}
if euid != NOCHG {
if !allowed.contains(&euid) {
return Err(StarryError::OperationNotPermitted);
}
new.euid = euid;
}
if suid != NOCHG {
if !allowed.contains(&suid) {
return Err(StarryError::OperationNotPermitted);
}
new.suid = suid;
}
}
new.fsuid = new.euid;
let reset_dumpable = dumpable_should_reset(&old, &new);
commit_cred_with_id_rules(thread, new);
if reset_dumpable {
thread.proc_data.set_dumpable(0);
}
Ok(0)
}
pub fn sys_setresgid(
current: &crate::task::UserTaskRef,
rgid: u32,
egid: u32,
sgid: u32,
) -> crate::StarryResult<isize> {
debug!("sys_setresgid <= rgid: {rgid}, egid: {egid}, sgid: {sgid}");
let thread = current;
let thread = thread.as_thread();
let old = thread.cred();
let mut new = (*old).clone();
if old.has_cap_setgid() {
if rgid != NOCHG {
new.gid = rgid;
}
if egid != NOCHG {
new.egid = egid;
}
if sgid != NOCHG {
new.sgid = sgid;
}
} else {
let allowed = [old.gid, old.egid, old.sgid];
if rgid != NOCHG {
if !allowed.contains(&rgid) {
return Err(StarryError::OperationNotPermitted);
}
new.gid = rgid;
}
if egid != NOCHG {
if !allowed.contains(&egid) {
return Err(StarryError::OperationNotPermitted);
}
new.egid = egid;
}
if sgid != NOCHG {
if !allowed.contains(&sgid) {
return Err(StarryError::OperationNotPermitted);
}
new.sgid = sgid;
}
}
new.fsgid = new.egid;
let reset_dumpable = dumpable_should_reset(&old, &new);
commit_cred_with_id_rules(thread, new);
if reset_dumpable {
thread.proc_data.set_dumpable(0);
}
Ok(0)
}
pub fn sys_setuid(current: &crate::task::UserTaskRef, uid: u32) -> crate::StarryResult<isize> {
debug!("sys_setuid <= uid: {uid}");
if !uid_valid(uid) {
return Err(StarryError::InvalidInput);
}
let thread = current;
let thread = thread.as_thread();
let old = thread.cred();
let mut new = (*old).clone();
if old.has_cap_setuid() {
new.uid = uid;
new.euid = uid;
new.suid = uid;
} else {
if uid != old.uid && uid != old.suid {
return Err(StarryError::OperationNotPermitted);
}
new.euid = uid;
}
new.fsuid = new.euid;
let reset_dumpable = dumpable_should_reset(&old, &new);
commit_cred_with_id_rules(thread, new);
if reset_dumpable {
thread.proc_data.set_dumpable(0);
}
Ok(0)
}
pub fn sys_setgid(current: &crate::task::UserTaskRef, gid: u32) -> crate::StarryResult<isize> {
debug!("sys_setgid <= gid: {gid}");
if !uid_valid(gid) {
return Err(StarryError::InvalidInput);
}
let thread = current;
let thread = thread.as_thread();
let old = thread.cred();
let mut new = (*old).clone();
if old.has_cap_setgid() {
new.gid = gid;
new.egid = gid;
new.sgid = gid;
} else {
if gid != old.gid && gid != old.sgid {
return Err(StarryError::OperationNotPermitted);
}
new.egid = gid;
}
new.fsgid = new.egid;
let reset_dumpable = dumpable_should_reset(&old, &new);
commit_cred_with_id_rules(thread, new);
if reset_dumpable {
thread.proc_data.set_dumpable(0);
}
Ok(0)
}
pub fn sys_setreuid(
current: &crate::task::UserTaskRef,
ruid: u32,
euid: u32,
) -> crate::StarryResult<isize> {
debug!("sys_setreuid <= ruid: {ruid}, euid: {euid}");
let thread = current;
let thread = thread.as_thread();
let old = thread.cred();
let mut new = (*old).clone();
if old.has_cap_setuid() {
if ruid != NOCHG {
new.uid = ruid;
}
if euid != NOCHG {
new.euid = euid;
}
} else {
if ruid != NOCHG {
if ruid != old.uid && ruid != old.euid {
return Err(StarryError::OperationNotPermitted);
}
new.uid = ruid;
}
if euid != NOCHG {
if euid != old.uid && euid != old.euid && euid != old.suid {
return Err(StarryError::OperationNotPermitted);
}
new.euid = euid;
}
}
if ruid != NOCHG || (euid != NOCHG && new.euid != old.uid) {
new.suid = new.euid;
}
new.fsuid = new.euid;
let reset_dumpable = dumpable_should_reset(&old, &new);
commit_cred_with_id_rules(thread, new);
if reset_dumpable {
thread.proc_data.set_dumpable(0);
}
Ok(0)
}
pub fn sys_setregid(
current: &crate::task::UserTaskRef,
rgid: u32,
egid: u32,
) -> crate::StarryResult<isize> {
debug!("sys_setregid <= rgid: {rgid}, egid: {egid}");
let thread = current;
let thread = thread.as_thread();
let old = thread.cred();
let mut new = (*old).clone();
if old.has_cap_setgid() {
if rgid != NOCHG {
new.gid = rgid;
}
if egid != NOCHG {
new.egid = egid;
}
} else {
if rgid != NOCHG {
if rgid != old.gid && rgid != old.egid {
return Err(StarryError::OperationNotPermitted);
}
new.gid = rgid;
}
if egid != NOCHG {
if egid != old.gid && egid != old.egid && egid != old.sgid {
return Err(StarryError::OperationNotPermitted);
}
new.egid = egid;
}
}
if rgid != NOCHG || (egid != NOCHG && new.egid != old.gid) {
new.sgid = new.egid;
}
new.fsgid = new.egid;
let reset_dumpable = dumpable_should_reset(&old, &new);
commit_cred_with_id_rules(thread, new);
if reset_dumpable {
thread.proc_data.set_dumpable(0);
}
Ok(0)
}
pub fn sys_setfsuid(current: &crate::task::UserTaskRef, fsuid: u32) -> crate::StarryResult<isize> {
debug!("sys_setfsuid <= fsuid: {fsuid}");
let thread = current;
let thread = thread.as_thread();
let old = thread.cred();
let prev_fsuid = old.fsuid;
if fsuid == NOCHG {
return Ok(prev_fsuid as isize);
}
let allowed = old.has_cap_setuid()
|| fsuid == old.uid
|| fsuid == old.euid
|| fsuid == old.suid
|| fsuid == old.fsuid;
if allowed {
let mut new = (*old).clone();
new.fsuid = fsuid;
let reset_dumpable = dumpable_should_reset(&old, &new);
commit_cred_with_id_rules(thread, new);
if reset_dumpable {
thread.proc_data.set_dumpable(0);
}
}
Ok(prev_fsuid as isize)
}
pub fn sys_setfsgid(current: &crate::task::UserTaskRef, fsgid: u32) -> crate::StarryResult<isize> {
debug!("sys_setfsgid <= fsgid: {fsgid}");
let thread = current;
let thread = thread.as_thread();
let old = thread.cred();
let prev_fsgid = old.fsgid;
if fsgid == NOCHG {
return Ok(prev_fsgid as isize);
}
let allowed = old.has_cap_setgid()
|| fsgid == old.gid
|| fsgid == old.egid
|| fsgid == old.sgid
|| fsgid == old.fsgid;
if allowed {
let mut new = (*old).clone();
new.fsgid = fsgid;
let reset_dumpable = dumpable_should_reset(&old, &new);
commit_cred_with_id_rules(thread, new);
if reset_dumpable {
thread.proc_data.set_dumpable(0);
}
}
Ok(prev_fsgid as isize)
}
pub fn sys_getgroups(
current: &crate::task::UserTaskRef,
size: i32,
list: *mut u32,
) -> crate::StarryResult<isize> {
debug!("sys_getgroups <= size: {size}");
if size < 0 {
return Err(StarryError::InvalidInput);
}
let size = size as usize;
let cred = current.as_thread().cred();
let ngroups = cred.groups.len();
if size == 0 {
return Ok(ngroups as isize);
}
if size < ngroups {
return Err(StarryError::InvalidInput);
}
if ngroups > 0 {
vm_write_slice(current, list, &cred.groups)?;
}
Ok(ngroups as isize)
}
const NGROUPS_MAX: u32 = 65536;
pub fn sys_setgroups(
current: &crate::task::UserTaskRef,
size: i32,
list: *const u32,
) -> crate::StarryResult<isize> {
debug!("sys_setgroups <= size: {size}");
let thread = current;
let thread = thread.as_thread();
let old = thread.cred();
if !old.has_cap_setgid() {
return Err(StarryError::OperationNotPermitted);
}
if thread.setgroups_deny() {
return Err(StarryError::OperationNotPermitted);
}
if size as u32 > NGROUPS_MAX {
return Err(StarryError::InvalidInput);
}
let size = size as usize;
let groups = if size > 0 {
let mut buf: Vec<MaybeUninit<u32>> = vec![MaybeUninit::uninit(); size];
vm_read_slice(current, list, &mut buf)?;
buf.into_iter()
.map(|v| unsafe { v.assume_init() })
.collect()
} else {
Vec::new()
};
let groups: Arc<[u32]> = Arc::from(groups.into_boxed_slice());
thread.update_process_creds(|old| {
let mut new = old.clone();
new.groups = groups.clone();
new
});
Ok(0)
}
pub fn sys_uname(
current: &crate::task::UserTaskRef,
name: *mut new_utsname,
) -> crate::StarryResult<isize> {
let curr = current;
let uts = {
let nsproxy = curr.as_thread().proc_data.namespace_snapshot();
let ns = nsproxy.uts_ns.lock();
crate::namespace::build_utsname(&ns)
};
write_utsname(current, name, uts)?;
Ok(0)
}
fn write_utsname(
current: &crate::task::UserTaskRef,
user: *mut new_utsname,
value: new_utsname,
) -> crate::StarryResult<()> {
let user = UserPtr::from(user);
user.write_field_slice(current, offset_of!(new_utsname, sysname), &value.sysname)?;
user.write_field_slice(current, offset_of!(new_utsname, nodename), &value.nodename)?;
user.write_field_slice(current, offset_of!(new_utsname, release), &value.release)?;
user.write_field_slice(current, offset_of!(new_utsname, version), &value.version)?;
user.write_field_slice(current, offset_of!(new_utsname, machine), &value.machine)?;
user.write_field_slice(
current,
offset_of!(new_utsname, domainname),
&value.domainname,
)
}
pub fn sys_sethostname(
current: &crate::task::UserTaskRef,
name: *const c_char,
len: i32,
) -> crate::StarryResult<isize> {
let curr = current;
if !curr.as_thread().cred().has_cap_sys_admin() {
return Err(StarryError::OperationNotPermitted);
}
if !(0..=64).contains(&len) {
return Err(StarryError::InvalidInput);
}
let len = len as usize;
let mut buf: Vec<MaybeUninit<u8>> = vec![MaybeUninit::uninit(); len];
vm_read_slice(current, name.cast::<u8>(), &mut buf)?;
let bytes: Vec<u8> = unsafe { buf.into_iter().map(|v| v.assume_init()).collect() };
let mut nodename: [c_char; 65] = [0; 65];
unsafe {
core::ptr::copy_nonoverlapping(bytes.as_ptr().cast::<c_char>(), nodename.as_mut_ptr(), len);
}
let proc_data = &curr.as_thread().proc_data;
let update = proc_data.namespace_update();
update.snapshot().uts_ns.lock().nodename = nodename;
Ok(0)
}
pub fn sys_setdomainname(
current: &crate::task::UserTaskRef,
name: *const c_char,
len: i32,
) -> crate::StarryResult<isize> {
let curr = current;
if !curr.as_thread().cred().has_cap_sys_admin() {
return Err(StarryError::OperationNotPermitted);
}
if !(0..=64).contains(&len) {
return Err(StarryError::InvalidInput);
}
let len = len as usize;
let mut buf: Vec<MaybeUninit<u8>> = vec![MaybeUninit::uninit(); len];
vm_read_slice(current, name.cast::<u8>(), &mut buf)?;
let bytes: Vec<u8> = unsafe { buf.into_iter().map(|v| v.assume_init()).collect() };
let mut domainname: [c_char; 65] = [0; 65];
unsafe {
core::ptr::copy_nonoverlapping(
bytes.as_ptr().cast::<c_char>(),
domainname.as_mut_ptr(),
len,
);
}
let proc_data = &curr.as_thread().proc_data;
let update = proc_data.namespace_update();
update.snapshot().uts_ns.lock().domainname = domainname;
Ok(0)
}
pub fn sys_sysinfo(
current: &crate::task::UserTaskRef,
info: *mut sysinfo,
) -> crate::StarryResult<isize> {
let mut kinfo: sysinfo = unsafe { core::mem::zeroed() };
let total = ax_runtime::hal::mem::total_ram_size();
let usages = ax_alloc::global_allocator().usages();
let used = usages.get(ax_alloc::UsageKind::RustHeap)
+ usages.get(ax_alloc::UsageKind::VirtMem)
+ usages.get(ax_alloc::UsageKind::PageCache)
+ usages.get(ax_alloc::UsageKind::PageTable)
+ usages.get(ax_alloc::UsageKind::TaskStack)
+ usages.get(ax_alloc::UsageKind::Dma)
+ usages.get(ax_alloc::UsageKind::Global);
let free = total.saturating_sub(used);
let uptime = ax_runtime::hal::time::monotonic_time();
kinfo.uptime = uptime.as_secs() as _;
kinfo.totalram = total as _;
kinfo.freeram = free as _;
kinfo.procs = processes().len() as _;
kinfo.mem_unit = 1;
write_sysinfo(current, info, kinfo)?;
Ok(0)
}
fn write_sysinfo(
current: &crate::task::UserTaskRef,
user: *mut sysinfo,
value: sysinfo,
) -> crate::StarryResult<()> {
let user = UserPtr::from(user);
user.write_field(current, offset_of!(sysinfo, uptime), value.uptime)?;
user.write_field(current, offset_of!(sysinfo, loads), value.loads)?;
user.write_field(current, offset_of!(sysinfo, totalram), value.totalram)?;
user.write_field(current, offset_of!(sysinfo, freeram), value.freeram)?;
user.write_field(current, offset_of!(sysinfo, sharedram), value.sharedram)?;
user.write_field(current, offset_of!(sysinfo, bufferram), value.bufferram)?;
user.write_field(current, offset_of!(sysinfo, totalswap), value.totalswap)?;
user.write_field(current, offset_of!(sysinfo, freeswap), value.freeswap)?;
user.write_field(current, offset_of!(sysinfo, procs), value.procs)?;
user.write_field(current, offset_of!(sysinfo, pad), value.pad)?;
user.write_field(current, offset_of!(sysinfo, totalhigh), value.totalhigh)?;
user.write_field(current, offset_of!(sysinfo, freehigh), value.freehigh)?;
user.write_field(current, offset_of!(sysinfo, mem_unit), value.mem_unit)
}
fn require_syslog_privilege(current: &crate::task::UserTaskRef) -> crate::StarryResult<()> {
if current.as_thread().cred().euid == 0 {
Ok(())
} else {
Err(StarryError::OperationNotPermitted)
}
}
fn validate_syslog_read_args(buf: *mut c_char, len: i32) -> StarryResult<()> {
if buf.is_null() || len < 0 {
Err(StarryError::InvalidInput)
} else {
Ok(())
}
}
pub fn sys_syslog(
current: &crate::task::UserTaskRef,
ty: i32,
buf: *mut c_char,
len: i32,
) -> StarryResult<isize> {
match ty {
SYSLOG_ACTION_CLOSE | SYSLOG_ACTION_OPEN => Ok(0),
SYSLOG_ACTION_READ => {
require_syslog_privilege(current)?;
validate_syslog_read_args(buf, len)?;
let data = {
let mut state = SYSLOG_STATE.lock();
state.read(len as usize)
};
if !data.is_empty() {
vm_write_slice(current, buf.cast::<u8>(), &data)?;
}
Ok(data.len() as isize)
}
SYSLOG_ACTION_READ_ALL => {
require_syslog_privilege(current)?;
validate_syslog_read_args(buf, len)?;
let data = {
let state = SYSLOG_STATE.lock();
state.read_all(len as usize)
};
if !data.is_empty() {
vm_write_slice(current, buf.cast::<u8>(), &data)?;
}
Ok(data.len() as isize)
}
SYSLOG_ACTION_READ_CLEAR => {
require_syslog_privilege(current)?;
validate_syslog_read_args(buf, len)?;
let data = {
let mut state = SYSLOG_STATE.lock();
let data = state.read_all(len as usize);
state.clear();
data
};
if !data.is_empty() {
vm_write_slice(current, buf.cast::<u8>(), &data)?;
}
Ok(data.len() as isize)
}
SYSLOG_ACTION_CLEAR => {
require_syslog_privilege(current)?;
let mut state = SYSLOG_STATE.lock();
state.clear();
Ok(0)
}
SYSLOG_ACTION_CONSOLE_OFF => {
require_syslog_privilege(current)?;
let mut state = SYSLOG_STATE.lock();
state.console_enabled = false;
Ok(0)
}
SYSLOG_ACTION_CONSOLE_ON => {
require_syslog_privilege(current)?;
let mut state = SYSLOG_STATE.lock();
state.console_enabled = true;
Ok(0)
}
SYSLOG_ACTION_CONSOLE_LEVEL => {
require_syslog_privilege(current)?;
if !(1..=8).contains(&len) {
return Err(StarryError::InvalidInput);
}
let mut state = SYSLOG_STATE.lock();
let old_level = state.console_level;
state.console_level = len as usize;
Ok(old_level as isize)
}
SYSLOG_ACTION_SIZE_UNREAD => {
require_syslog_privilege(current)?;
let state = SYSLOG_STATE.lock();
Ok(state.unread_len() as isize)
}
SYSLOG_ACTION_SIZE_BUFFER => {
let state = SYSLOG_STATE.lock();
Ok(state.buffer_len() as isize)
}
_ => Err(StarryError::InvalidInput),
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetRandomFlags: u32 {
const NONBLOCK = GRND_NONBLOCK;
const RANDOM = GRND_RANDOM;
const INSECURE = GRND_INSECURE;
}
}
pub fn sys_getrandom(
current: &crate::task::UserTaskRef,
buf: *mut u8,
len: usize,
flags: u32,
) -> crate::StarryResult<isize> {
if len == 0 {
return Ok(0);
}
let flags = GetRandomFlags::from_bits(flags).ok_or(StarryError::InvalidInput)?;
if flags.contains(GetRandomFlags::INSECURE) && flags.contains(GetRandomFlags::RANDOM) {
return Err(StarryError::InvalidInput);
}
debug!("sys_getrandom <= buf: {buf:p}, len: {len}, flags: {flags:?}");
let path = if flags.contains(GetRandomFlags::RANDOM) {
"/dev/random"
} else {
"/dev/urandom"
};
let len = len.min(GETRANDOM_MAX_LEN);
(buf as usize).checked_add(len).ok_or(Errno::EFAULT)?;
let f = ax_fs_ng::vfs::current_fs_context().lock().resolve(path)?;
let file = f.entry().as_file()?;
let mut kbuf = [0u8; GETRANDOM_CHUNK_SIZE];
let mut written = 0;
while written < len {
let chunk_len = (len - written).min(kbuf.len());
let read = file.read_at(&mut kbuf[..chunk_len], 0)?;
if read == 0 {
break;
}
let dst = (buf as usize).checked_add(written).ok_or(Errno::EFAULT)? as *mut u8;
vm_write_slice(current, dst, &kbuf[..read])?;
written += read;
if read < chunk_len {
break;
}
}
Ok(written as _)
}
fn check_seccomp_install_permission(current: &crate::task::UserTaskRef) -> crate::StarryResult<()> {
let curr = current;
let thread = curr.as_thread();
if thread.no_new_privs() || thread.cred().has_cap_sys_admin() {
Ok(())
} else {
Err(StarryError::OperationNotPermitted)
}
}
fn read_seccomp_filter(
current: &crate::task::UserTaskRef,
args: *const (),
) -> crate::StarryResult<Vec<SockFilter>> {
if args.is_null() {
return Err(StarryError::BadAddress);
}
let prog = unsafe {
(args as *const SockFprog)
.vm_read_uninit(current)?
.assume_init()
};
if prog.len == 0 || prog.filter.is_null() {
return Err(StarryError::InvalidInput);
}
let mut raw = vec![MaybeUninit::<SockFilter>::uninit(); prog.len as usize];
vm_read_slice(current, prog.filter, &mut raw)?;
Ok(raw
.into_iter()
.map(|insn| unsafe { insn.assume_init() })
.collect())
}
fn seccomp_action_available(
current: &crate::task::UserTaskRef,
args: *const (),
) -> crate::StarryResult<isize> {
if args.is_null() {
return Err(StarryError::BadAddress);
}
let action = unsafe { (args as *const u32).vm_read_uninit(current)?.assume_init() };
match action {
SECCOMP_RET_ALLOW
| SECCOMP_RET_LOG
| SECCOMP_RET_ERRNO
| SECCOMP_RET_KILL_THREAD
| SECCOMP_RET_KILL_PROCESS => Ok(0),
_ => Err(StarryError::OperationNotSupported),
}
}
fn sync_seccomp_to_thread_group(current: &crate::task::UserTaskRef) {
let curr = current;
let thread = curr.as_thread();
let state = thread.seccomp_state();
let no_new_privs = thread.no_new_privs();
for tid in thread.proc_data.proc.threads() {
if tid == thread.tid_number() {
continue;
}
if let Ok(task) = get_task_by_number(tid) {
let peer = task.as_thread();
if no_new_privs {
peer.set_no_new_privs();
}
peer.set_seccomp_state(state.clone());
}
}
}
pub fn sys_seccomp(
current: &crate::task::UserTaskRef,
op: u32,
flags: u32,
args: *const (),
) -> crate::StarryResult<isize> {
if flags & !SECCOMP_ALLOWED_FLAGS != 0 {
return Err(StarryError::InvalidInput);
}
match op {
SECCOMP_SET_MODE_STRICT => {
if flags != 0 || !args.is_null() {
return Err(StarryError::InvalidInput);
}
let _update = current.as_thread().proc_data.thread_group_update();
current.as_thread().install_seccomp_strict()?;
}
SECCOMP_SET_MODE_FILTER => {
check_seccomp_install_permission(current)?;
let filter = read_seccomp_filter(current, args)?;
let curr = current;
let thread = curr.as_thread();
let _update = thread.proc_data.thread_group_update();
thread.append_seccomp_filter(filter)?;
if flags & SECCOMP_FILTER_FLAG_TSYNC != 0 {
sync_seccomp_to_thread_group(current);
}
}
SECCOMP_GET_ACTION_AVAIL => {
if flags != 0 {
return Err(StarryError::InvalidInput);
}
return seccomp_action_available(current, args);
}
_ => return Err(StarryError::InvalidInput),
}
Ok(0)
}
#[cfg(target_arch = "riscv64")]
const SYS_RISCV_FLUSH_ICACHE_LOCAL: usize = 1;
#[cfg(target_arch = "riscv64")]
pub fn sys_riscv_flush_icache(start: usize, end: usize, flags: usize) -> StarryResult<isize> {
if flags & !SYS_RISCV_FLUSH_ICACHE_LOCAL != 0 {
return Err(StarryError::InvalidInput);
}
if end < start {
return Err(StarryError::InvalidInput);
}
if flags & SYS_RISCV_FLUSH_ICACHE_LOCAL != 0 {
ax_cpu::cache::flush_icache_all();
} else {
ax_runtime::hal::cache::flush_icache_all_cpus();
}
Ok(0)
}
#[cfg(target_arch = "riscv64")]
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::AnyBitPattern, bytemuck::NoUninit)]
struct RiscvHwprobe {
key: i64,
value: u64,
}
#[cfg(target_arch = "riscv64")]
pub fn sys_riscv_hwprobe(
current: &crate::task::UserTaskRef,
pairs: *mut u8,
pair_count: usize,
cpu_count: usize,
cpus: *const usize,
flags: u32,
) -> StarryResult<isize> {
if flags != 0 || cpu_count != 0 || !cpus.is_null() {
return Err(StarryError::InvalidInput);
}
if pair_count == 0 {
return Ok(0);
}
if pair_count > isize::MAX as usize / core::mem::size_of::<RiscvHwprobe>() {
return Err(StarryError::InvalidInput);
}
let user_pairs = pairs.cast::<RiscvHwprobe>();
for index in 0..pair_count {
let pair = user_pairs.wrapping_add(index);
let key_ptr = pair.cast::<i64>();
let mut key = key_ptr.vm_read(current)?;
let value = if let Some(value) = crate::cpu_capabilities::riscv_hwprobe(key) {
value
} else {
key = -1;
0
};
key_ptr.vm_write(current, key)?;
pair.cast::<u8>()
.wrapping_add(core::mem::offset_of!(RiscvHwprobe, value))
.cast::<u64>()
.vm_write(current, value)?;
}
Ok(0)
}
#[cfg(all(test, not(axtest)))]
fn uid_valid_and_syslog_validation_rules_hold_for_test() -> bool {
uid_valid(0)
&& uid_valid(1)
&& uid_valid(1000)
&& uid_valid(u32::MAX - 1)
&& !uid_valid(u32::MAX)
&& validate_syslog_read_args(core::ptr::null_mut(), 0).is_err()
&& validate_syslog_read_args(core::ptr::null_mut::<c_char>(), 100).is_err()
&& validate_syslog_read_args(core::ptr::dangling_mut::<c_char>(), 0).is_ok() && {
let mut dummy: c_char = 0;
let ptr: *mut c_char = &mut dummy;
validate_syslog_read_args(ptr, i32::MAX).is_ok()
&& validate_syslog_read_args(ptr, -1).is_err()
}
}
#[cfg(all(test, not(axtest)))]
mod tests {
#[test]
fn uid_valid_and_syslog_validation_rules_hold() {
assert!(super::uid_valid_and_syslog_validation_rules_hold_for_test());
}
#[test]
fn reboot_syscall_does_not_tear_down_filesystems() {
let source = include_str!("sys.rs");
let start = source
.find("pub fn sys_reboot(")
.expect("sys_reboot must exist");
let remainder = &source[start..];
let end = remainder[1..]
.find("\npub fn ")
.map(|offset| offset + 1)
.unwrap_or(remainder.len());
let reboot = &remainder[..end];
assert!(
!reboot.contains("shutdown_filesystems"),
"reboot(2) must not sync or unmount filesystems; Linux leaves that to userspace"
);
assert!(
reboot.contains("system_reset") && reboot.contains("system_off"),
"reboot(2) restart and power-off must still reach the platform power helpers"
);
}
}