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
2 changes: 2 additions & 0 deletions src/glibc/lind_syscall/lind_syscall_num.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@
#define STATFS_SYSCALL 137
#define FSTATFS_SYSCALL 138
#define GETHOSTNAME_SYSCALL 170
#define SETXATTR_SYSCALL 188
#define LISTXATTR_SYSCALL 194
#define FUTEX_SYSCALL 202
#define EPOLL_CREATE_SYSCALL 213
#define CLOCK_GETTIME_SYSCALL 228
Expand Down
21 changes: 21 additions & 0 deletions src/glibc/sysdeps/unix/sysv/linux/i386/i686/listxattr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <sys/xattr.h>
#include <errno.h>
#include <syscall-template.h>
#include <lind_syscall_num.h>
#include <addr_translation.h>

/* List extended attributes associated with the file specified by path.
If list is NULL and size is zero, returns the buffer size needed.
Returns the size of the attribute list, or -1 and sets errno on error. */
ssize_t
__listxattr (const char *path, char *list, size_t size)
{
return MAKE_LEGACY_SYSCALL (LISTXATTR_SYSCALL, "syscall|listxattr",
(uint64_t) TRANSLATE_GUEST_POINTER_TO_HOST (path),
(uint64_t) TRANSLATE_GUEST_POINTER_TO_HOST (list),
(uint64_t) size,
NOTUSED,
NOTUSED,
NOTUSED, TRANSLATE_ERRNO_ON);
}
weak_alias(__listxattr, listxattr)
21 changes: 21 additions & 0 deletions src/glibc/sysdeps/unix/sysv/linux/i386/i686/setxattr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <sys/xattr.h>
#include <errno.h>
#include <syscall-template.h>
#include <lind_syscall_num.h>
#include <addr_translation.h>

/* Set extended attributes on a file specified by path.
Returns 0 on success, or -1 and sets errno on error. */
int
__setxattr (const char *path, const char *name, const void *value,
size_t size, int flags)
{
return MAKE_LEGACY_SYSCALL (SETXATTR_SYSCALL, "syscall|setxattr",
(uint64_t) TRANSLATE_GUEST_POINTER_TO_HOST (path),
(uint64_t) TRANSLATE_GUEST_POINTER_TO_HOST (name),
(uint64_t) TRANSLATE_GUEST_POINTER_TO_HOST (value),
(uint64_t) size,
(uint64_t) flags,
NOTUSED, TRANSLATE_ERRNO_ON);
}
weak_alias(__setxattr, setxattr)
144 changes: 144 additions & 0 deletions src/rawposix/src/fs_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4803,3 +4803,147 @@ pub extern "C" fn symlinkat_syscall(

ret
}

/// Linux reference: https://man7.org/linux/man-pages/man2/setxattr.2.html
///
/// Sets the value of an extended attribute identified by `name`
/// and associated with the given `path` in the filesystem.
///
/// ## Arguments:
/// * `cageid` - Cage identifier
/// * `path_arg` - Path to the file
/// * `path_cageid` - Cage ID for path
/// * `name_arg` - Name of the extended attribute
/// * `name_cageid` - Cage ID for name
/// * `value_arg` - Pointer to the value to set
/// * `value_cageid` - Cage ID for value
/// * `size_arg` - Size of the value
/// * `size_cageid` - Cage ID for size
/// * `flags_arg` - Flags for setxattr (e.g., XATTR_CREATE, XATTR_REPLACE)
/// * `flags_cageid` - Cage ID for flags
///
/// ## Returns:
/// On success, 0 is returned. On error, -1 is returned and errno is set appropriately.
pub extern "C" fn setxattr_syscall(
cageid: u64,
path_arg: u64,
path_cageid: u64,
name_arg: u64,
name_cageid: u64,
value_arg: u64,
value_cageid: u64,
size_arg: u64,
size_cageid: u64,
flags_arg: u64,
flags_cageid: u64,
arg6: u64,
arg6_cageid: u64,
) -> i32 {
// Type conversion for path
let path = match sc_convert_path_to_host(path_arg, path_cageid, cageid) {
Ok(path) => path,
Err(e) => return syscall_error(e, "setxattr", "path conversion failed"),
};

// Type conversion for name (attribute name, not a path - no path normalization needed)
let name_str = match get_cstr(name_arg) {
Ok(s) => s,
Err(_) => return syscall_error(Errno::EFAULT, "setxattr", "name conversion failed"),
};
let name = match std::ffi::CString::new(name_str) {
Ok(s) => s,
Err(_) => return syscall_error(Errno::EINVAL, "setxattr", "name contains null byte"),
};

// Type conversion for value buffer
let value = sc_convert_buf(value_arg, value_cageid, cageid) as *const libc::c_void;
let size = sc_convert_sysarg_to_usize(size_arg, size_cageid, cageid);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the comment for listxattr, there can be an out-of-bounds read here. We need to check if pointer + size is within the allowed cage memory.

If the guest allocates a 5-byte buffer containing "hello", but calls setxattr with a size of 1000, the host kernel will read 1000 bytes starting from that pointer and save it to the file's extended attributes. The guest just forced the host kernel to read 995 bytes of adjacent memory and save it to the disk. The guest can then read that file's extended attributes to steal the host's memory.

@Yaxuan-w @rennergade @qianxichen233

let flags = sc_convert_sysarg_to_i32(flags_arg, flags_cageid, cageid);

// Validate unused arg
if !sc_unusedarg(arg6, arg6_cageid) {
panic!(
"{}: unused arguments contain unexpected values -- security violation",
"setxattr_syscall"
);
}

// Call to kernel setxattr
let ret = unsafe { libc::setxattr(path.as_ptr(), name.as_ptr(), value, size, flags) };

if ret < 0 {
let errno = get_errno();
return handle_errno(errno, "setxattr");
}

ret
}

/// Linux reference: https://man7.org/linux/man-pages/man2/listxattr.2.html
///
/// Retrieves the list of extended attribute names associated with the given `path`.
///
/// ## Arguments:
/// * `cageid` - Cage identifier
/// * `path_arg` - Path to the file
/// * `path_cageid` - Cage ID for path
/// * `list_arg` - Pointer to buffer to store the list of attribute names
/// * `list_cageid` - Cage ID for list
/// * `size_arg` - Size of the buffer
/// * `size_cageid` - Cage ID for size
///
/// ## Returns:
/// On success, returns the size of the list of attribute names. If `list` is NULL and `size` is zero,
/// returns the size of the buffer needed to store the list.
/// On error, -1 is returned and errno is set appropriately.
pub extern "C" fn listxattr_syscall(
cageid: u64,
path_arg: u64,
path_cageid: u64,
list_arg: u64,
list_cageid: u64,
size_arg: u64,
size_cageid: u64,
arg4: u64,
arg4_cageid: u64,
arg5: u64,
arg5_cageid: u64,
arg6: u64,
arg6_cageid: u64,
) -> i32 {
// Type conversion for path
let path = match sc_convert_path_to_host(path_arg, path_cageid, cageid) {
Ok(path) => path,
Err(e) => return syscall_error(e, "listxattr", "path conversion failed"),
};

// Type conversion for list buffer (may be NULL)
let list = if list_arg == 0 {
Comment thread
celinehoang177 marked this conversation as resolved.
std::ptr::null_mut()
} else {
sc_convert_to_u8_mut(list_arg, list_cageid, cageid) as *mut libc::c_char
};
let size = sc_convert_sysarg_to_usize(size_arg, size_cageid, cageid);
Copy link
Copy Markdown
Contributor

@vidyalakshmir vidyalakshmir Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the earlier line, guest memory pointers are translated using sc_convert_buf without validating the guest's provided size argument. This allows a malicious guest to allocate a tiny buffer but pass a massive size, tricking the host kernel into writing outside the guest's memory boundaries (a buffer overflow).

Proposed Fix: We need to add a bounds check to validate that the entire contiguous memory region from pointer to pointer + size resides completely within the allowed cage memory. (pointer is list_arg).
@Yaxuan-w @rennergade @qianxichen233


// Validate unused args
if !(sc_unusedarg(arg4, arg4_cageid)
&& sc_unusedarg(arg5, arg5_cageid)
&& sc_unusedarg(arg6, arg6_cageid))
{
panic!(
"{}: unused arguments contain unexpected values -- security violation",
"listxattr_syscall"
);
}

// Call to kernel listxattr
let ret = unsafe { libc::listxattr(path.as_ptr(), list, size) };

if ret < 0 {
let errno = get_errno();
return handle_errno(errno, "listxattr");
}
Copy link
Copy Markdown
Contributor

@vidyalakshmir vidyalakshmir Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setxattr and listxattr implementations allow the guest to operate on arbitrary xattr namespaces. Since the host kernel evaluates these calls using the sandbox runtime's privileges, a malicious guest can write to security.* attributes (e.g. security.capability to grant root capabilities) or modify ACLs via system.* — fully bypassing host access controls. All xattr operations must be restricted to the user.* namespace, with anything else rejected at the shim layer.
@Yaxuan-w @rennergade @qianxichen233


// Convert ssize_t to i32 safely
ret.try_into().unwrap_or(i32::MAX)
}
17 changes: 10 additions & 7 deletions src/rawposix/src/syscall_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ use super::fs_calls::{
close_syscall, dup2_syscall, dup3_syscall, dup_syscall, fchdir_syscall, fchmod_syscall,
fcntl_syscall, fdatasync_syscall, flock_syscall, fstat_syscall, fstatfs_syscall, fsync_syscall,
ftruncate_syscall, futex_syscall, getcwd_syscall, getdents_syscall, getrandom_syscall,
ioctl_syscall, link_syscall, lseek_syscall, lstat_syscall, mkdir_syscall, mknod_syscall,
mmap_syscall, mprotect_syscall, munmap_syscall, nanosleep_time64_syscall, open_syscall,
openat_syscall, pipe2_syscall, pipe_syscall, pread_syscall, preadv_syscall, pwrite_syscall,
pwritev_syscall, read_syscall, readlink_syscall, readlinkat_syscall, readv_syscall,
rename_syscall, rmdir_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, write_syscall, writev_syscall,
ioctl_syscall, link_syscall, listxattr_syscall, lseek_syscall, lstat_syscall, mkdir_syscall,
mknod_syscall, mmap_syscall, mprotect_syscall, munmap_syscall, nanosleep_time64_syscall,
open_syscall, openat_syscall, pipe2_syscall, pipe_syscall, pread_syscall, preadv_syscall,
pwrite_syscall, pwritev_syscall, read_syscall, readlink_syscall, readlinkat_syscall,
readv_syscall, rename_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,
write_syscall, writev_syscall,
};
use super::init::RawCallFunc;
use super::net_calls::{
Expand Down Expand Up @@ -133,6 +134,8 @@ pub const SYSCALL_TABLE: &[(u64, RawCallFunc)] = &[
syscall_const::GETHOSTNAME_SYSCALL as u64,
gethostname_syscall,
),
(188 as u64, setxattr_syscall),
(194 as u64, listxattr_syscall),
(syscall_const::FUTEX_SYSCALL as u64, futex_syscall),
(
syscall_const::EPOLL_CREATE_SYSCALL as u64,
Expand Down