Skip to content

Commit 5945d40

Browse files
committed
Reimplement unsafe-aot-compilation feature
Signed-off-by: Lann Martin <[email protected]>
1 parent f494323 commit 5945d40

File tree

4 files changed

+136
-62
lines changed

4 files changed

+136
-62
lines changed

crates/trigger/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ homepage.workspace = true
88
repository.workspace = true
99
rust-version.workspace = true
1010

11+
[features]
12+
# Enables loading AOT compiled components, a potentially unsafe operation. See
13+
# `ComponentLoader::enable_loading_aot_compiled_components`
14+
# documentation for more information about the safety risks.
15+
unsafe-aot-compilation = []
16+
1117
[dependencies]
1218
anyhow = "1"
1319
clap = { version = "3.1.18", features = ["derive", "env"] }

crates/trigger/src/cli.rs

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ use spin_app::App;
1313
use spin_common::sloth;
1414
use spin_common::ui::quoted_path;
1515
use spin_common::url::parse_file_url;
16-
use spin_core::async_trait;
1716
use spin_factors::RuntimeFactors;
18-
use spin_factors_executor::{ComponentLoader, FactorsExecutor};
17+
use spin_factors_executor::FactorsExecutor;
1918

19+
use crate::loader::ComponentLoader;
2020
use crate::{Trigger, TriggerApp};
2121
pub use initial_kv_setter::InitialKvSetterHook;
2222
pub use launch_metadata::LaunchMetadata;
@@ -312,73 +312,14 @@ impl<T: Trigger<B::Factors>, B: RuntimeFactorsBuilder> TriggerAppBuilder<T, B> {
312312

313313
let (factors, runtime_config) = B::build(&common_options, &options)?;
314314

315-
// TODO: port the rest of the component loader logic
316-
struct SimpleComponentLoader;
317-
318-
#[async_trait]
319-
impl spin_compose::ComponentSourceLoader for SimpleComponentLoader {
320-
async fn load_component_source(
321-
&self,
322-
source: &spin_app::locked::LockedComponentSource,
323-
) -> anyhow::Result<Vec<u8>> {
324-
let source = source
325-
.content
326-
.source
327-
.as_ref()
328-
.context("LockedComponentSource missing source field")?;
329-
330-
let path = parse_file_url(source)?;
331-
332-
let bytes: Vec<u8> = tokio::fs::read(&path).await.with_context(|| {
333-
format!(
334-
"failed to read component source from disk at path {}",
335-
quoted_path(&path)
336-
)
337-
})?;
338-
339-
let component = spin_componentize::componentize_if_necessary(&bytes)?;
340-
341-
Ok(component.into())
342-
}
343-
}
344-
345-
#[async_trait]
346-
impl ComponentLoader for SimpleComponentLoader {
347-
async fn load_component(
348-
&self,
349-
engine: &spin_core::wasmtime::Engine,
350-
component: &spin_factors::AppComponent,
351-
) -> anyhow::Result<spin_core::Component> {
352-
let source = component
353-
.source()
354-
.content
355-
.source
356-
.as_ref()
357-
.context("LockedComponentSource missing source field")?;
358-
let path = parse_file_url(source)?;
359-
360-
let composed = spin_compose::compose(self, component.locked)
361-
.await
362-
.with_context(|| {
363-
format!(
364-
"failed to resolve dependencies for component {:?}",
365-
component.locked.id
366-
)
367-
})?;
368-
369-
spin_core::Component::new(engine, composed)
370-
.with_context(|| format!("compiling wasm {}", quoted_path(&path)))
371-
}
372-
}
373-
374315
let mut executor = FactorsExecutor::new(core_engine_builder, factors)?;
375316
B::configure_app(&mut executor, &runtime_config, &common_options, &options)?;
376317
let executor = Arc::new(executor);
377318

378319
let configured_app = {
379320
let _sloth_guard = warn_if_wasm_build_slothful();
380321
executor
381-
.load_app(app, runtime_config.into(), &SimpleComponentLoader)
322+
.load_app(app, runtime_config.into(), &ComponentLoader::default())
382323
.await?
383324
};
384325

crates/trigger/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod cli;
2+
pub mod loader;
23

34
use std::future::Future;
45

crates/trigger/src/loader.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use anyhow::Context as _;
2+
use spin_common::{ui::quoted_path, url::parse_file_url};
3+
use spin_core::{async_trait, wasmtime, Component};
4+
use spin_factors::AppComponent;
5+
6+
#[derive(Default)]
7+
pub struct ComponentLoader {
8+
#[cfg(feature = "unsafe-aot-compilation")]
9+
aot_compilation_enabled: bool,
10+
}
11+
12+
impl ComponentLoader {
13+
/// Updates the TriggerLoader to load AOT precompiled components
14+
///
15+
/// **Warning: This feature may bypass important security guarantees of the
16+
/// Wasmtime security sandbox if used incorrectly! Read this documentation
17+
/// carefully.**
18+
///
19+
/// Usually, components are compiled just-in-time from portable Wasm
20+
/// sources. This method causes components to instead be loaded
21+
/// ahead-of-time as Wasmtime-precompiled native executable binaries.
22+
/// Precompiled binaries must be produced with a compatible Wasmtime engine
23+
/// using the same Wasmtime version and compiler target settings - typically
24+
/// by a host with the same processor that will be executing them. See the
25+
/// Wasmtime documentation for more information:
26+
/// https://docs.rs/wasmtime/latest/wasmtime/struct.Module.html#method.deserialize
27+
///
28+
/// # Safety
29+
///
30+
/// This method is marked as `unsafe` because it enables potentially unsafe
31+
/// behavior if used to load malformed or malicious precompiled binaries.
32+
/// Loading sources from an incompatible Wasmtime engine will fail but is
33+
/// otherwise safe. This method is safe if it can be guaranteed that
34+
/// `<TriggerLoader as Loader>::load_component` will only ever be called
35+
/// with a trusted `LockedComponentSource`. **Precompiled binaries must
36+
/// never be loaded from untrusted sources.**
37+
#[cfg(feature = "unsafe-aot-compilation")]
38+
pub unsafe fn enable_loading_aot_compiled_components(&mut self) {
39+
self.aot_compilation_enabled = true;
40+
}
41+
42+
#[cfg(feature = "unsafe-aot-compilation")]
43+
fn load_precompiled_component(
44+
&self,
45+
engine: &wasmtime::Engine,
46+
path: &std::path::Path,
47+
) -> anyhow::Result<Component> {
48+
assert!(self.aot_compilation_enabled);
49+
match engine.detect_precompiled_file(path)? {
50+
Some(wasmtime::Precompiled::Component) => unsafe {
51+
Component::deserialize_file(engine, path)
52+
},
53+
Some(wasmtime::Precompiled::Module) => {
54+
anyhow::bail!("expected AOT compiled component but found module");
55+
}
56+
None => {
57+
anyhow::bail!("expected AOT compiled component but found other data");
58+
}
59+
}
60+
}
61+
}
62+
63+
#[async_trait]
64+
impl spin_factors_executor::ComponentLoader for ComponentLoader {
65+
async fn load_component(
66+
&self,
67+
engine: &wasmtime::Engine,
68+
component: &AppComponent,
69+
) -> anyhow::Result<Component> {
70+
let source = component
71+
.source()
72+
.content
73+
.source
74+
.as_ref()
75+
.context("LockedComponentSource missing source field")?;
76+
let path = parse_file_url(source)?;
77+
78+
#[cfg(feature = "unsafe-aot-compilation")]
79+
if self.aot_compilation_enabled {
80+
return self
81+
.load_precompiled_component(engine, &path)
82+
.with_context(|| format!("error deserializing component from {path:?}"));
83+
}
84+
85+
let composed = spin_compose::compose(&ComponentSourceLoader, component.locked)
86+
.await
87+
.with_context(|| {
88+
format!(
89+
"failed to resolve dependencies for component {:?}",
90+
component.locked.id
91+
)
92+
})?;
93+
94+
spin_core::Component::new(engine, composed)
95+
.with_context(|| format!("failed to compile component from {}", quoted_path(&path)))
96+
}
97+
}
98+
99+
struct ComponentSourceLoader;
100+
101+
#[async_trait]
102+
impl spin_compose::ComponentSourceLoader for ComponentSourceLoader {
103+
async fn load_component_source(
104+
&self,
105+
source: &spin_app::locked::LockedComponentSource,
106+
) -> anyhow::Result<Vec<u8>> {
107+
let source = source
108+
.content
109+
.source
110+
.as_ref()
111+
.context("LockedComponentSource missing source field")?;
112+
113+
let path = parse_file_url(source)?;
114+
115+
let bytes: Vec<u8> = tokio::fs::read(&path).await.with_context(|| {
116+
format!(
117+
"failed to read component source from disk at path {}",
118+
quoted_path(&path)
119+
)
120+
})?;
121+
122+
let component = spin_componentize::componentize_if_necessary(&bytes)?;
123+
124+
Ok(component.into())
125+
}
126+
}

0 commit comments

Comments
 (0)