Skip to content

Commit a2a40c5

Browse files
committed
fix(native-sidecar): enforce maxFilesystemBytes on mapped host fd writes
1 parent a8379bc commit a2a40c5

1 file changed

Lines changed: 42 additions & 2 deletions

File tree

crates/native-sidecar/src/filesystem.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,9 +1598,11 @@ pub(crate) fn service_javascript_fs_sync_rpc(
15981598
)?;
15991599
record_fs_sync_subphase(request.method.as_str(), "parse", phase_start);
16001600
let phase_start = Instant::now();
1601+
// Read the byte budget before taking the mutable borrow on `process`.
1602+
let max_filesystem_bytes = kernel.resource_limits().max_filesystem_bytes;
16011603
if let Some(mapped) = process.mapped_host_fd_mut(fd) {
16021604
record_fs_sync_subphase(request.method.as_str(), "mapped_fd_match", phase_start);
1603-
return write_mapped_host_fd(mapped, fd, &contents, position);
1605+
return write_mapped_host_fd(mapped, fd, &contents, position, max_filesystem_bytes);
16041606
}
16051607
record_fs_sync_subphase(request.method.as_str(), "mapped_fd_none", phase_start);
16061608
let phase_start = Instant::now();
@@ -1650,11 +1652,14 @@ pub(crate) fn service_javascript_fs_sync_rpc(
16501652
record_fs_sync_subphase(request.method.as_str(), "parse", phase_start);
16511653

16521654
let mut total_written = 0usize;
1655+
// Read the byte budget before taking the mutable borrow on `process`.
1656+
let max_filesystem_bytes = kernel.resource_limits().max_filesystem_bytes;
16531657
if let Some(mapped) = process.mapped_host_fd_mut(fd) {
16541658
record_fs_sync_subphase(request.method.as_str(), "mapped_fd_match", phase_start);
16551659
let mut next_position = position;
16561660
for buffer in buffers {
1657-
let written = write_all_mapped_host_fd(mapped, fd, buffer, next_position)?;
1661+
let written =
1662+
write_all_mapped_host_fd(mapped, fd, buffer, next_position, max_filesystem_bytes)?;
16581663
total_written = total_written.saturating_add(written);
16591664
if let Some(position) = &mut next_position {
16601665
*position = position.saturating_add(written as u64);
@@ -4360,12 +4365,45 @@ fn read_mapped_host_fd(
43604365
Ok(javascript_sync_rpc_bytes_value(&bytes))
43614366
}
43624367

4368+
/// Enforce the VM's filesystem byte budget on a host-mapped write.
4369+
///
4370+
/// Writes through a mapped host fd go straight to the host file and never reach
4371+
/// the kernel VFS, so `check_filesystem_usage` never sees them. Without this a
4372+
/// guest writing to a host-backed path (`/tmp`, `/workspace`) could exceed
4373+
/// `limits.resources.maxFilesystemBytes` without bound and fill the host disk
4374+
/// (LT-011 / LT-017). Only runs when a limit is configured, so the common
4375+
/// unlimited case keeps the original fast path.
4376+
fn check_mapped_host_fd_write_budget(
4377+
mapped: &crate::state::ActiveMappedHostFd,
4378+
fd: u32,
4379+
len: usize,
4380+
position: Option<u64>,
4381+
max_filesystem_bytes: Option<u64>,
4382+
) -> Result<(), SidecarError> {
4383+
let Some(limit) = max_filesystem_bytes else {
4384+
return Ok(());
4385+
};
4386+
let current = mapped.file.metadata().map(|meta| meta.len()).unwrap_or(0);
4387+
let start = position.unwrap_or(current);
4388+
let resulting = start.saturating_add(len as u64).max(current);
4389+
if resulting > limit {
4390+
return Err(SidecarError::Execution(format!(
4391+
"ENOSPC: writing {len} byte(s) to mapped guest fd {fd} would reach {resulting} bytes, \
4392+
exceeding the VM filesystem limit of {limit} (limits.resources.maxFilesystemBytes); \
4393+
raise the limit to store more data"
4394+
)));
4395+
}
4396+
Ok(())
4397+
}
4398+
43634399
fn write_mapped_host_fd(
43644400
mapped: &mut crate::state::ActiveMappedHostFd,
43654401
fd: u32,
43664402
contents: &[u8],
43674403
position: Option<u64>,
4404+
max_filesystem_bytes: Option<u64>,
43684405
) -> Result<Value, SidecarError> {
4406+
check_mapped_host_fd_write_budget(mapped, fd, contents.len(), position, max_filesystem_bytes)?;
43694407
let written = match position {
43704408
Some(offset) => mapped.file.write_at(contents, offset),
43714409
None => mapped.file.write(contents),
@@ -4384,7 +4422,9 @@ fn write_all_mapped_host_fd(
43844422
fd: u32,
43854423
contents: &[u8],
43864424
position: Option<u64>,
4425+
max_filesystem_bytes: Option<u64>,
43874426
) -> Result<usize, SidecarError> {
4427+
check_mapped_host_fd_write_budget(mapped, fd, contents.len(), position, max_filesystem_bytes)?;
43884428
let mut total = 0usize;
43894429
while total < contents.len() {
43904430
let write_position = position.map(|offset| offset.saturating_add(total as u64));

0 commit comments

Comments
 (0)