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
4 changes: 4 additions & 0 deletions guests/evil/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ impl Evil {
root: Box::new(root::not_tar::root),
udfs: Box::new(common::udfs_empty),
},
"root::path_long" => Self {
root: Box::new(root::path_long::root),
udfs: Box::new(common::udfs_empty),
},
"root::unsupported_entry" => Self {
root: Box::new(root::unsupported_entry::root),
udfs: Box::new(common::udfs_empty),
Expand Down
1 change: 1 addition & 0 deletions guests/evil/src/root/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
pub(crate) mod invalid_entry;
pub(crate) mod many_files;
pub(crate) mod not_tar;
pub(crate) mod path_long;
pub(crate) mod unsupported_entry;
19 changes: 19 additions & 0 deletions guests/evil/src/root/path_long.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Evil payloads that creates a file with a long path.

/// Return root file system.
#[expect(clippy::unnecessary_wraps, reason = "public API through export! macro")]
pub(crate) fn root() -> Option<Vec<u8>> {
let mut ar = tar::Builder::new(Vec::new());

let limit: usize = std::env::var("limit").unwrap().parse().unwrap();

let mut header = tar::Header::new_gnu();
header
.set_path(std::iter::repeat_n('x', limit + 1).collect::<String>())
.unwrap();
header.set_size(0);
header.set_cksum();
ar.append(&header, b"".as_slice()).unwrap();

Some(ar.into_inner().unwrap())
}
49 changes: 49 additions & 0 deletions host/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Helper for simpler error handling.
use datafusion_common::DataFusionError;
use wasmtime_wasi::p2::FsError;

/// Extension for [`wasmtime::Error`].
pub(crate) trait WasmToDataFusionErrorExt {
Expand Down Expand Up @@ -74,3 +75,51 @@ where
self.map_err(|e| e.into().context(description))
}
}

/// Failed allocation error.
#[derive(Debug, Clone)]
#[expect(missing_copy_implementations, reason = "allow later extensions")]
pub struct LimitExceeded {
/// Name of the allocation type/resource.
pub(crate) name: &'static str,

/// Allocation limit.
pub(crate) limit: u64,

/// Current allocation size.
pub(crate) current: u64,

/// Requested/additional allocation.
pub(crate) requested: u64,
}

impl std::fmt::Display for LimitExceeded {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
name,
limit,
current,
requested,
} = self;

write!(
f,
"{name} limit reached: limit<={limit} current=={current} requested+={requested}"
)
}
}

impl std::error::Error for LimitExceeded {}

impl From<LimitExceeded> for std::io::Error {
fn from(e: LimitExceeded) -> Self {
Self::new(std::io::ErrorKind::QuotaExceeded, e.to_string())
}
}

impl From<LimitExceeded> for FsError {
fn from(e: LimitExceeded) -> Self {
let e: std::io::Error = e.into();
e.into()
}
}
6 changes: 2 additions & 4 deletions host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,13 @@ use crate::{
#[cfg(test)]
use datafusion_udf_wasm_bundle as _;
#[cfg(test)]
use insta as _;
#[cfg(test)]
use regex as _;
#[cfg(test)]
use wiremock as _;

mod bindings;
mod conversion;
mod error;
pub mod error;
pub mod http;
mod linker;
mod tokio_helpers;
Expand Down Expand Up @@ -449,7 +447,7 @@ impl WasmScalarUdf {
let component = component_res.context("create WASM component", None)?;

// Create in-memory VFS
let vfs_state = VfsState::new(&permissions.vfs);
let vfs_state = VfsState::new(permissions.vfs.clone());

// set up WASI p2 context
let stderr = MemoryOutputPipe::new(1024);
Expand Down
37 changes: 37 additions & 0 deletions host/src/vfs/limits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Limit configuration.

/// Limits for virtual filesystems.
///
/// # Depth
/// Note that we do NOT per se limit the depth of the file system, since it is virtually not different from limiting
/// [the number of inodes](Self::inodes). Expensive path traversal is further limited by
/// [`max_path_length`](Self::max_path_length).
#[derive(Debug, Clone)]
#[expect(missing_copy_implementations, reason = "allow later extensions")]
pub struct VfsLimits {
/// Maximum number of inodes.
pub inodes: u64,

/// Maximum number of bytes in size.
pub bytes: u64,

/// Maximum path length, in bytes.
pub max_path_length: u64,

/// Maximum path segment size, in bytes.
///
/// Keep this to a rather small size to prevent super-linear complexity due to string hashing.
pub max_path_segment_size: u64,
}

impl Default for VfsLimits {
fn default() -> Self {
Self {
inodes: 10_000,
// 100MB
bytes: 100 * 1024 * 1024,
max_path_length: 255,
max_path_segment_size: 50,
}
}
}
Loading