Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/cage/src/cage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub use once_cell::sync::Lazy;
/// interaction and increases efficiency.
pub use parking_lot::{Mutex, RwLock};
pub use std::path::{Path, PathBuf};
pub use std::sync::atomic::{AtomicBool, AtomicI32, AtomicPtr, AtomicU64, Ordering};
pub use std::sync::atomic::{AtomicBool, AtomicI32, AtomicPtr, AtomicU32, AtomicU64, Ordering};
pub use std::sync::Arc;
use sysdefs::constants::lind_platform_const::MAX_CAGEID;
use sysdefs::constants::sys_const::EXIT_SUCCESS;
Expand Down Expand Up @@ -251,6 +251,9 @@ pub struct Cage {
/// Could also be moved to wasmtime/crate/lind-3i if grate_inflight
/// tracking is considered a VMContext-level concern.
pub grate_inflight: AtomicU64,
/// The file mode creation mask for this cage. Applied during file/directory
/// creation to restrict permissions. Mirrors Linux per-process umask.
pub umask: AtomicU32,
}

/// We achieve an O(1) complexity for our cage map implementation through the following three approaches:
Expand Down Expand Up @@ -464,6 +467,7 @@ mod tests {
exit_group_initiated: AtomicBool::new(false),
is_dead: AtomicBool::new(false),
grate_inflight: AtomicU64::new(0),
umask: AtomicU32::new(0o022),
};

add_cage(2, test_cage);
Expand Down
1 change: 1 addition & 0 deletions src/glibc/lind_syscall/lind_syscall_num.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
#define FCHMOD_SYSCALL 91
#define CHOWN_SYSCALL 92
#define LCHOWN_SYSCALL 94
#define UMASK_SYSCALL 95

#define GETUID_SYSCALL 102
#define GETGID_SYSCALL 104
Expand Down
6 changes: 5 additions & 1 deletion src/glibc/sysdeps/unix/sysv/linux/umask.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <syscall-template.h>
#include <lind_syscall_num.h>

mode_t
umask (mode_t mask)
{
return -1;
return (mode_t) MAKE_LEGACY_SYSCALL (
UMASK_SYSCALL, "syscall|umask", (uint64_t) mask,
NOTUSED, NOTUSED, NOTUSED, NOTUSED, NOTUSED, TRANSLATE_ERRNO_ON);
}
70 changes: 68 additions & 2 deletions src/rawposix/src/fs_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use cage::{
use dashmap::mapref::entry::Entry::{Occupied, Vacant};
use fdtables;
use libc::c_void;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64};
use std::sync::atomic::{Ordering, Ordering::*};
use std::sync::Arc;
use sysdefs::constants::err_const::{get_errno, handle_errno, syscall_error, Errno};
use sysdefs::constants::fs_const::{
Expand Down Expand Up @@ -86,6 +88,7 @@ pub extern "C" fn openat_syscall(
};
let oflag = sc_convert_sysarg_to_i32(oflag_arg, oflag_cageid, cageid);
let mode = sc_convert_sysarg_to_u32(mode_arg, mode_cageid, cageid);

if !(sc_unusedarg(arg5, arg5_cageid) && sc_unusedarg(arg6, arg6_cageid)) {
panic!(
"{}: unused arguments contain unexpected values -- security violation",
Expand Down Expand Up @@ -128,8 +131,11 @@ pub extern "C" fn openat_syscall(
Err(_) => return syscall_error(Errno::EINVAL, "openat", "invalid path"),
};

let kernel_fd =
unsafe { libc::openat(host_fd, c_path.as_ptr(), oflag, mode as libc::mode_t) };
let cage = get_cage(cageid).unwrap();
let umask = cage.umask.load(Ordering::Relaxed);
let masked_mode = (mode & !umask) as libc::mode_t;
let kernel_fd = unsafe { libc::openat(host_fd, c_path.as_ptr(), oflag, masked_mode) };

if kernel_fd < 0 {
return handle_errno(get_errno(), "openat_syscall");
}
Expand Down Expand Up @@ -201,6 +207,10 @@ pub extern "C" fn open_syscall(
);
}

let cage = get_cage(cageid).unwrap();
let umask = cage.umask.load(Ordering::Relaxed);
let mode = mode & !umask;

// Get the kernel fd first
let kernel_fd = unsafe { libc::open(path.as_ptr(), oflag, mode) };

Expand Down Expand Up @@ -502,6 +512,10 @@ pub extern "C" fn mkdir_syscall(
);
}

let cage = get_cage(cageid).unwrap();
let umask = cage.umask.load(Ordering::Relaxed);
let mode = mode & !umask;

let ret = unsafe { libc::mkdir(path.as_ptr(), mode) };
// Error handling
if ret < 0 {
Expand Down Expand Up @@ -557,6 +571,9 @@ pub extern "C" fn mknod_syscall(
"mknod_syscall"
);
}
let cage = get_cage(cageid).unwrap();
let umask = cage.umask.load(Ordering::Relaxed);
let mode = mode & !umask;

let ret = unsafe { libc::mknod(path.as_ptr(), mode, dev) };
if ret < 0 {
Expand Down Expand Up @@ -5524,3 +5541,52 @@ pub extern "C" fn listxattr_syscall(
// Convert ssize_t to i32 safely
ret.try_into().unwrap_or(i32::MAX)
}

/// Reference to Linux: https://man7.org/linux/man-pages/man2/umask.2.html
///
/// Linux `umask()` syscall sets the calling process's file mode creation mask
/// to `mask & 0777` and returns the previous value of the mask. The umask is
/// applied during file/directory creation (open, mkdir, mknod) to restrict
/// permissions. Since each cage is isolated, the umask is stored per-cage in
/// the Cage struct rather than delegating to the host kernel.
///
/// ## Input:
/// - cageid: current cage identifier
/// - mask_arg: the new umask value (only the lower 9 bits are used)
/// - arg2-arg6: additional arguments which are expected to be unused
///
/// ## Returns:
/// - Always succeeds and returns the previous umask value.
pub extern "C" fn umask_syscall(
cageid: u64,
mask_arg: u64,
mask_cageid: u64,
arg2: u64,
arg2_cageid: u64,
arg3: u64,
arg3_cageid: u64,
arg4: u64,
arg4_cageid: u64,
arg5: u64,
arg5_cageid: u64,
arg6: u64,
arg6_cageid: u64,
) -> i32 {
let mask = sc_convert_sysarg_to_u32(mask_arg, mask_cageid, cageid);

if !(sc_unusedarg(arg2, arg2_cageid)
&& sc_unusedarg(arg3, arg3_cageid)
&& sc_unusedarg(arg4, arg4_cageid)
&& sc_unusedarg(arg5, arg5_cageid)
&& sc_unusedarg(arg6, arg6_cageid))
{
panic!(
"{}: unused arguments contain unexpected values -- security violation",
"umask_syscall"
);
}

let cage = get_cage(cageid).unwrap();
let old_mask = cage.umask.swap(mask & 0o777, Ordering::SeqCst);
old_mask as i32
}
3 changes: 2 additions & 1 deletion src/rawposix/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use fdtables;
use parking_lot::{Mutex, RwLock};
use std::ffi::CString;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering::*};
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, Ordering::*};
use std::sync::Arc;
use sysdefs::constants::{
EXIT_SUCCESS, FDKIND_KERNEL, INIT_CAGEID, MAIN_THREADID, RAWPOSIX_CAGEID, STDERR_FILENO,
Expand Down Expand Up @@ -287,6 +287,7 @@ pub fn rawposix_start(verbosity: isize) {
exit_group_initiated: AtomicBool::new(false),
is_dead: AtomicBool::new(false),
grate_inflight: AtomicU64::new(0),
umask: AtomicU32::new(0o022), // POSIX default: masks group-write and other-write bits
};

// Add cage to cagetable
Expand Down
5 changes: 3 additions & 2 deletions src/rawposix/src/sys_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use libc::sched_yield;
use parking_lot::{Mutex, RwLock};
use std::ffi::CString;
use std::path::PathBuf;
use std::sync::atomic::Ordering::*;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64};
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64};
use std::sync::atomic::{Ordering, Ordering::*};
use std::sync::Arc;
use std::time::Duration;
use sysdefs::constants::err_const::{syscall_error, Errno, VERBOSE};
Expand Down Expand Up @@ -148,6 +148,7 @@ pub extern "C" fn fork_syscall(
exit_group_initiated: AtomicBool::new(false),
is_dead: AtomicBool::new(false),
grate_inflight: AtomicU64::new(0),
umask: AtomicU32::new(selfcage.umask.load(Ordering::SeqCst)),
};

// increment child counter for parent
Expand Down
5 changes: 3 additions & 2 deletions src/rawposix/src/syscall_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use super::fs_calls::{
read_syscall, readlink_syscall, readlinkat_syscall, readv_syscall, rename_syscall,
renameat2_syscall, renameat_syscall, rmdir_syscall, setxattr_syscall, shmat_syscall,
shmctl_syscall, shmdt_syscall, shmget_syscall, stat_syscall, statfs_syscall, symlink_syscall,
symlinkat_syscall, sync_file_range_syscall, truncate_syscall, unlink_syscall, unlinkat_syscall,
utimensat_syscall, write_syscall, writev_syscall,
symlinkat_syscall, sync_file_range_syscall, truncate_syscall, umask_syscall, unlink_syscall,
unlinkat_syscall, utimensat_syscall, write_syscall, writev_syscall,
};
use super::init::RawCallFunc;
use super::net_calls::{
Expand Down Expand Up @@ -129,6 +129,7 @@ pub const SYSCALL_TABLE: &[(u64, RawCallFunc)] = &[
(syscall_const::FCHMOD_SYSCALL as u64, fchmod_syscall),
(syscall_const::CHOWN_SYSCALL as u64, chown_syscall),
(syscall_const::LCHOWN_SYSCALL as u64, lchown_syscall),
(syscall_const::UMASK_SYSCALL as u64, umask_syscall),
(syscall_const::GETUID_SYSCALL as u64, getuid_syscall),
(syscall_const::GETGID_SYSCALL as u64, getgid_syscall),
(syscall_const::GETEUID_SYSCALL as u64, geteuid_syscall),
Expand Down
1 change: 1 addition & 0 deletions src/sysdefs/src/constants/syscall_const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub const CHMOD_SYSCALL: i32 = 90;
pub const FCHMOD_SYSCALL: i32 = 91;
pub const CHOWN_SYSCALL: i32 = 92;
pub const LCHOWN_SYSCALL: i32 = 94;
pub const UMASK_SYSCALL: i32 = 95;
pub const GETUID_SYSCALL: i32 = 102;
pub const GETGID_SYSCALL: i32 = 104;
pub const GETEUID_SYSCALL: i32 = 107;
Expand Down
88 changes: 88 additions & 0 deletions tests/unit-tests/file_tests/deterministic/umask.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Before running this test:
* 1. Ensure the test directory exists in $LIND_FS_ROOT.
* 2. No pre-existing files named "umask_test_file.txt" or "umask_test_dir".
*/


#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>

int main() {
mode_t old_mask;
int fd;
struct stat st;

/* Cleanup from any previous run */
rmdir("testfiles/umask_test_dir");
unlink("testfiles/umask_test_file.txt");
unlink("testfiles/umask_test_file2.txt");


/* Test 1: umask returns previous mask */
old_mask = umask(0022);
if (old_mask != 0022) {
printf("FAIL: expected initial umask 0022, got %04o\n", old_mask);
return 1;
}
umask(old_mask); /* restore */
printf("PASS: umask returns previous mask\n");

/* Test 2: umask applied on open — file created with 0666 & ~0022 = 0644 */
umask(0022);
fd = open("testfiles/umask_test_file.txt", O_CREAT | O_WRONLY, 0666);
if (fd == -1) {
perror("open failed");
return 1;
}
close(fd);

if (stat("testfiles/umask_test_file.txt", &st) == -1) {
perror("stat failed");
return 1;
}
if ((st.st_mode & 0777) != 0644) {
printf("FAIL: expected file perms 0644, got %04o\n", st.st_mode & 0777);
return 1;
}
printf("PASS: umask applied correctly on open (0644)\n");

/* Test 3: umask applied on mkdir — dir created with 0777 & ~0022 = 0755 */
umask(0022);
if (mkdir("testfiles/umask_test_dir", 0777) == -1) {
perror("mkdir failed");
return 1;
}
if (stat("testfiles/umask_test_dir", &st) == -1) {
perror("stat on dir failed");
return 1;
}
if ((st.st_mode & 0777) != 0755) {
printf("FAIL: expected dir perms 0755, got %04o\n", st.st_mode & 0777);
return 1;
}
printf("PASS: umask applied correctly on mkdir (0755)\n");

/* Test 4: changing umask affects subsequent creates */
umask(0077);
fd = open("testfiles/umask_test_file2.txt", O_CREAT | O_WRONLY, 0666);
if (fd == -1) {
perror("open failed");
return 1;
}
close(fd);
if (stat("testfiles/umask_test_file2.txt", &st) == -1) {
perror("stat failed");
return 1;
}
if ((st.st_mode & 0777) != 0600) {
printf("FAIL: expected file perms 0600, got %04o\n", st.st_mode & 0777);
return 1;
}
printf("PASS: umask 0077 applied correctly on open (0600)\n");

printf("All umask tests passed.\n");
return 0;
}