8000 Add host_env feature for sandbox isolation · RustPython/RustPython@3fcc809 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3fcc809

Browse files
committed
Add host_env feature for sandbox isolation
Introduce a `host_env` feature flag that gates all host environment access (filesystem, network, signals, processes). When disabled, the VM operates in sandbox mode: - _io module always available; FileIO gated by host_env - SandboxStdio provides lightweight stdin/stdout/stderr via Rust std::io - BytesIO/StringIO/BufferedIO/TextIOWrapper work without host_env - open() returns UnsupportedOperation in sandbox - stdlib modules (os, socket, signal, etc.) gated by host_env - CI checks both host_env ON and OFF builds
1 parent d54cf8f commit 3fcc809

File tree

19 files changed

+696
-407
lines changed

19 files changed

+696
-407
lines changed

.cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
"pytype",
114114
"reducelib",
115115
"richcompare",
116+
"rustix",
116117
"RustPython",
117118
"significand",
118119
"struc",

.github/workflows/ci.yaml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ concurrency:
1616
cancel-in-progress: true
1717

1818
env:
19-
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls
20-
CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite
19+
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls,host_env
20+
CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,host_env
2121
# Crates excluded from workspace builds:
2222
# - rustpython_wasm: requires wasm target
2323
# - rustpython-compiler-source: deprecated
@@ -153,6 +153,19 @@ jobs:
153153
- name: check compilation without threading
154154
run: cargo check ${{ env.CARGO_ARGS }}
155155

156+
- name: check compilation without host_env (sandbox mode)
157+
run: |
158+
cargo check -p rustpython-vm --no-default-features --features compiler
159+
cargo check -p rustpython-stdlib --no-default-features --features compiler
160+
cargo build --no-default-features --features stdlib,importlib,stdio,encodings,freeze-stdlib
161+
if: runner.os == 'Linux'
162+
163+
- name: sandbox smoke test
164+
run: |
165+
target/debug/rustpython extra_tests/snippets/sandbox_smoke.py
166+
target/debug/rustpython extra_tests/snippets/stdlib_re.py
167+
if: runner.os == 'Linux'
168+
156169
- name: Test openssl build
157170
run: cargo build --no-default-features --features ssl-openssl
158171
if: runner.os == 'Linux'

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ repository.workspace = true
1010
license.workspace = true
1111

1212
[features]
13-
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls"]
13+
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls", "host_env"]
14+
host_env = ["rustpython-vm/host_env", "rustpython-stdlib?/host_env"]
1415
importlib = ["rustpython-vm/importlib"]
1516
encodings = ["rustpython-vm/encodings"]
1617
stdio = ["rustpython-vm/stdio"]

crates/stdlib/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ license.workspace = true
1111
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1212

1313
[features]
14-
default = ["compiler"]
14+
default = ["compiler", "host_env"]
15+
host_env = ["rustpython-vm/host_env"]
1516
compiler = ["rustpython-vm/compiler"]
1617
threading = ["rustpython-common/threading", "rustpython-vm/threading"]
1718
sqlite = ["dep:libsqlite3-sys"]

crates/stdlib/src/lib.rs

Lines changed: 105 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ mod sha512;
3434

3535
mod json;
3636

37-
#[cfg(not(any(target_os = "ios", target_arch = "wasm32")))]
37+
#[cfg(all(
38+
feature = "host_env",
39+
not(any(target_os = "ios", target_arch = "wasm32"))
40+
))]
3841
mod locale;
3942

4043
mod _opcode;
4144
mod math;
42-
#[cfg(any(unix, windows))]
45+
#[cfg(all(feature = "host_env", any(unix, windows)))]
4346
mod mmap;
4447
mod pyexpat;
4548
mod pystruct;
@@ -48,57 +51,83 @@ mod statistics;
4851
mod suggestions;
4952
// TODO: maybe make this an extension module, if we ever get those
5053
// mod re;
51-
#[cfg(not(target_arch = "wasm32"))]
54+
#[cfg(all(feature = "host_env", not(target_arch = "wasm32")))]
5255
pub mod socket;
53-
#[cfg(all(unix, not(target_os = "redox")))]
56+
#[cfg(all(feature = "host_env", unix, not(target_os = "redox")))]
5457
mod syslog;
5558
mod unicodedata;
5659

60+
#[cfg(feature = "host_env")]
5761
mod faulthandler;
58-
#[cfg(any(unix, target_os = "wasi"))]
62+
#[cfg(all(feature = "host_env", any(unix, target_os = "wasi")))]
5963
mod fcntl;
60-
#[cfg(not(target_arch = "wasm32"))]
64+
#[cfg(all(feature = "host_env", not(target_arch = "wasm32")))]
6165
mod multiprocessing;
62-
#[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))]
66+
#[cfg(all(
67+
feature = "host_env",
68+
unix,
69+
not(target_os = "redox"),
70+
not(target_os = "android")
71+
))]
6372
mod posixshmem;
64-
#[cfg(unix)]
73+
#[cfg(all(feature = "host_env", unix))]
6574
mod posixsubprocess;
6675
// libc is missing constants on redox
6776
#[cfg(all(
6877
feature = "sqlite",
6978
not(any(target_os = "android", target_arch = "wasm32"))
7079
))]
7180
mod _sqlite3;
72-
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
81+
#[cfg(all(
82+
feature = "host_env",
83+
unix,
84+
not(any(target_os = "android", target_os = "redox"))
85+
))]
7386
mod grp;
74-
#[cfg(windows)]
87+
#[cfg(all(feature = "host_env", windows))]
7588
mod overlapped;
76-
#[cfg(all(unix, not(target_os = "redox")))]
89+
#[cfg(all(feature = "host_env", unix, not(target_os = "redox")))]
7790
mod resource;
78-
#[cfg(target_os = "macos")]
91+
#[cfg(all(feature = "host_env", target_os = "macos"))]
7992
mod scproxy;
80-
#[cfg(any(unix, windows, target_os = "wasi"))]
93+
#[cfg(all(feature = "host_env", any(unix, windows, target_os = "wasi")))]
8194
mod select;
8295

83-
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-openssl"))]
96+
#[cfg(all(
97+
feature = "host_env",
98+
not(target_arch = "wasm32"),
99+
feature = "ssl-openssl"
100+
))]
84101
mod openssl;
85-
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-rustls"))]
102+
#[cfg(all(
103+
feature = "host_env",
104+
not(target_arch = "wasm32"),
105+
feature = "ssl-rustls"
106+
))]
86107
mod ssl;
87108
#[cfg(all(feature = "ssl-openssl", feature = "ssl-rustls"))]
88109
compile_error!("features \"ssl-openssl\" and \"ssl-rustls\" are mutually exclusive");
89110

90-
#[cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))]
111+
#[cfg(all(
112+
feature = "host_env",
113+
unix,
114+
not(target_os = "redox"),
115+
not(target_os = "ios")
116+
))]
91117
mod termios;
92-
#[cfg(not(any(
93-
target_os = "android",
94-
target_os = "ios",
95-
target_os = "windows",
96-
target_arch = "wasm32",
97-
target_os = "redox",
98-
)))]
118+
#[cfg(all(
119+
feature = "host_env",
120+
not(any(
121+
target_os = "android",
122+
target_os = "ios",
123+
target_os = "windows",
124+
target_arch = "wasm32",
125+
target_os = "redox",
126+
))
127+
))]
99128
mod uuid;
100129

101-
#[cfg(feature = "tkinter")]
130+
#[cfg(all(feature = "host_env", feature = "tkinter"))]
102131
mod tkinter;
103132

104133
use rustpython_common as common;
@@ -122,69 +151,97 @@ pub fn stdlib_module_defs(ctx: &Context) -> Vec<&'static builtins::PyModuleDef>
122151
cmath::module_def(ctx),
123152
contextvars::module_def(ctx),
124153
csv::module_def(ctx),
154+
#[cfg(feature = "host_env")]
125155
faulthandler::module_def(ctx),
126-
#[cfg(any(unix, target_os = "wasi"))]
156+
#[cfg(all(feature = "host_env", any(unix, target_os = "wasi")))]
127157
fcntl::module_def(ctx),
128-
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
158+
#[cfg(all(
159+
feature = "host_env",
160+
unix,
161+
not(any(target_os = "android", target_os = "redox"))
162+
))]
129163
grp::module_def(ctx),
130164
hashlib::module_def(ctx),
131165
json::module_def(ctx),
132-
#[cfg(not(any(target_os = "ios", target_arch = "wasm32")))]
166+
#[cfg(all(
167+
feature = "host_env",
168+
not(any(target_os = "ios", target_arch = "wasm32"))
169+
))]
133170
locale::module_def(ctx),
134171
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
135172
lzma::module_def(ctx),
136173
math::module_def(ctx),
137174
md5::module_def(ctx),
138-
#[cfg(any(unix, windows))]
175+
#[cfg(all(feature = "host_env", any(unix, windows)))]
139176
mmap::module_def(ctx),
140-
#[cfg(not(target_arch = "wasm32"))]
177+
#[cfg(all(feature = "host_env", not(target_arch = "wasm32")))]
141178
multiprocessing::module_def(ctx),
142-
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-openssl"))]
179+
#[cfg(all(
180+
feature = "host_env",
181+
not(target_arch = "wasm32"),
182+
feature = "ssl-openssl"
183+
))]
143184
openssl::module_def(ctx),
144-
#[cfg(windows)]
185+
#[cfg(all(feature = "host_env", windows))]
145186
overlapped::module_def(ctx),
146-
#[cfg(unix)]
187+
#[cfg(all(feature = "host_env", unix))]
147188
posixsubprocess::module_def(ctx),
148-
#[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))]
189+
#[cfg(all(
190+
feature = "host_env",
191+
unix,
192+
not(target_os = "redox"),
193+
not(target_os = "android")
194+
))]
149195
posixshmem::module_def(ctx),
150196
pyexpat::module_def(ctx),
151197
pystruct::module_def(ctx),
152198
random::module_def(ctx),
153-
#[cfg(all(unix, not(target_os = "redox")))]
199+
#[cfg(all(feature = "host_env", unix, not(target_os = "redox")))]
154200
resource::module_def(ctx),
155-
#[cfg(target_os = "macos")]
201+
#[cfg(all(feature = "host_env", target_os = "macos"))]
156202
scproxy::module_def(ctx),
157-
#[cfg(any(unix, windows, target_os = "wasi"))]
203+
#[cfg(all(feature = "host_env", any(unix, windows, target_os = "wasi")))]
158204
select::module_def(ctx),
159205
sha1::module_def(ctx),
160206
sha256::module_def(ctx),
161207
sha3::module_def(ctx),
162208
sha512::module_def(ctx),
163-
#[cfg(not(target_arch = "wasm32"))]
209+
#[cfg(all(feature = "host_env", not(target_arch = "wasm32")))]
164210
socket::module_def(ctx),
165211
#[cfg(all(
166212
feature = "sqlite",
167213
not(any(target_os = "android", target_arch = "wasm32"))
168214
))]
169215
_sqlite3::module_def(ctx),
170-
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-rustls"))]
216+
#[cfg(all(
217+
feature = "host_env",
218+
not(target_arch = "wasm32"),
219+
feature = "ssl-rustls"
220+
))]
171221
ssl::module_def(ctx),
172222
statistics::module_def(ctx),
173223
suggestions::module_def(ctx),
174-
#[cfg(all(unix, not(target_os = "redox")))]
224+
#[cfg(all(feature = "host_env", unix, not(target_os = "redox")))]
175225
syslog::module_def(ctx),
176-
#[cfg(all(unix, not(any(target_os = "ios", target_os = "redox"))))]
226+
#[cfg(all(
227+
feature = "host_env",
228+
unix,
229+
not(any(target_os = "ios", target_os = "redox"))
230+
))]
177231
termios::module_def(ctx),
178-
#[cfg(feature = "tkinter")]
232+
#[cfg(all(feature = "host_env", feature = "tkinter"))]
179233
tkinter::module_def(ctx),
180234
unicodedata::module_def(ctx),
181-
#[cfg(not(any(
182-
target_os = "android",
183-
target_os = "ios",
184-
target_os = "windows",
185-
target_arch = "wasm32",
186-
target_os = "redox"
187-
)))]
235+
#[cfg(all(
236+
feature = "host_env",
237+
not(any(
238+
target_os = "android",
239+
target_os = "ios",
240+
target_os = "windows",
241+
target_arch = "wasm32",
242+
target_os = "redox"
243+
))
244+
))]
188245
uuid::module_def(ctx),
189246
zlib::module_def(ctx),
190247
]

crates/vm/Cargo.toml

Lines changed: 2 additions & 1 deletion

0 commit comments

Comments
 (0)
0
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ repository.workspace = true
1010
license.workspace = true
1111

1212
[features]
13-
default = ["compiler", "wasmbind", "stdio", "gc"]
13+
default = ["compiler", "wasmbind", "gc", "host_env", "stdio"]
14+
host_env = []
1415
stdio = []
1516
importlib = []
1617
encodings = ["importlib"]