Skip to content
Draft
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
1 change: 1 addition & 0 deletions kernel/src/filesystem/vfs/syscall/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod open_utils;
mod readlink_at;
mod rename_utils;
mod sys_chdir;
mod sys_chroot;
mod sys_close;
mod sys_dup;
mod sys_dup3;
Expand Down
91 changes: 91 additions & 0 deletions kernel/src/filesystem/vfs/syscall/sys_chroot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! System call handler for changing the root directory (chroot).

use system_error::SystemError;

use alloc::{string::String, vec::Vec};

use crate::arch::interrupt::TrapFrame;
use crate::arch::syscall::nr::SYS_CHROOT;
use crate::filesystem::vfs::{FileType, MAX_PATHLEN, VFS_MAX_FOLLOW_SYMLINK_TIMES};
use crate::process::cred::CAPFlags;
use crate::process::ProcessManager;
use crate::syscall::table::FormattedSyscallParam;
use crate::syscall::table::Syscall;
use crate::syscall::user_access::check_and_clone_cstr;

/// System call handler for the `chroot` syscall
///
/// Changes the root directory of the calling process.
pub struct SysChrootHandle;

impl Syscall for SysChrootHandle {
/// Returns the number of arguments expected by the `chroot` syscall
fn num_args(&self) -> usize {
1
}

/// 切换根目录(chroot)
///
/// 权限要求:CAP_SYS_CHROOT
///
/// 可能错误:
/// - EFAULT: 用户指针无效
/// - EINVAL: 路径编码非法
/// - ENOENT: 目标不存在
/// - ENOTDIR: 目标不是目录
/// - EPERM: 缺少 CAP_SYS_CHROOT 权限
fn handle(&self, args: &[usize], _frame: &mut TrapFrame) -> Result<usize, SystemError> {
let path_ptr = Self::path(args);

if path_ptr.is_null() {
return Err(SystemError::EFAULT);
}

// 权限检查:需要 CAP_SYS_CHROOT
let pcb = ProcessManager::current_pcb();
if !pcb.cred().has_capability(CAPFlags::CAP_SYS_CHROOT) {
return Err(SystemError::EPERM);
}

let path = check_and_clone_cstr(path_ptr, Some(MAX_PATHLEN))?
.into_string()
.map_err(|_| SystemError::EINVAL)?;

// 查找并跟随符号链接
let root_inode = pcb.fs_struct().root();
let inode = match root_inode.lookup_follow_symlink(&path, VFS_MAX_FOLLOW_SYMLINK_TIMES)
{
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

The error from lookup_follow_symlink is being discarded and always converted to ENOENT. This could hide other important errors such as EACCES (permission denied), ELOOP (too many symbolic links), or other filesystem errors. While this pattern is consistent with other syscalls in the codebase (like sys_chdir), it makes debugging more difficult and could confuse users who encounter permission issues but receive "file not found" errors instead.

Consider propagating the actual error or at least handling common cases explicitly:

let inode = root_inode
    .lookup_follow_symlink(&path, VFS_MAX_FOLLOW_SYMLINK_TIMES)
    .map_err(|e| match e {
        SystemError::EACCES => SystemError::EACCES,
        SystemError::ELOOP => SystemError::ELOOP,
        _ => SystemError::ENOENT,
    })?;
Suggested change
{
{
Err(SystemError::EACCES) => return Err(SystemError::EACCES),
Err(SystemError::ELOOP) => return Err(SystemError::ELOOP),

Copilot uses AI. Check for mistakes.
Err(_) => return Err(SystemError::ENOENT),
Ok(i) => i,
};

// 必须是目录
let metadata = inode.metadata()?;
if metadata.file_type != FileType::Dir {
return Err(SystemError::ENOTDIR);
}

// 更新进程的 fs 视图:设置新的 root。
// 同时将工作目录移动到新根,保证位于 chroot 内部。
pcb.fs_struct_mut().set_root(inode);
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

The comment states "同时将工作目录移动到新根,保证位于 chroot 内部" (simultaneously move the working directory to the new root to ensure it's inside the chroot), but the code only calls set_root(inode) without updating the working directory. This means the current working directory (CWD) could remain pointing to a path outside the new root, creating a potential security issue.

To fix this, you should also update the working directory. For example:

// 更新进程的 fs 视图:设置新的 root。
// 同时将工作目录移动到新根,保证位于 chroot 内部。
let fs_struct = pcb.fs_struct_mut();
fs_struct.set_root(inode.clone());
fs_struct.set_pwd(inode);
pcb.basic_mut().set_cwd(String::from("/"));
Suggested change
pcb.fs_struct_mut().set_root(inode);
let fs_struct = pcb.fs_struct_mut();
fs_struct.set_root(inode.clone());
fs_struct.set_pwd(inode);
pcb.basic_mut().set_cwd(String::from("/"));

Copilot uses AI. Check for mistakes.
Ok(0)
}

/// Formats the syscall parameters for display/debug purposes
fn entry_format(&self, args: &[usize]) -> Vec<FormattedSyscallParam> {
vec![FormattedSyscallParam::new(
"path",
format!("{:#x}", Self::path(args) as usize),
)]
}
}

impl SysChrootHandle {
/// Extracts the path argument from syscall parameters
fn path(args: &[usize]) -> *const u8 {
args[0] as *const u8
}
}

syscall_table_macros::declare_syscall!(SYS_CHROOT, SysChrootHandle);

4 changes: 4 additions & 0 deletions user/apps/tests/syscall/gvisor/blocklists/chroot_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ChrootTest.PermissionDenied
ChrootTest.CreatesNewRoot
ChrootTest.ProcMemSelfMapsNoEscapeProcOpen
ChrootTest.ProcMountsMountinfoNoEscape
1 change: 1 addition & 0 deletions user/apps/tests/syscall/gvisor/whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mknod_test
unlink_test
lseek_test
sync_test
chroot_test
#stat_test
#chmod_test
#chown_test
Expand Down
Loading