Skip to content

Commit 922efdb

Browse files
authored
Add v8 sandbox (#1861)
1 parent 9e602b2 commit 922efdb

File tree

11 files changed

+206
-145
lines changed

11 files changed

+206
-145
lines changed

.gn

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,10 @@ default_args = {
3232

3333
v8_embedder_string = "-rusty"
3434

35-
v8_enable_sandbox = false
3635
v8_enable_javascript_promise_hooks = true
3736
v8_promise_internal_field_count = 1
3837
v8_use_external_startup_data = false
3938

40-
v8_enable_pointer_compression = false
41-
4239
v8_imminent_deprecation_warnings = false
4340

4441
# This flag speeds up the performance of fork/execve on Linux systems for

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ opt-level = 1
101101
default = ["use_custom_libcxx"]
102102
use_custom_libcxx = []
103103
v8_enable_pointer_compression = []
104+
v8_enable_sandbox = ["v8_enable_pointer_compression"]
104105
v8_enable_v8_checks = []
105106

106107
[dependencies]

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,3 +242,9 @@ for M1 build.
242242
$ V8_FROM_SOURCE=1 cargo build
243243
$ V8_FROM_SOURCE=1 cargo build --release
244244
```
245+
246+
## Experimental Features
247+
248+
rusty_v8 includes experimental support for certain feature(s) that may be useful in security focused contexts but are not as well tested and do not undergo any sort of CI related testing or prebuilt archives. Due to their experimental status, these features require either ``V8_FROM_SOURCE=1`` to be set or the use of a custom-built archive of v8.
249+
250+
- ``v8_enable_sandbox``: Enables v8 sandbox mode. The v8 sandbox enables improved safety while executing potentially malicious JavaScript code through the use of memory cages. Note that the v8 sandbox will allocate ~1TB of virtual memory (although this should not be an issue as many operating systems allow 128-256TB of virtual memory per process). Creating isolates with the sandbox enabled comes with API limitations and may have increased overhead. Note that enabling the V8 sandbox also implies pointer compression to be enabled as well.

build.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,14 +273,42 @@ fn build_v8(is_asan: bool) {
273273
"use_custom_libcxx={}",
274274
env::var("CARGO_FEATURE_USE_CUSTOM_LIBCXX").is_ok()
275275
));
276-
gn_args.push(format!(
277-
"v8_enable_pointer_compression={}",
278-
env::var("CARGO_FEATURE_V8_ENABLE_POINTER_COMPRESSION").is_ok()
279-
));
276+
277+
let extra_args = {
278+
if env::var("CARGO_FEATURE_V8_ENABLE_SANDBOX").is_ok() {
279+
vec![
280+
// Enable pointer compression (along with its dependencies)
281+
"v8_enable_sandbox=true",
282+
"v8_enable_external_code_space=true", // Needed for sandbox
283+
"v8_enable_pointer_compression=true",
284+
// Note that sandbox requires shared_ro_heap and verify_heap
285+
// to be true/default
286+
]
287+
} else {
288+
let mut opts = vec![
289+
// Disable sandbox
290+
"v8_enable_sandbox=false",
291+
];
292+
293+
if env::var("CARGO_FEATURE_V8_ENABLE_POINTER_COMPRESSION").is_ok() {
294+
opts.push("v8_enable_pointer_compression=true");
295+
} else {
296+
opts.push("v8_enable_pointer_compression=false");
297+
}
298+
299+
opts
300+
}
301+
};
302+
303+
for arg in extra_args {
304+
gn_args.push(arg.to_string());
305+
}
306+
280307
gn_args.push(format!(
281308
"v8_enable_v8_checks={}",
282309
env::var("CARGO_FEATURE_V8_ENABLE_V8_CHECKS").is_ok()
283310
));
311+
284312
// Fix GN's host_cpu detection when using x86_64 bins on Apple Silicon
285313
if cfg!(target_os = "macos") && cfg!(target_arch = "aarch64") {
286314
gn_args.push("host_cpu=\"arm64\"".to_string());
@@ -503,6 +531,9 @@ fn prebuilt_features_suffix() -> String {
503531
if env::var("CARGO_FEATURE_V8_ENABLE_POINTER_COMPRESSION").is_ok() {
504532
features.push_str("_ptrcomp");
505533
}
534+
if env::var("CARGO_FEATURE_V8_ENABLE_SANDBOX").is_ok() {
535+
features.push_str("_sandbox");
536+
}
506537
features
507538
}
508539

src/V8.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,28 @@ pub fn set_fatal_error_handler(that: impl MapFnTo<V8FatalErrorCallback>) {
280280
v8__V8__SetFatalErrorHandler(that.map_fn_to());
281281
}
282282
}
283+
284+
#[cfg(test)]
285+
mod test_sandbox_use {
286+
// __IsSandboxEnabled is used for testing that sandbox is actually
287+
// enabled and is not a stable API
288+
unsafe extern "C" {
289+
fn v8__V8__IsSandboxEnabled() -> bool;
290+
}
291+
292+
fn is_sandboxed() -> bool {
293+
unsafe { v8__V8__IsSandboxEnabled() }
294+
}
295+
296+
#[test]
297+
#[cfg(feature = "v8_enable_sandbox")]
298+
fn test_sandbox_on() {
299+
assert!(is_sandboxed());
300+
}
301+
302+
#[test]
303+
#[cfg(not(feature = "v8_enable_sandbox"))]
304+
fn test_sandbox_off() {
305+
assert!(!is_sandboxed());
306+
}
307+
}

src/array_buffer.rs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ use crate::support::long;
2525

2626
unsafe extern "C" {
2727
fn v8__ArrayBuffer__Allocator__NewDefaultAllocator() -> *mut Allocator;
28-
fn v8__ArrayBuffer__Allocator__NewRustAllocator(
29-
handle: *const c_void,
30-
vtable: *const RustAllocatorVtable<c_void>,
31-
) -> *mut Allocator;
3228
fn v8__ArrayBuffer__Allocator__DELETE(this: *mut Allocator);
3329
fn v8__ArrayBuffer__New__with_byte_length(
3430
isolate: *mut RealIsolate,
@@ -60,7 +56,6 @@ unsafe extern "C" {
6056
deleter: BackingStoreDeleterCallback,
6157
deleter_data: *mut c_void,
6258
) -> *mut BackingStore;
63-
6459
fn v8__BackingStore__Data(this: *const BackingStore) -> *mut c_void;
6560
fn v8__BackingStore__ByteLength(this: *const BackingStore) -> usize;
6661
fn v8__BackingStore__IsShared(this: *const BackingStore) -> bool;
@@ -108,6 +103,15 @@ unsafe extern "C" {
108103
) -> long;
109104
}
110105

106+
// Rust allocator feature is only available in non-sandboxed mode
107+
#[cfg(not(feature = "v8_enable_sandbox"))]
108+
unsafe extern "C" {
109+
fn v8__ArrayBuffer__Allocator__NewRustAllocator(
110+
handle: *const c_void,
111+
vtable: *const RustAllocatorVtable<c_void>,
112+
) -> *mut Allocator;
113+
}
114+
111115
/// A thread-safe allocator that V8 uses to allocate |ArrayBuffer|'s memory.
112116
/// The allocator is a global V8 setting. It has to be set via
113117
/// Isolate::CreateParams.
@@ -130,6 +134,7 @@ unsafe extern "C" {
130134
pub struct Allocator(Opaque);
131135

132136
/// A wrapper around the V8 Allocator class.
137+
#[cfg(not(feature = "v8_enable_sandbox"))]
133138
#[repr(C)]
134139
pub struct RustAllocatorVtable<T> {
135140
pub allocate: unsafe extern "C" fn(handle: &T, len: usize) -> *mut c_void,
@@ -172,7 +177,10 @@ pub fn new_default_allocator() -> UniqueRef<Allocator> {
172177
/// Creates an allocator managed by Rust code.
173178
///
174179
/// Marked `unsafe` because the caller must ensure that `handle` is valid and matches what `vtable` expects.
180+
///
181+
/// Not usable in sandboxed mode
175182
#[inline(always)]
183+
#[cfg(not(feature = "v8_enable_sandbox"))]
176184
pub unsafe fn new_rust_allocator<T: Sized + Send + Sync + 'static>(
177185
handle: *const T,
178186
vtable: &'static RustAllocatorVtable<T>,
@@ -187,6 +195,7 @@ pub unsafe fn new_rust_allocator<T: Sized + Send + Sync + 'static>(
187195
}
188196

189197
#[test]
198+
#[cfg(not(feature = "v8_enable_sandbox"))]
190199
fn test_rust_allocator() {
191200
use std::sync::Arc;
192201
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -226,6 +235,10 @@ fn test_rust_allocator() {
226235

227236
#[test]
228237
fn test_default_allocator() {
238+
crate::V8::initialize_platform(
239+
crate::new_default_platform(0, false).make_shared(),
240+
);
241+
crate::V8::initialize();
229242
new_default_allocator();
230243
}
231244

@@ -241,6 +254,7 @@ pub type BackingStoreDeleterCallback = unsafe extern "C" fn(
241254
deleter_data: *mut c_void,
242255
);
243256

257+
#[cfg(not(feature = "v8_enable_sandbox"))]
244258
pub(crate) mod sealed {
245259
pub trait Rawable {
246260
fn byte_len(&mut self) -> usize;
@@ -249,6 +263,7 @@ pub(crate) mod sealed {
249263
}
250264
}
251265

266+
#[cfg(not(feature = "v8_enable_sandbox"))]
252267
macro_rules! rawable {
253268
($ty:ty) => {
254269
impl sealed::Rawable for Box<[$ty]> {
@@ -289,17 +304,28 @@ macro_rules! rawable {
289304
};
290305
}
291306

307+
#[cfg(not(feature = "v8_enable_sandbox"))]
292308
rawable!(u8);
309+
#[cfg(not(feature = "v8_enable_sandbox"))]
293310
rawable!(u16);
311+
#[cfg(not(feature = "v8_enable_sandbox"))]
294312
rawable!(u32);
313+
#[cfg(not(feature = "v8_enable_sandbox"))]
295314
rawable!(u64);
315+
#[cfg(not(feature = "v8_enable_sandbox"))]
296316
rawable!(i8);
317+
#[cfg(not(feature = "v8_enable_sandbox"))]
297318
rawable!(i16);
319+
#[cfg(not(feature = "v8_enable_sandbox"))]
298320
rawable!(i32);
321+
#[cfg(not(feature = "v8_enable_sandbox"))]
299322
rawable!(i64);
323+
#[cfg(not(feature = "v8_enable_sandbox"))]
300324
rawable!(f32);
325+
#[cfg(not(feature = "v8_enable_sandbox"))]
301326
rawable!(f64);
302327

328+
#[cfg(not(feature = "v8_enable_sandbox"))]
303329
impl<T: Sized> sealed::Rawable for Box<T>
304330
where
305331
T: AsMut<[u8]>,
@@ -548,7 +574,10 @@ impl ArrayBuffer {
548574
///
549575
/// The result can be later passed to ArrayBuffer::New. The raw pointer
550576
/// to the buffer must not be passed again to any V8 API function.
577+
///
578+
/// Not available in Sandbox Mode, see new_backing_store_from_bytes for a potential alternative
551579
#[inline(always)]
580+
#[cfg(not(feature = "v8_enable_sandbox"))]
552581
pub fn new_backing_store_from_boxed_slice(
553582
data: Box<[u8]>,
554583
) -> UniqueRef<BackingStore> {
@@ -562,7 +591,10 @@ impl ArrayBuffer {
562591
///
563592
/// The result can be later passed to ArrayBuffer::New. The raw pointer
564593
/// to the buffer must not be passed again to any V8 API function.
594+
///
595+
/// Not available in Sandbox Mode, see new_backing_store_from_bytes for a potential alternative
565596
#[inline(always)]
597+
#[cfg(not(feature = "v8_enable_sandbox"))]
566598
pub fn new_backing_store_from_vec(data: Vec<u8>) -> UniqueRef<BackingStore> {
567599
Self::new_backing_store_from_bytes(data)
568600
}
@@ -575,6 +607,12 @@ impl ArrayBuffer {
575607
/// `Box<[u8]>`, and `Vec<u8>`. This will also support most other mutable bytes containers (including `bytes::BytesMut`),
576608
/// though these buffers will need to be boxed to manage ownership of memory.
577609
///
610+
/// Not available in sandbox mode. Sandbox mode requires data to be allocated
611+
/// within the sandbox's address space. Within sandbox mode, consider the below alternatives
612+
///
613+
/// 1. consider using new_backing_store and BackingStore::data() followed by doing a std::ptr::copy to copy the data into a BackingStore.
614+
/// 2. If you truly do have data that is allocated inside the sandbox address space, consider using the unsafe new_backing_store_from_ptr API
615+
///
578616
/// ```
579617
/// // Vector of bytes
580618
/// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(vec![1, 2, 3]);
@@ -585,6 +623,7 @@ impl ArrayBuffer {
585623
/// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(Box::new(bytes::BytesMut::new()));
586624
/// ```
587625
#[inline(always)]
626+
#[cfg(not(feature = "v8_enable_sandbox"))]
588627
pub fn new_backing_store_from_bytes<T>(
589628
mut bytes: T,
590629
) -> UniqueRef<BackingStore>
@@ -620,6 +659,12 @@ impl ArrayBuffer {
620659
///
621660
/// SAFETY: This API consumes raw pointers so is inherently
622661
/// unsafe. Usually you should use new_backing_store_from_boxed_slice.
662+
///
663+
/// WARNING: Using sandbox mode has extra limitations that may cause crashes
664+
/// or memory safety violations if this API is used incorrectly:
665+
///
666+
/// 1. Sandbox mode requires data to be allocated within the sandbox's address space.
667+
/// 2. It is very easy to cause memory safety errors when using this API with sandbox mode
623668
#[inline(always)]
624669
pub unsafe fn new_backing_store_from_ptr(
625670
data_ptr: *mut c_void,

src/binding.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ static_assert(sizeof(v8::Isolate::DisallowJavascriptExecutionScope) == 12,
121121
"DisallowJavascriptExecutionScope size mismatch");
122122
#endif
123123

124+
// Note: this currently uses an internal API to determine if the v8 sandbox is
125+
// enabled in the testsuite etc.
126+
extern "C" bool v8__V8__IsSandboxEnabled() {
127+
return v8::internal::SandboxIsEnabled();
128+
}
129+
124130
extern "C" {
125131
void v8__V8__SetFlagsFromCommandLine(int* argc, char** argv,
126132
const char* usage) {

src/shared_array_buffer.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ impl SharedArrayBuffer {
118118
///
119119
/// The result can be later passed to SharedArrayBuffer::New. The raw pointer
120120
/// to the buffer must not be passed again to any V8 API function.
121+
///
122+
/// Not available in Sandbox Mode, see new_backing_store_from_bytes for a potential alternative
121123
#[inline(always)]
124+
#[cfg(not(feature = "v8_enable_sandbox"))]
122125
pub fn new_backing_store_from_boxed_slice(
123126
data: Box<[u8]>,
124127
) -> UniqueRef<BackingStore> {
@@ -132,7 +135,10 @@ impl SharedArrayBuffer {
132135
///
133136
/// The result can be later passed to SharedArrayBuffer::New. The raw pointer
134137
/// to the buffer must not be passed again to any V8 API function.
138+
///
139+
/// Not available in Sandbox Mode, see new_backing_store_from_bytes for a potential alternative
135140
#[inline(always)]
141+
#[cfg(not(feature = "v8_enable_sandbox"))]
136142
pub fn new_backing_store_from_vec(data: Vec<u8>) -> UniqueRef<BackingStore> {
137143
Self::new_backing_store_from_bytes(data)
138144
}
@@ -145,6 +151,12 @@ impl SharedArrayBuffer {
145151
/// `Box<[u8]>`, and `Vec<u8>`. This will also support most other mutable bytes containers (including `bytes::BytesMut`),
146152
/// though these buffers will need to be boxed to manage ownership of memory.
147153
///
154+
/// Not available in sandbox mode. Sandbox mode requires data to be allocated
155+
/// within the sandbox's address space. Within sandbox mode, consider the below alternatives:
156+
///
157+
/// 1. consider using new_backing_store and BackingStore::data() followed by doing a std::ptr::copy to copy the data into a BackingStore.
158+
/// 2. If you truly do have data that is allocated inside the sandbox address space, consider using the unsafe new_backing_store_from_ptr API
159+
///
148160
/// ```
149161
/// // Vector of bytes
150162
/// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(vec![1, 2, 3]);
@@ -154,6 +166,7 @@ impl SharedArrayBuffer {
154166
/// // BytesMut from bytes crate
155167
/// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(Box::new(bytes::BytesMut::new()));
156168
/// ```
169+
#[cfg(not(feature = "v8_enable_sandbox"))]
157170
#[inline(always)]
158171
pub fn new_backing_store_from_bytes<T>(
159172
mut bytes: T,
@@ -173,9 +186,7 @@ impl SharedArrayBuffer {
173186
data: *mut c_void,
174187
) {
175188
// SAFETY: We know that data is a raw T from above
176-
unsafe {
177-
<T as crate::array_buffer::sealed::Rawable>::drop_raw(data as _, len);
178-
}
189+
unsafe { T::drop_raw(data as _, len) }
179190
}
180191

181192
// SAFETY: We are extending the lifetime of a slice, but we're locking away the box that we
@@ -190,10 +201,16 @@ impl SharedArrayBuffer {
190201
}
191202
}
192203

193-
/// Returns a new standalone shared BackingStore backed by given ptr.
204+
/// Returns a new standalone BackingStore backed by given ptr.
194205
///
195206
/// SAFETY: This API consumes raw pointers so is inherently
196207
/// unsafe. Usually you should use new_backing_store_from_boxed_slice.
208+
///
209+
/// WARNING: Using sandbox mode has extra limitations that may cause crashes
210+
/// or memory safety violations if this API is used incorrectly:
211+
///
212+
/// 1. Sandbox mode requires data to be allocated within the sandbox's address space.
213+
/// 2. It is very easy to cause memory safety errors when using this API with sandbox mode
197214
#[inline(always)]
198215
pub unsafe fn new_backing_store_from_ptr(
199216
data_ptr: *mut c_void,

0 commit comments

Comments
 (0)