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
370 changes: 358 additions & 12 deletions crates/execution/assets/runners/wasm-runner.mjs

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions crates/execution/src/node_import_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3539,6 +3539,13 @@ function createRpcBackedFsSync(fromGuestDir = '/') {
}
return createGuestFsStats(callSync('fs.fstatSync', [normalizedFd]));
},
ftruncateSync: (fd, len) => {
const normalizedFd = normalizeFsFd(fd);
if (isStdioFd(normalizedFd)) {
return hostFs.ftruncateSync(normalizedFd, len);
}
return callSync('fs.ftruncateSync', [normalizedFd, normalizeFsInteger(len ?? 0, 'length')]);
},
linkSync: (existingPath, newPath) =>
callSync('fs.linkSync', [
resolveGuestFsPath(existingPath, fromGuestDir),
Expand Down Expand Up @@ -3616,6 +3623,11 @@ function createRpcBackedFsSync(fromGuestDir = '/') {
resolveGuestFsPath(linkPath, fromGuestDir),
type,
]),
truncateSync: (target, len) =>
callSync('fs.truncateSync', [
resolveGuestFsPath(target, fromGuestDir),
normalizeFsInteger(len ?? 0, 'length'),
]),
unlinkSync: (target) =>
callSync('fs.unlinkSync', [resolveGuestFsPath(target, fromGuestDir)]),
utimesSync: (target, atime, mtime) =>
Expand Down
101 changes: 94 additions & 7 deletions crates/sidecar/src/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,29 @@ fn kernel_path_error(
other => other,
}
}

fn filesystem_access_denied(path: &str, mode: u32) -> SidecarError {
SidecarError::Execution(format!(
"EACCES: filesystem access denied for {path} with mode {mode:o}"
))
}

fn filesystem_access_mode_is_allowed(file_mode: u32, requested: u32) -> bool {
const ACCESS_MASK: u32 = libc::R_OK as u32 | libc::W_OK as u32 | libc::X_OK as u32;
if requested & ACCESS_MASK == 0 {
return true;
}
if requested & libc::R_OK as u32 != 0 && file_mode & 0o444 == 0 {
return false;
}
if requested & libc::W_OK as u32 != 0 && file_mode & 0o222 == 0 {
return false;
}
if requested & libc::X_OK as u32 != 0 && file_mode & 0o111 == 0 {
return false;
}
true
}
const PYTHON_PYODIDE_CACHE_GUEST_ROOT: &str = "/__agentos_pyodide_cache";
const UTIME_NOW_NSEC: i64 = libc::UTIME_NOW;
const UTIME_OMIT_NSEC: i64 = libc::UTIME_OMIT;
Expand Down Expand Up @@ -1177,6 +1200,7 @@ pub(crate) fn service_javascript_fs_sync_rpc(
return open_mapped_host_fd(
process,
host_path,
Some(path.to_string()),
opened.handle.proc_path(),
flags,
)
Expand Down Expand Up @@ -1389,16 +1413,36 @@ pub(crate) fn service_javascript_fs_sync_rpc(
)?
.unwrap_or(0);
if let Some(mapped) = process.mapped_host_fd_mut(fd) {
return mapped
let guest_path = mapped.guest_path.clone();
let host_path = mapped.path.clone();
let metadata = mapped
.file
.set_len(length)
.map(|()| Value::Null)
.map_err(|error| {
SidecarError::Io(format!(
"failed to truncate mapped guest fd {fd} -> {}: {error}",
mapped.path.display()
))
});
})
.and_then(|()| {
mapped.file.metadata().map_err(|error| {
SidecarError::Io(format!(
"failed to stat truncated mapped guest fd {fd} -> {}: {error}",
mapped.path.display()
))
})
})?;
if let Some(guest_path) = guest_path {
mirror_mapped_host_file_to_kernel(
kernel,
process,
kernel_pid,
&guest_path,
&host_path,
&metadata,
)?;
}
return Ok(Value::Null);
}
let fd_stat = kernel
.fd_stat(EXECUTION_DRIVER_NAME, kernel_pid, fd)
Expand Down Expand Up @@ -1597,6 +1641,15 @@ pub(crate) fn service_javascript_fs_sync_rpc(
let path =
javascript_sync_rpc_path_arg(process, &request.args, 0, "filesystem access path")?;
let path = path.as_str();
let mode =
javascript_sync_rpc_arg_u32_optional(&request.args, 1, "filesystem access mode")?
.unwrap_or(0);
let valid_mask = libc::R_OK as u32 | libc::W_OK as u32 | libc::X_OK as u32;
if mode & !valid_mask != 0 {
return Err(SidecarError::Execution(format!(
"EINVAL: invalid filesystem access mode {mode:o}"
)));
}
if let Some(mapped_host) = mapped_runtime_host_path_for_read(process, path) {
materialize_mapped_host_path_from_kernel(kernel, kernel_pid, path, &mapped_host)?;
let opened = open_mapped_runtime_beneath(
Expand All @@ -1605,19 +1658,25 @@ pub(crate) fn service_javascript_fs_sync_rpc(
O_PATH_ANCHOR,
Mode::empty(),
)?;
fs::metadata(opened.handle.proc_path()).map_err(|error| {
let metadata = fs::metadata(opened.handle.proc_path()).map_err(|error| {
SidecarError::Io(format!(
"failed to access mapped guest path {} -> {}: {error}",
path,
opened.host_path.display()
))
})?;
if !filesystem_access_mode_is_allowed(metadata.permissions().mode(), mode) {
return Err(filesystem_access_denied(path, mode));
}
return Ok(Value::Null);
}
kernel
let stat = kernel
.stat_for_process(EXECUTION_DRIVER_NAME, kernel_pid, path)
.map(|_| Value::Null)
.map_err(kernel_error)
.map_err(kernel_error)?;
if !filesystem_access_mode_is_allowed(stat.mode, mode) {
return Err(filesystem_access_denied(path, mode));
}
Ok(Value::Null)
}
"fs.copyFileSync" | "fs.promises.copyFile" => {
let source = javascript_sync_rpc_path_arg(
Expand Down Expand Up @@ -2228,6 +2287,32 @@ fn mirror_kernel_path_to_process_shadow(
write_process_shadow_file(&shadow_path, guest_path, &bytes)
}

fn mirror_mapped_host_file_to_kernel(
kernel: &mut SidecarKernel,
process: &ActiveProcess,
kernel_pid: u32,
guest_path: &str,
host_path: &Path,
metadata: &fs::Metadata,
) -> Result<(), SidecarError> {
let bytes = fs::read(host_path).map_err(|error| {
SidecarError::Io(format!(
"failed to read mapped host file {}: {error}",
host_path.display()
))
})?;
kernel
.write_file_for_process(
EXECUTION_DRIVER_NAME,
kernel_pid,
guest_path,
bytes,
Some(metadata.permissions().mode() & 0o7777),
)
.map_err(|error| kernel_path_error("fs.ftruncate", guest_path, error))?;
mirror_kernel_path_to_process_shadow(kernel, process, kernel_pid, guest_path)
}

fn write_process_shadow_file(
shadow_path: &Path,
guest_path: &str,
Expand Down Expand Up @@ -3349,6 +3434,7 @@ fn materialize_mapped_host_path_from_kernel(
fn open_mapped_host_fd(
process: &mut ActiveProcess,
host_path: PathBuf,
guest_path: Option<String>,
proc_path: PathBuf,
flags: u32,
) -> Result<Value, SidecarError> {
Expand Down Expand Up @@ -3386,6 +3472,7 @@ fn open_mapped_host_fd(
let fd = process.allocate_mapped_host_fd(crate::state::ActiveMappedHostFd {
file,
path: host_path,
guest_path,
});
Ok(json!(fd))
}
Expand Down
1 change: 1 addition & 0 deletions crates/sidecar/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ pub(crate) struct ActiveProcess {
pub(crate) struct ActiveMappedHostFd {
pub(crate) file: File,
pub(crate) path: PathBuf,
pub(crate) guest_path: Option<String>,
}

pub(crate) struct ActiveCipherSession {
Expand Down
2 changes: 1 addition & 1 deletion registry/native/c/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ COMMANDS_DIR ?= ../target/wasm32-wasip1/release/commands
COMMANDS := zip unzip envsubst sqlite3 curl wget duckdb http_get vim

# Programs requiring patched sysroot (Tier 2+ custom host imports)
PATCHED_PROGRAMS := isatty_test getpid_test getppid_test getppid_verify userinfo pipe_test dup_test spawn_child spawn_exit_code pipeline kill_child waitpid_return waitpid_edge syscall_coverage getpwuid_test signal_tests sigaction_self sigaction_behavior delayed_tcp_echo delayed_kill pipe_edge tcp_accept_spawn tcp_echo tcp_server http_server udp_echo unix_socket signal_handler http_get dns_lookup sqlite3_cli curl wget
PATCHED_PROGRAMS := isatty_test getpid_test getppid_test getppid_verify userinfo pipe_test dup_test spawn_child spawn_exit_code pipeline kill_child waitpid_return waitpid_edge syscall_coverage getpwuid_test signal_tests sigaction_self sigaction_behavior delayed_tcp_echo delayed_kill pipe_edge tcp_accept_spawn tcp_echo tcp_server http_server udp_echo unix_socket signal_handler http_get dns_lookup sqlite3_cli curl wget fs_probe

# Discover all .c source files in programs/
ALL_SOURCES := $(wildcard programs/*.c)
Expand Down
Loading
Loading