From 33e28cfc4e0caad95d9e2e85d9b4357c8a8df439 Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Wed, 18 Feb 2026 14:07:59 -0500 Subject: [PATCH] feat: identity syscalls, umask, utimensat, pread64/pwrite64, and /etc files Add uid/gid/euid/egid/umask fields to Process struct with root defaults and fork inheritance. Implement getuid/geteuid/getgid/getegid/setuid/ setgid/umask/pread64/pwrite64/utimensat syscalls with proper dispatch on both x86_64 and ARM64. Add ESPIPE errno and Ext2Fs::write_inode() helper. Populate /etc/passwd and /etc/group on ext2 image for musl getpwuid/getgrgid. Add identity_musl_test C program. Co-Authored-By: Ryan Breen Co-Authored-By: Claude Opus 4.6 --- kernel/src/arch_impl/aarch64/syscall_entry.rs | 14 + kernel/src/boot/test_list.rs | 1 + kernel/src/fs/ext2/mod.rs | 10 + kernel/src/process/fork.rs | 8 +- kernel/src/process/process.rs | 18 + kernel/src/syscall/dispatcher.rs | 14 + kernel/src/syscall/errno.rs | 3 + kernel/src/syscall/fs.rs | 212 ++++++++++++ kernel/src/syscall/handler.rs | 14 + kernel/src/syscall/handlers.rs | 309 ++++++++++++++++++ kernel/src/syscall/mod.rs | 37 +++ scripts/create_ext2_disk.sh | 22 ++ userspace/c-programs/Makefile | 5 +- userspace/c-programs/identity_test.c | 101 ++++++ 14 files changed, 765 insertions(+), 3 deletions(-) create mode 100644 userspace/c-programs/identity_test.c diff --git a/kernel/src/arch_impl/aarch64/syscall_entry.rs b/kernel/src/arch_impl/aarch64/syscall_entry.rs index ea54f9ff..457a8274 100644 --- a/kernel/src/arch_impl/aarch64/syscall_entry.rs +++ b/kernel/src/arch_impl/aarch64/syscall_entry.rs @@ -573,6 +573,20 @@ fn dispatch_syscall_enum( SyscallNumber::EpollCtl => result_to_u64(crate::syscall::epoll::sys_epoll_ctl(arg1 as i32, arg2 as i32, arg3 as i32, arg4)), SyscallNumber::EpollWait => result_to_u64(crate::syscall::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, 0, 0)), SyscallNumber::EpollPwait => result_to_u64(crate::syscall::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, arg5, arg6)), + // Identity syscalls + SyscallNumber::Getuid => result_to_u64(crate::syscall::handlers::sys_getuid()), + SyscallNumber::Geteuid => result_to_u64(crate::syscall::handlers::sys_geteuid()), + SyscallNumber::Getgid => result_to_u64(crate::syscall::handlers::sys_getgid()), + SyscallNumber::Getegid => result_to_u64(crate::syscall::handlers::sys_getegid()), + SyscallNumber::Setuid => result_to_u64(crate::syscall::handlers::sys_setuid(arg1 as u32)), + SyscallNumber::Setgid => result_to_u64(crate::syscall::handlers::sys_setgid(arg1 as u32)), + // File creation mask + SyscallNumber::Umask => result_to_u64(crate::syscall::handlers::sys_umask(arg1 as u32)), + // Timestamps + SyscallNumber::Utimensat => result_to_u64(crate::syscall::fs::sys_utimensat(arg1 as i32, arg2, arg3, arg4 as u32)), + // Positional I/O + SyscallNumber::Pread64 => result_to_u64(crate::syscall::handlers::sys_pread64(arg1 as i32, arg2, arg3, arg4 as i64)), + SyscallNumber::Pwrite64 => result_to_u64(crate::syscall::handlers::sys_pwrite64(arg1 as i32, arg2, arg3, arg4 as i64)), } } diff --git a/kernel/src/boot/test_list.rs b/kernel/src/boot/test_list.rs index cd4c952c..a865d7de 100644 --- a/kernel/src/boot/test_list.rs +++ b/kernel/src/boot/test_list.rs @@ -82,6 +82,7 @@ pub const TEST_BINARIES: &[&str] = &[ "env_musl_test", "uname_musl_test", "rlimit_musl_test", + "identity_musl_test", // Fork / CoW tests "fork_memory_test", "fork_state_test", diff --git a/kernel/src/fs/ext2/mod.rs b/kernel/src/fs/ext2/mod.rs index faba75a4..37896e66 100644 --- a/kernel/src/fs/ext2/mod.rs +++ b/kernel/src/fs/ext2/mod.rs @@ -249,6 +249,16 @@ impl Ext2Fs { Ok(data.len()) } + /// Write a modified inode back to disk + /// + /// # Arguments + /// * `inode_num` - The inode number to write + /// * `inode` - The modified inode data + pub fn write_inode(&mut self, inode_num: u32, inode: &Ext2Inode) -> Result<(), &'static str> { + inode.write_to(self.device.as_ref(), inode_num, &self.superblock, &self.block_groups) + .map_err(|_| "Failed to write inode") + } + /// Create a new file in the filesystem /// /// # Arguments diff --git a/kernel/src/process/fork.rs b/kernel/src/process/fork.rs index 57bcc0c3..42681cd2 100644 --- a/kernel/src/process/fork.rs +++ b/kernel/src/process/fork.rs @@ -532,8 +532,12 @@ pub fn copy_process_state( child_process.sid = parent_process.sid; } - // 5. Copy umask (when per-process umask is implemented) - // TODO: child_process.umask = parent_process.umask; + // 5. Copy uid/gid/euid/egid/umask + child_process.uid = parent_process.uid; + child_process.gid = parent_process.gid; + child_process.euid = parent_process.euid; + child_process.egid = parent_process.egid; + child_process.umask = parent_process.umask; // 6. Current working directory: inherited from parent in fork_internal() // (before copy_process_state is called) diff --git a/kernel/src/process/process.rs b/kernel/src/process/process.rs index 7f140f75..b7789766 100644 --- a/kernel/src/process/process.rs +++ b/kernel/src/process/process.rs @@ -77,6 +77,17 @@ pub struct Process { /// a controlling terminal. Initially set to pid on process creation. pub sid: ProcessId, + /// Real user ID + pub uid: u32, + /// Real group ID + pub gid: u32, + /// Effective user ID + pub euid: u32, + /// Effective group ID + pub egid: u32, + /// File creation mask (umask) + pub umask: u32, + /// Current working directory (absolute path) pub cwd: String, @@ -196,6 +207,13 @@ impl Process { pgid: id, // By default, a process's sid equals its pid (process is its own session leader) sid: id, + // Single-user OS: everything runs as root (uid=0, gid=0) + uid: 0, + gid: 0, + euid: 0, + egid: 0, + // Standard default umask: owner rwx, group/other rx + umask: 0o022, // Default working directory is root cwd: String::from("/"), name, diff --git a/kernel/src/syscall/dispatcher.rs b/kernel/src/syscall/dispatcher.rs index 3936f0ec..a222cf6f 100644 --- a/kernel/src/syscall/dispatcher.rs +++ b/kernel/src/syscall/dispatcher.rs @@ -182,5 +182,19 @@ pub fn dispatch_syscall( SyscallNumber::EpollCtl => super::epoll::sys_epoll_ctl(arg1 as i32, arg2 as i32, arg3 as i32, arg4), SyscallNumber::EpollWait => super::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, 0, 0), SyscallNumber::EpollPwait => super::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, arg5, arg6), + // Identity syscalls + SyscallNumber::Getuid => handlers::sys_getuid(), + SyscallNumber::Geteuid => handlers::sys_geteuid(), + SyscallNumber::Getgid => handlers::sys_getgid(), + SyscallNumber::Getegid => handlers::sys_getegid(), + SyscallNumber::Setuid => handlers::sys_setuid(arg1 as u32), + SyscallNumber::Setgid => handlers::sys_setgid(arg1 as u32), + // File creation mask + SyscallNumber::Umask => handlers::sys_umask(arg1 as u32), + // Timestamps + SyscallNumber::Utimensat => super::fs::sys_utimensat(arg1 as i32, arg2, arg3, arg4 as u32), + // Positional I/O + SyscallNumber::Pread64 => handlers::sys_pread64(arg1 as i32, arg2, arg3, arg4 as i64), + SyscallNumber::Pwrite64 => handlers::sys_pwrite64(arg1 as i32, arg2, arg3, arg4 as i64), } } diff --git a/kernel/src/syscall/errno.rs b/kernel/src/syscall/errno.rs index 76c21e85..d1d1744f 100644 --- a/kernel/src/syscall/errno.rs +++ b/kernel/src/syscall/errno.rs @@ -61,6 +61,9 @@ pub const ENOTTY: i32 = 25; /// No space left on device pub const ENOSPC: i32 = 28; +/// Illegal seek (not a seekable fd) +pub const ESPIPE: i32 = 29; + /// Broken pipe pub const EPIPE: i32 = 32; diff --git a/kernel/src/syscall/fs.rs b/kernel/src/syscall/fs.rs index 3f796cfb..624fdf56 100644 --- a/kernel/src/syscall/fs.rs +++ b/kernel/src/syscall/fs.rs @@ -3470,3 +3470,215 @@ pub fn sys_readlinkat(dirfd: i32, pathname: u64, buf: u64, bufsiz: u64) -> Sysca } sys_readlink(pathname, buf, bufsiz) } + +// ============================================================================= +// utimensat - Update file timestamps +// ============================================================================= + +/// Special timespec value: set timestamp to current time +const UTIME_NOW: i64 = 0x3FFFFFFF; +/// Special timespec value: leave timestamp unchanged +const UTIME_OMIT: i64 = 0x3FFFFFFE; +/// AT_SYMLINK_NOFOLLOW flag +#[allow(dead_code)] +const AT_SYMLINK_NOFOLLOW: u32 = 0x100; + +/// Timespec layout for utimensat (matches Linux ABI) +#[repr(C)] +#[derive(Copy, Clone)] +struct UtimeTimespec { + tv_sec: i64, + tv_nsec: i64, +} + +/// utimensat(dirfd, pathname, times, flags) - Update file timestamps +/// +/// If pathname is NULL and dirfd is a valid fd: operate on that fd (futimens behavior). +/// If times is NULL: set atime and mtime to current time. +/// Otherwise: read two Timespec structs from userspace for atime and mtime. +/// UTIME_NOW (0x3FFFFFFF): use current time for that field. +/// UTIME_OMIT (0x3FFFFFFE): don't change that timestamp. +pub fn sys_utimensat(dirfd: i32, path_ptr: u64, times_ptr: u64, flags: u32) -> SyscallResult { + use crate::fs::ext2; + + let now = crate::time::current_unix_time() as u32; + + // Determine what atime/mtime to set + let (set_atime, set_mtime) = if times_ptr == 0 { + // NULL times = set both to current time + (Some(now), Some(now)) + } else { + // Read two Timespec structs from userspace + let times: [UtimeTimespec; 2] = match super::userptr::copy_from_user(times_ptr as *const [UtimeTimespec; 2]) { + Ok(t) => t, + Err(e) => return SyscallResult::Err(e), + }; + + let atime = if times[0].tv_nsec == UTIME_NOW { + Some(now) + } else if times[0].tv_nsec == UTIME_OMIT { + None + } else { + Some(times[0].tv_sec as u32) + }; + + let mtime = if times[1].tv_nsec == UTIME_NOW { + Some(now) + } else if times[1].tv_nsec == UTIME_OMIT { + None + } else { + Some(times[1].tv_sec as u32) + }; + + (atime, mtime) + }; + + // If both are OMIT, nothing to do + if set_atime.is_none() && set_mtime.is_none() { + return SyscallResult::Ok(0); + } + + // Determine the target inode + if path_ptr == 0 { + // futimens behavior: operate on dirfd + if dirfd < 0 { + return SyscallResult::Err(super::errno::EBADF as u64); + } + + let thread_id = match crate::task::scheduler::current_thread_id() { + Some(id) => id, + None => return SyscallResult::Err(super::errno::EBADF as u64), + }; + + let fd_info = crate::arch_without_interrupts(|| { + let manager_guard = crate::process::manager(); + if let Some(ref manager) = *manager_guard { + if let Some((_pid, process)) = manager.find_process_by_thread(thread_id) { + if let Some(fd_entry) = process.fd_table.get(dirfd) { + if let FdKind::RegularFile(file_ref) = &fd_entry.kind { + let file = file_ref.lock(); + return Some((file.inode_num as u32, file.mount_id)); + } + } + } + } + None + }); + + let (inode_num, mount_id) = match fd_info { + Some((ino, mid)) => (ino, mid), + None => return SyscallResult::Err(super::errno::EBADF as u64), + }; + + return update_inode_timestamps(inode_num, mount_id, set_atime, set_mtime); + } + + // Path-based: resolve path + let path = match super::userptr::copy_cstr_from_user(path_ptr) { + Ok(s) => s, + Err(e) => return SyscallResult::Err(e as u64), + }; + + // Handle AT_FDCWD + if dirfd != AT_FDCWD && !path.starts_with('/') { + return SyscallResult::Err(super::errno::ENOSYS as u64); + } + + let full_path = if path.starts_with('/') { + path.clone() + } else { + let cwd = get_current_cwd().unwrap_or_else(|| alloc::string::String::from("/")); + if cwd.ends_with('/') { + alloc::format!("{}{}", cwd, path) + } else { + alloc::format!("{}/{}", cwd, path) + } + }; + + let is_home = ext2::is_home_path(&full_path); + let fs_path = if is_home { ext2::strip_home_prefix(&full_path) } else { &full_path }; + let no_follow = (flags & AT_SYMLINK_NOFOLLOW) != 0; + + // Resolve path first (read lock), then update timestamps (write lock) + let (inode_num, mount_id) = if is_home { + let fs_guard = ext2::home_fs_read(); + let fs = match fs_guard.as_ref() { + Some(f) => f, + None => return SyscallResult::Err(super::errno::ENOENT as u64), + }; + let ino = if no_follow { + fs.resolve_path_no_follow(fs_path) + } else { + fs.resolve_path(fs_path) + }; + match ino { + Ok(n) => (n, fs.mount_id), + Err(_) => return SyscallResult::Err(super::errno::ENOENT as u64), + } + } else { + let fs_guard = ext2::root_fs_read(); + let fs = match fs_guard.as_ref() { + Some(f) => f, + None => return SyscallResult::Err(super::errno::ENOENT as u64), + }; + let ino = if no_follow { + fs.resolve_path_no_follow(fs_path) + } else { + fs.resolve_path(fs_path) + }; + match ino { + Ok(n) => (n, fs.mount_id), + Err(_) => return SyscallResult::Err(super::errno::ENOENT as u64), + } + }; + + update_inode_timestamps(inode_num, mount_id, set_atime, set_mtime) +} + +/// Helper: update an inode's atime/mtime on the ext2 filesystem +fn update_inode_timestamps( + inode_num: u32, + mount_id: usize, + set_atime: Option, + set_mtime: Option, +) -> SyscallResult { + use crate::fs::ext2; + + let is_home = ext2::home_mount_id().map_or(false, |id| id == mount_id); + + let do_update = |fs: &mut ext2::Ext2Fs| -> SyscallResult { + let mut inode = match fs.read_inode(inode_num) { + Ok(i) => i, + Err(_) => return SyscallResult::Err(super::errno::EIO as u64), + }; + + if let Some(atime) = set_atime { + inode.i_atime = atime; + } + if let Some(mtime) = set_mtime { + inode.i_mtime = mtime; + } + // Always update ctime when timestamps change + inode.i_ctime = crate::time::current_unix_time() as u32; + + match fs.write_inode(inode_num, &inode) { + Ok(()) => SyscallResult::Ok(0), + Err(_) => SyscallResult::Err(super::errno::EIO as u64), + } + }; + + if is_home { + let mut fs_guard = ext2::home_fs_write(); + match fs_guard.as_mut() { + Some(fs) => do_update(fs), + None => SyscallResult::Err(super::errno::EIO as u64), + } + } else { + let mut fs_guard = ext2::root_fs_write(); + match fs_guard.as_mut() { + Some(fs) => do_update(fs), + None => SyscallResult::Err(super::errno::EIO as u64), + } + } +} + diff --git a/kernel/src/syscall/handler.rs b/kernel/src/syscall/handler.rs index e6e9af6c..e7786732 100644 --- a/kernel/src/syscall/handler.rs +++ b/kernel/src/syscall/handler.rs @@ -456,6 +456,20 @@ pub extern "C" fn rust_syscall_handler(frame: &mut SyscallFrame) { Some(SyscallNumber::EpollCtl) => super::epoll::sys_epoll_ctl(args.0 as i32, args.1 as i32, args.2 as i32, args.3), Some(SyscallNumber::EpollWait) => super::epoll::sys_epoll_pwait(args.0 as i32, args.1, args.2 as i32, args.3 as i32, 0, 0), Some(SyscallNumber::EpollPwait) => super::epoll::sys_epoll_pwait(args.0 as i32, args.1, args.2 as i32, args.3 as i32, args.4, args.5), + // Identity syscalls + Some(SyscallNumber::Getuid) => super::handlers::sys_getuid(), + Some(SyscallNumber::Geteuid) => super::handlers::sys_geteuid(), + Some(SyscallNumber::Getgid) => super::handlers::sys_getgid(), + Some(SyscallNumber::Getegid) => super::handlers::sys_getegid(), + Some(SyscallNumber::Setuid) => super::handlers::sys_setuid(args.0 as u32), + Some(SyscallNumber::Setgid) => super::handlers::sys_setgid(args.0 as u32), + // File creation mask + Some(SyscallNumber::Umask) => super::handlers::sys_umask(args.0 as u32), + // Timestamps + Some(SyscallNumber::Utimensat) => super::fs::sys_utimensat(args.0 as i32, args.1, args.2, args.3 as u32), + // Positional I/O + Some(SyscallNumber::Pread64) => super::handlers::sys_pread64(args.0 as i32, args.1, args.2, args.3 as i64), + Some(SyscallNumber::Pwrite64) => super::handlers::sys_pwrite64(args.0 as i32, args.1, args.2, args.3 as i64), None => { log::warn!("Unknown syscall number: {} - returning ENOSYS", syscall_num); SyscallResult::Err(super::ErrorCode::NoSys as u64) diff --git a/kernel/src/syscall/handlers.rs b/kernel/src/syscall/handlers.rs index 04ab507c..9570afe0 100644 --- a/kernel/src/syscall/handlers.rs +++ b/kernel/src/syscall/handlers.rs @@ -3840,3 +3840,312 @@ pub fn sys_uname(buf_ptr: u64) -> SyscallResult { } SyscallResult::Ok(0) } + +// ============================================================================= +// Identity syscalls (getuid, geteuid, getgid, getegid, setuid, setgid) +// ============================================================================= + +/// getuid - Get real user ID +pub fn sys_getuid() -> SyscallResult { + crate::arch_without_interrupts(|| { + if let Some(thread_id) = crate::task::scheduler::current_thread_id() { + if let Some(ref manager) = *crate::process::manager() { + if let Some((_pid, process)) = manager.find_process_by_thread(thread_id) { + return SyscallResult::Ok(process.uid as u64); + } + } + } + SyscallResult::Ok(0) + }) +} + +/// geteuid - Get effective user ID +pub fn sys_geteuid() -> SyscallResult { + crate::arch_without_interrupts(|| { + if let Some(thread_id) = crate::task::scheduler::current_thread_id() { + if let Some(ref manager) = *crate::process::manager() { + if let Some((_pid, process)) = manager.find_process_by_thread(thread_id) { + return SyscallResult::Ok(process.euid as u64); + } + } + } + SyscallResult::Ok(0) + }) +} + +/// getgid - Get real group ID +pub fn sys_getgid() -> SyscallResult { + crate::arch_without_interrupts(|| { + if let Some(thread_id) = crate::task::scheduler::current_thread_id() { + if let Some(ref manager) = *crate::process::manager() { + if let Some((_pid, process)) = manager.find_process_by_thread(thread_id) { + return SyscallResult::Ok(process.gid as u64); + } + } + } + SyscallResult::Ok(0) + }) +} + +/// getegid - Get effective group ID +pub fn sys_getegid() -> SyscallResult { + crate::arch_without_interrupts(|| { + if let Some(thread_id) = crate::task::scheduler::current_thread_id() { + if let Some(ref manager) = *crate::process::manager() { + if let Some((_pid, process)) = manager.find_process_by_thread(thread_id) { + return SyscallResult::Ok(process.egid as u64); + } + } + } + SyscallResult::Ok(0) + }) +} + +/// setuid - Set user ID +/// +/// If euid == 0 (root): set both uid and euid to the new value. +/// Otherwise: can only set euid to uid or euid (no-op). +pub fn sys_setuid(uid: u32) -> SyscallResult { + crate::arch_without_interrupts(|| { + if let Some(thread_id) = crate::task::scheduler::current_thread_id() { + let mut manager_guard = crate::process::manager(); + if let Some(ref mut manager) = *manager_guard { + if let Some((_pid, process)) = manager.find_process_by_thread_mut(thread_id) { + if process.euid == 0 { + process.uid = uid; + process.euid = uid; + } else if uid == process.uid || uid == process.euid { + process.euid = uid; + } else { + return SyscallResult::Err(super::errno::EPERM as u64); + } + return SyscallResult::Ok(0); + } + } + } + SyscallResult::Err(super::errno::EPERM as u64) + }) +} + +/// setgid - Set group ID +/// +/// If euid == 0 (root): set both gid and egid to the new value. +/// Otherwise: can only set egid to gid or egid (no-op). +pub fn sys_setgid(gid: u32) -> SyscallResult { + crate::arch_without_interrupts(|| { + if let Some(thread_id) = crate::task::scheduler::current_thread_id() { + let mut manager_guard = crate::process::manager(); + if let Some(ref mut manager) = *manager_guard { + if let Some((_pid, process)) = manager.find_process_by_thread_mut(thread_id) { + if process.euid == 0 { + process.gid = gid; + process.egid = gid; + } else if gid == process.gid || gid == process.egid { + process.egid = gid; + } else { + return SyscallResult::Err(super::errno::EPERM as u64); + } + return SyscallResult::Ok(0); + } + } + } + SyscallResult::Err(super::errno::EPERM as u64) + }) +} + +// ============================================================================= +// umask syscall +// ============================================================================= + +/// umask - Set file creation mask +/// +/// Sets the process's file creation mask to `mask & 0o777` and returns the old mask. +pub fn sys_umask(mask: u32) -> SyscallResult { + crate::arch_without_interrupts(|| { + if let Some(thread_id) = crate::task::scheduler::current_thread_id() { + let mut manager_guard = crate::process::manager(); + if let Some(ref mut manager) = *manager_guard { + if let Some((_pid, process)) = manager.find_process_by_thread_mut(thread_id) { + let old = process.umask; + process.umask = mask & 0o777; + return SyscallResult::Ok(old as u64); + } + } + } + SyscallResult::Ok(0o022) + }) +} + +// ============================================================================= +// pread64 / pwrite64 syscalls +// ============================================================================= + +/// pread64 - Read from file at given offset without changing file position +pub fn sys_pread64(fd: i32, buf_ptr: u64, count: u64, offset: i64) -> SyscallResult { + use crate::ipc::FdKind; + + if buf_ptr == 0 || count == 0 { + return SyscallResult::Ok(0); + } + if offset < 0 { + return SyscallResult::Err(super::errno::EINVAL as u64); + } + + let thread_id = match crate::task::scheduler::current_thread_id() { + Some(id) => id, + None => return SyscallResult::Err(super::errno::EBADF as u64), + }; + + // Extract file info from fd table under process lock + let fd_result: Result<(u64, usize), u64> = crate::arch_without_interrupts(|| { + let manager_guard = crate::process::manager(); + if let Some(ref manager) = *manager_guard { + if let Some((_pid, process)) = manager.find_process_by_thread(thread_id) { + if let Some(fd_entry) = process.fd_table.get(fd) { + match &fd_entry.kind { + FdKind::RegularFile(file_ref) => { + let file = file_ref.lock(); + return Ok((file.inode_num, file.mount_id)); + } + FdKind::PipeRead(_) | FdKind::PipeWrite(_) => { + return Err(super::errno::ESPIPE as u64); + } + _ => return Err(super::errno::ESPIPE as u64), + } + } + return Err(super::errno::EBADF as u64); + } + Err(super::errno::EBADF as u64) + } else { + Err(super::errno::EBADF as u64) + } + }); + + let (inode_num, mount_id) = match fd_result { + Ok((ino, mid)) => (ino, mid), + Err(e) => return SyscallResult::Err(e), + }; + + let file_offset = offset as u64; + + // Read from ext2 at the given offset (no process lock held) + use crate::fs::ext2; + let read_fn = |fs: &ext2::Ext2Fs| -> SyscallResult { + let inode = match fs.read_inode(inode_num as u32) { + Ok(i) => i, + Err(_) => return SyscallResult::Err(super::errno::EIO as u64), + }; + let file_size = inode.size(); + if file_offset >= file_size { + return SyscallResult::Ok(0); + } + let to_read = core::cmp::min(count, file_size - file_offset) as usize; + match fs.read_file_range(&inode, file_offset, to_read) { + Ok(data) => { + let actual = core::cmp::min(data.len(), to_read); + unsafe { + core::ptr::copy_nonoverlapping( + data.as_ptr(), + buf_ptr as *mut u8, + actual, + ); + } + SyscallResult::Ok(actual as u64) + } + Err(_) => SyscallResult::Err(super::errno::EIO as u64), + } + }; + + let is_home = ext2::home_mount_id().map_or(false, |id| id == mount_id); + if is_home { + let fs_guard = ext2::home_fs_read(); + match fs_guard.as_ref() { + Some(fs) => read_fn(fs), + None => SyscallResult::Err(super::errno::EIO as u64), + } + } else { + let fs_guard = ext2::root_fs_read(); + match fs_guard.as_ref() { + Some(fs) => read_fn(fs), + None => SyscallResult::Err(super::errno::EIO as u64), + } + } +} + +/// pwrite64 - Write to file at given offset without changing file position +pub fn sys_pwrite64(fd: i32, buf_ptr: u64, count: u64, offset: i64) -> SyscallResult { + use crate::ipc::FdKind; + + if buf_ptr == 0 || count == 0 { + return SyscallResult::Ok(0); + } + if offset < 0 { + return SyscallResult::Err(super::errno::EINVAL as u64); + } + + let thread_id = match crate::task::scheduler::current_thread_id() { + Some(id) => id, + None => return SyscallResult::Err(super::errno::EBADF as u64), + }; + + // Extract file info from fd table under process lock + let fd_result: Result<(u64, usize), u64> = crate::arch_without_interrupts(|| { + let manager_guard = crate::process::manager(); + if let Some(ref manager) = *manager_guard { + if let Some((_pid, process)) = manager.find_process_by_thread(thread_id) { + if let Some(fd_entry) = process.fd_table.get(fd) { + match &fd_entry.kind { + FdKind::RegularFile(file_ref) => { + let file = file_ref.lock(); + return Ok((file.inode_num, file.mount_id)); + } + FdKind::PipeRead(_) | FdKind::PipeWrite(_) => { + return Err(super::errno::ESPIPE as u64); + } + _ => return Err(super::errno::ESPIPE as u64), + } + } + return Err(super::errno::EBADF as u64); + } + Err(super::errno::EBADF as u64) + } else { + Err(super::errno::EBADF as u64) + } + }); + + let (inode_num, mount_id) = match fd_result { + Ok((ino, mid)) => (ino, mid), + Err(e) => return SyscallResult::Err(e), + }; + + let file_offset = offset as u64; + + // Read user data (no process lock held) + let data = match copy_from_user(buf_ptr, count as usize) { + Ok(d) => d, + Err(_) => return SyscallResult::Err(super::errno::EFAULT as u64), + }; + + use crate::fs::ext2; + let write_fn = |fs: &mut ext2::Ext2Fs| -> SyscallResult { + match fs.write_file_range(inode_num as u32, file_offset, &data) { + Ok(written) => SyscallResult::Ok(written as u64), + Err(_) => SyscallResult::Err(super::errno::EIO as u64), + } + }; + + let is_home = ext2::home_mount_id().map_or(false, |id| id == mount_id); + if is_home { + let mut fs_guard = ext2::home_fs_write(); + match fs_guard.as_mut() { + Some(fs) => write_fn(fs), + None => SyscallResult::Err(super::errno::EIO as u64), + } + } else { + let mut fs_guard = ext2::root_fs_write(); + match fs_guard.as_mut() { + Some(fs) => write_fn(fs), + None => SyscallResult::Err(super::errno::EIO as u64), + } + } +} diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index 76b0ba7f..67a0c68e 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -179,6 +179,20 @@ pub enum SyscallNumber { EpollCtl, EpollWait, EpollPwait, + // Process identity + Getuid, + Geteuid, + Getgid, + Getegid, + Setuid, + Setgid, + // File creation mask + Umask, + // Timestamps + Utimensat, + // Positional I/O + Pread64, + Pwrite64, } #[allow(dead_code)] @@ -289,6 +303,16 @@ impl SyscallNumber { 281 => Some(Self::EpollPwait), 291 => Some(Self::EpollCreate1), 302 => Some(Self::Prlimit64), + 95 => Some(Self::Umask), + 102 => Some(Self::Getuid), + 104 => Some(Self::Getgid), + 105 => Some(Self::Setuid), + 106 => Some(Self::Setgid), + 107 => Some(Self::Geteuid), + 108 => Some(Self::Getegid), + 17 => Some(Self::Pread64), + 18 => Some(Self::Pwrite64), + 280 => Some(Self::Utimensat), 318 => Some(Self::GetRandom), // PTY syscalls (Breenix-specific, same on both archs) 400 => Some(Self::PosixOpenpt), @@ -407,6 +431,19 @@ impl SyscallNumber { // Wait 260 => Some(Self::Wait4), 261 => Some(Self::Prlimit64), + // Positional I/O + 67 => Some(Self::Pread64), + 68 => Some(Self::Pwrite64), + // Timestamps + 88 => Some(Self::Utimensat), + // Process identity + 144 => Some(Self::Setgid), + 146 => Some(Self::Setuid), + 166 => Some(Self::Umask), + 174 => Some(Self::Getuid), + 175 => Some(Self::Geteuid), + 176 => Some(Self::Getgid), + 177 => Some(Self::Getegid), // Random 278 => Some(Self::GetRandom), // PTY syscalls (Breenix-specific, same on both archs) diff --git a/scripts/create_ext2_disk.sh b/scripts/create_ext2_disk.sh index b838e6d2..f8a71a3d 100755 --- a/scripts/create_ext2_disk.sh +++ b/scripts/create_ext2_disk.sh @@ -140,6 +140,17 @@ if [[ "$(uname)" == "Darwin" ]]; then echo " Installed $sbin_count binaries in /sbin" echo " Installed $test_count test binaries in /usr/local/test/bin" + # Create /etc with passwd and group for musl getpwuid/getgrgid + mkdir -p /mnt/ext2/etc + cat > /mnt/ext2/etc/passwd << PASSWD +root:x:0:0:root:/root:/bin/bsh +nobody:x:65534:65534:nobody:/nonexistent:/bin/false +PASSWD + cat > /mnt/ext2/etc/group << GROUP +root:x:0: +nobody:x:65534: +GROUP + # Create /tmp for filesystem write tests mkdir -p /mnt/ext2/tmp @@ -255,6 +266,17 @@ else echo " Installed $sbin_count binaries in /sbin" echo " Installed $test_count test binaries in /usr/local/test/bin" + # Create /etc with passwd and group for musl getpwuid/getgrgid + mkdir -p "$MOUNT_DIR/etc" + cat > "$MOUNT_DIR/etc/passwd" << PASSWD +root:x:0:0:root:/root:/bin/bsh +nobody:x:65534:65534:nobody:/nonexistent:/bin/false +PASSWD + cat > "$MOUNT_DIR/etc/group" << GROUP +root:x:0: +nobody:x:65534: +GROUP + # Create /tmp for filesystem write tests mkdir -p "$MOUNT_DIR/tmp" diff --git a/userspace/c-programs/Makefile b/userspace/c-programs/Makefile index 8ac7787e..49ef107c 100644 --- a/userspace/c-programs/Makefile +++ b/userspace/c-programs/Makefile @@ -45,7 +45,7 @@ LDFLAGS := -T $(LINKER_SCRIPT) --static \ AARCH64_DIR := $(BREENIX_ROOT)/userspace/programs/aarch64 # Programs -PROGRAMS := hello_musl env_musl_test uname_musl_test rlimit_musl_test +PROGRAMS := hello_musl env_musl_test uname_musl_test rlimit_musl_test identity_musl_test .PHONY: all install clean @@ -66,6 +66,9 @@ uname_musl_test.elf: uname_test.o rlimit_musl_test.elf: rlimit_test.o $(LLD) $(LDFLAGS) $< -o $@ +identity_musl_test.elf: identity_test.o + $(LLD) $(LDFLAGS) $< -o $@ + install: all for prog in $(PROGRAMS); do cp $$prog.elf $(AARCH64_DIR)/$$prog.elf; done diff --git a/userspace/c-programs/identity_test.c b/userspace/c-programs/identity_test.c new file mode 100644 index 00000000..78ccd499 --- /dev/null +++ b/userspace/c-programs/identity_test.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + int pass = 0; + int fail = 0; + + /* Test 1: getuid() == 0 (running as root) */ + uid_t uid = getuid(); + if (uid == 0) { + printf("PASS: getuid() = %d\n", uid); + pass++; + } else { + printf("FAIL: getuid() = %d (expected 0)\n", uid); + fail++; + } + + /* Test 2: getgid() == 0 */ + gid_t gid = getgid(); + if (gid == 0) { + printf("PASS: getgid() = %d\n", gid); + pass++; + } else { + printf("FAIL: getgid() = %d (expected 0)\n", gid); + fail++; + } + + /* Test 3: geteuid() == 0 */ + uid_t euid = geteuid(); + if (euid == 0) { + printf("PASS: geteuid() = %d\n", euid); + pass++; + } else { + printf("FAIL: geteuid() = %d (expected 0)\n", euid); + fail++; + } + + /* Test 4: getegid() == 0 */ + gid_t egid = getegid(); + if (egid == 0) { + printf("PASS: getegid() = %d\n", egid); + pass++; + } else { + printf("FAIL: getegid() = %d (expected 0)\n", egid); + fail++; + } + + /* Test 5: umask round-trip */ + mode_t old_mask = umask(077); + if (old_mask == 022) { + printf("PASS: umask(077) returned old mask 0%03o\n", old_mask); + pass++; + } else { + printf("FAIL: umask(077) returned 0%03o (expected 022)\n", old_mask); + fail++; + } + + /* Test 6: umask returns previous value */ + mode_t new_mask = umask(022); + if (new_mask == 077) { + printf("PASS: umask(022) returned 0%03o\n", new_mask); + pass++; + } else { + printf("FAIL: umask(022) returned 0%03o (expected 077)\n", new_mask); + fail++; + } + + /* Test 7: getpwuid(0) returns root */ + struct passwd *pw = getpwuid(0); + if (pw != NULL && strcmp(pw->pw_name, "root") == 0) { + printf("PASS: getpwuid(0)->pw_name = \"%s\"\n", pw->pw_name); + pass++; + } else if (pw != NULL) { + printf("FAIL: getpwuid(0)->pw_name = \"%s\" (expected \"root\")\n", pw->pw_name); + fail++; + } else { + printf("FAIL: getpwuid(0) returned NULL\n"); + fail++; + } + + /* Test 8: getgrgid(0) returns root */ + struct group *gr = getgrgid(0); + if (gr != NULL && strcmp(gr->gr_name, "root") == 0) { + printf("PASS: getgrgid(0)->gr_name = \"%s\"\n", gr->gr_name); + pass++; + } else if (gr != NULL) { + printf("FAIL: getgrgid(0)->gr_name = \"%s\" (expected \"root\")\n", gr->gr_name); + fail++; + } else { + printf("FAIL: getgrgid(0) returned NULL\n"); + fail++; + } + + printf("\nidentity_test: %d passed, %d failed\n", pass, fail); + return fail > 0 ? 1 : 0; +}