Skip to content

Commit 8fe76a6

Browse files
committed
feat(fs): add filesystem isolation support
Add LLRT_FS_ALLOW and LLRT_FS_DENY environment variables to control filesystem access, similar to existing network isolation (LLRT_NET_ALLOW/DENY). - LLRT_FS_ALLOW: whitespace-separated list of allowed paths/patterns - LLRT_FS_DENY: whitespace-separated list of denied paths/patterns Supports exact paths (/tmp/file.txt), directory prefixes (/tmp/), and glob patterns (/tmp/*.txt). Closes #686
1 parent 2a48ea1 commit 8fe76a6

File tree

16 files changed

+313
-5
lines changed

16 files changed

+313
-5
lines changed

llrt_core/src/environment.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
//network
55
pub const ENV_LLRT_NET_ALLOW: &str = "LLRT_NET_ALLOW";
66
pub const ENV_LLRT_NET_DENY: &str = "LLRT_NET_DENY";
7+
8+
//filesystem
9+
pub const ENV_LLRT_FS_ALLOW: &str = "LLRT_FS_ALLOW";
10+
pub const ENV_LLRT_FS_DENY: &str = "LLRT_FS_DENY";
711
pub const ENV_LLRT_NET_POOL_IDLE_TIMEOUT: &str = "LLRT_NET_POOL_IDLE_TIMEOUT";
812
pub const ENV_LLRT_HTTP_VERSION: &str = "LLRT_HTTP_VERSION";
913
pub const ENV_LLRT_TLS_VERSION: &str = "LLRT_TLS_VERSION";

llrt_core/src/security.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ use std::{env, result::Result as StdResult};
44

55
use hyper::{http::uri::InvalidUri, Uri};
66

7-
use crate::environment::{ENV_LLRT_NET_ALLOW, ENV_LLRT_NET_DENY};
8-
use crate::modules::{fetch, net};
7+
use crate::environment::{
8+
ENV_LLRT_FS_ALLOW, ENV_LLRT_FS_DENY, ENV_LLRT_NET_ALLOW, ENV_LLRT_NET_DENY,
9+
};
10+
use crate::modules::{fetch, fs, net};
911

1012
pub fn init() -> StdResult<(), Box<dyn std::error::Error + Send + Sync>> {
13+
// Network isolation
1114
if let Ok(env_value) = env::var(ENV_LLRT_NET_ALLOW) {
1215
let allow_list = build_access_list(env_value);
1316
fetch::set_allow_list(build_http_access_list(&allow_list)?);
@@ -20,6 +23,17 @@ pub fn init() -> StdResult<(), Box<dyn std::error::Error + Send + Sync>> {
2023
net::set_deny_list(deny_list);
2124
}
2225

26+
// Filesystem isolation
27+
if let Ok(env_value) = env::var(ENV_LLRT_FS_ALLOW) {
28+
let allow_list = build_fs_access_list(env_value);
29+
fs::security::set_allow_list(allow_list);
30+
}
31+
32+
if let Ok(env_value) = env::var(ENV_LLRT_FS_DENY) {
33+
let deny_list = build_fs_access_list(env_value);
34+
fs::security::set_deny_list(deny_list);
35+
}
36+
2337
Ok(())
2438
}
2539

@@ -47,3 +61,10 @@ fn build_access_list(env_value: String) -> Vec<String> {
4761
})
4862
.collect()
4963
}
64+
65+
fn build_fs_access_list(env_value: String) -> Vec<String> {
66+
env_value
67+
.split_whitespace()
68+
.map(|entry| entry.to_string())
69+
.collect()
70+
}

modules/llrt_fs/src/access.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ use llrt_utils::result::ResultExt;
66
use rquickjs::{prelude::Opt, Ctx, Exception, Result};
77
use tokio::fs;
88

9+
use crate::security::ensure_access;
10+
911
#[allow(dead_code, unused_imports)]
1012
use super::{CONSTANT_F_OK, CONSTANT_R_OK, CONSTANT_W_OK, CONSTANT_X_OK};
1113

1214
pub async fn access(ctx: Ctx<'_>, path: String, mode: Opt<u32>) -> Result<()> {
15+
ensure_access(&ctx, &path)?;
16+
1317
let metadata = fs::metadata(&path).await.or_throw_msg(
1418
&ctx,
1519
&["No such file or directory \"", &path, "\""].concat(),
@@ -19,6 +23,8 @@ pub async fn access(ctx: Ctx<'_>, path: String, mode: Opt<u32>) -> Result<()> {
1923
}
2024

2125
pub fn access_sync(ctx: Ctx<'_>, path: String, mode: Opt<u32>) -> Result<()> {
26+
ensure_access(&ctx, &path)?;
27+
2228
let metadata = std::fs::metadata(path.clone()).or_throw_msg(
2329
&ctx,
2430
&["No such file or directory \"", &path, "\""].concat(),

modules/llrt_fs/src/chmod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use rquickjs::{Ctx, Result};
44
#[cfg(unix)]
55
use std::os::unix::prelude::PermissionsExt;
66

7+
use crate::security::ensure_access;
8+
79
#[cfg(unix)]
810
pub(crate) fn chmod_error(path: &str) -> String {
911
["Can't set permissions of \"", path, "\""].concat()
@@ -41,9 +43,11 @@ pub(crate) fn set_mode_sync(ctx: Ctx<'_>, path: &str, mode: u32) -> Result<()> {
4143
}
4244

4345
pub async fn chmod(ctx: Ctx<'_>, path: String, mode: u32) -> Result<()> {
46+
ensure_access(&ctx, &path)?;
4447
set_mode(ctx, &path, mode).await
4548
}
4649

4750
pub fn chmod_sync(ctx: Ctx<'_>, path: String, mode: u32) -> Result<()> {
51+
ensure_access(&ctx, &path)?;
4852
set_mode_sync(ctx, &path, mode)
4953
}

modules/llrt_fs/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod read_dir;
99
mod read_file;
1010
mod rename;
1111
mod rm;
12+
pub mod security;
1213
mod stats;
1314
mod symlink;
1415
mod write_file;

modules/llrt_fs/src/mkdir.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33
use crate::chmod::{set_mode, set_mode_sync};
4+
use crate::security::ensure_access;
45

56
use llrt_path::resolve_path;
67
use llrt_utils::result::ResultExt;
@@ -11,6 +12,8 @@ use tokio::fs;
1112
pub async fn mkdir<'js>(ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>) -> Result<String> {
1213
let (recursive, mode, path) = get_params(&path, options)?;
1314

15+
ensure_access(&ctx, &path)?;
16+
1417
if recursive {
1518
fs::create_dir_all(&path).await
1619
} else {
@@ -26,6 +29,8 @@ pub async fn mkdir<'js>(ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>)
2629
pub fn mkdir_sync<'js>(ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>) -> Result<String> {
2730
let (recursive, mode, path) = get_params(&path, options)?;
2831

32+
ensure_access(&ctx, &path)?;
33+
2934
if recursive {
3035
std::fs::create_dir_all(&path)
3136
} else {
@@ -68,6 +73,9 @@ fn random_chars(len: usize) -> String {
6873

6974
pub async fn mkdtemp(ctx: Ctx<'_>, prefix: String) -> Result<String> {
7075
let path = [prefix.as_str(), random_chars(6).as_str()].join(",");
76+
77+
ensure_access(&ctx, &path)?;
78+
7179
fs::create_dir_all(&path)
7280
.await
7381
.or_throw_msg(&ctx, &["Can't create dir \"", &path, "\""].concat())?;
@@ -76,6 +84,9 @@ pub async fn mkdtemp(ctx: Ctx<'_>, prefix: String) -> Result<String> {
7684

7785
pub fn mkdtemp_sync(ctx: Ctx<'_>, prefix: String) -> Result<String> {
7886
let path = [prefix.as_str(), random_chars(6).as_str()].join(",");
87+
88+
ensure_access(&ctx, &path)?;
89+
7990
std::fs::create_dir_all(&path)
8091
.or_throw_msg(&ctx, &["Can't create dir \"", &path, "\""].concat())?;
8192
Ok(path)

modules/llrt_fs/src/open.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ use rquickjs::{function::Opt, Ctx, Exception, Result};
77
use tokio::fs::OpenOptions;
88

99
use super::file_handle::FileHandle;
10+
use crate::security::ensure_access;
1011

1112
pub async fn open(
1213
ctx: Ctx<'_>,
1314
path: String,
1415
flags: Opt<String>,
1516
mode: Opt<u32>,
1617
) -> Result<FileHandle> {
18+
ensure_access(&ctx, &path)?;
19+
1720
let mut options = OpenOptions::new();
1821
match flags.0.as_deref().unwrap_or("r") {
1922
// We are not supporting the sync modes

modules/llrt_fs/src/read_dir.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use rquickjs::{
1010
atom::PredefinedAtom, prelude::Opt, Array, Class, Ctx, IntoJs, Object, Result, Value,
1111
};
1212

13+
use crate::security::ensure_access;
14+
1315
#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)]
1416
#[rquickjs::class]
1517
pub struct Dirent {
@@ -105,7 +107,9 @@ impl<'js> IntoJs<'js> for ReadDir {
105107
}
106108
}
107109

108-
pub async fn read_dir(mut path: String, options: Opt<Object<'_>>) -> Result<ReadDir> {
110+
pub async fn read_dir(ctx: Ctx<'_>, mut path: String, options: Opt<Object<'_>>) -> Result<ReadDir> {
111+
ensure_access(&ctx, &path)?;
112+
109113
let (with_file_types, skip_root_pos, mut directory_walker) =
110114
process_options_and_create_directory_walker(&mut path, options);
111115

@@ -126,7 +130,9 @@ pub async fn read_dir(mut path: String, options: Opt<Object<'_>>) -> Result<Read
126130
Ok(ReadDir { items, root: path })
127131
}
128132

129-
pub fn read_dir_sync(mut path: String, options: Opt<Object<'_>>) -> Result<ReadDir> {
133+
pub fn read_dir_sync(ctx: Ctx<'_>, mut path: String, options: Opt<Object<'_>>) -> Result<ReadDir> {
134+
ensure_access(&ctx, &path)?;
135+
130136
let (with_file_types, skip_root_pos, mut directory_walker) =
131137
process_options_and_create_directory_walker(&mut path, options);
132138

modules/llrt_fs/src/read_file.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ use llrt_utils::{object::ObjectExt, result::ResultExt};
66
use rquickjs::{function::Opt, Ctx, Error, FromJs, IntoJs, Result, Value};
77
use tokio::fs;
88

9+
use crate::security::ensure_access;
10+
911
pub async fn read_file(
1012
ctx: Ctx<'_>,
1113
path: String,
1214
options: Opt<Either<String, ReadFileOptions>>,
1315
) -> Result<Value<'_>> {
16+
ensure_access(&ctx, &path)?;
17+
1418
let bytes = fs::read(&path)
1519
.await
1620
.or_throw_msg(&ctx, &["Can't read \"", &path, "\""].concat())?;
@@ -23,6 +27,8 @@ pub fn read_file_sync(
2327
path: String,
2428
options: Opt<Either<String, ReadFileOptions>>,
2529
) -> Result<Value<'_>> {
30+
ensure_access(&ctx, &path)?;
31+
2632
let bytes =
2733
std::fs::read(&path).or_throw_msg(&ctx, &["Can't read \"", &path, "\""].concat())?;
2834

modules/llrt_fs/src/rename.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use llrt_utils::result::ResultExt;
22
use rquickjs::{Ctx, Result};
33

4+
use crate::security::ensure_access;
5+
46
pub(crate) fn rename_error(from: &str, to: &str) -> String {
57
[
68
"Can't rename file/folder from \"",
@@ -13,13 +15,19 @@ pub(crate) fn rename_error(from: &str, to: &str) -> String {
1315
}
1416

1517
pub async fn rename(ctx: Ctx<'_>, old_path: String, new_path: String) -> Result<()> {
18+
ensure_access(&ctx, &old_path)?;
19+
ensure_access(&ctx, &new_path)?;
20+
1621
tokio::fs::rename(&old_path, &new_path)
1722
.await
1823
.or_throw_msg(&ctx, &rename_error(&old_path, &new_path))?;
1924
Ok(())
2025
}
2126

2227
pub fn rename_sync(ctx: Ctx<'_>, old_path: String, new_path: String) -> Result<()> {
28+
ensure_access(&ctx, &old_path)?;
29+
ensure_access(&ctx, &new_path)?;
30+
2331
std::fs::rename(&old_path, &new_path)
2432
.or_throw_msg(&ctx, &rename_error(&old_path, &new_path))?;
2533
Ok(())

0 commit comments

Comments
 (0)