-
Notifications
You must be signed in to change notification settings - Fork 251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(loader): support loading AOT compiled components #2318
Conversation
Updates TriggerLoader::load_component to support loading precompiled components Signed-off-by: Kate Goldenring <[email protected]> Co-authored-by: Radu Matei <[email protected]> Co-authored-by: Brian H <[email protected]> Co-authored-by: Vaughn Dice <[email protected]>
Signed-off-by: Kate Goldenring <[email protected]>
Signed-off-by: Kate Goldenring <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
crates/trigger/src/loader.rs
Outdated
} | ||
} | ||
|
||
/// Creates a TriggerLoader that loads AOT compiled components. It enables |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'll want to spell out exactly what needs to happen here for this to be safe. i.e., that the LockedComponentSource
passed to Loader::load_component
references a precompiled component compiled using the same version of spin_core
used here and the same Engine
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The most important point to get across is that it is a complete security meltdown if used with untrusted input.
Practically it is "safe" for any wasmtime precompiled input (and almost any non-malicious input); it will just fail to deserialize unless compiled with a compatible engine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the comment a little, but am still only mentioning that the requirement is wasmtime
compilation (nothing Spin specific). Also, now the function is marked unsafe
. @rylev do these changes look appropriate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think marking the constructor as unsafe
is the right approach. The contract with unsafe
is that if the caller of the function can verify certain invariants are met at the time of the function call then the call is safe. However, it's the call to Loader::load_component
that needs to verify the invariant (in this case that the component source is valid to be used with Component::deserialize
).
unsafe
is IMO inappropriate because the invariant that needs to be satisfied cannot be satisfied in code. It's up to whoever is running the software to ensure the invariant holds. This is why I think a feature flag that prominently displays the safety risks is the right approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now see @lann's comment below pointing to similar usage in cranelift
. However, I think this is categorically different usage.
The usage in the cranelift
API can still be verified as correct by users by verifying its usage is correct in combination with the rest of the usage of cranelift
- in other words, the safety properties can be verified statically albeit manually. The usage here on the other hand, cannot be verified statically as correct - it depends on how the software is run - the same code can be correct when run in one context and incorrect when run in another. That is not what unsafe
is meant to signal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deserialize_file
is different in that the safety check happens at the call site of the function marked unsafe
. You should only call deserialize_file
if you're sure the input to that function meets some invariants. That's different than calling a function and promising all future input into the system as a whole meets some invariants.
Taking a step back... we both are trying to achieve the same thing here: ensuring that users of these APIs know what the right thing to do is. I personally think the feature flag is the right approach. It says "don't build and run this software unless you know what you're doing", and I think it does it in a better way than marking the function unsafe
. If others disagree though that's fine, I won't try to block merging this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be fine with the feature flag only if "unsafe" appears in it, but I would personally keep this unsafe
as well (belt and suspenders), just so unsafe
appears somewhere in the calling code. 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just so unsafe appears somewhere in the calling code.
I tend to agree with this (being the intent originally).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not willing to die on this hill 😉 so . It's certainly better than not marking a function as unsafe
when it clearly is.
86e10f8
to
94e4d99
Compare
Signed-off-by: Kate Goldenring <[email protected]>
94e4d99
to
b2196bc
Compare
Signed-off-by: Kate Goldenring <[email protected]>
crates/trigger/src/loader.rs
Outdated
/// The method is safe for any Wasmtime precompiled content (components | ||
/// serialized with Wasmtime` Component::serialize` or | ||
/// `Engine::precompile_module`). It should never be used with untrusted | ||
/// input. See the Wasmtime documentation for more information: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is confusing unless you've read the code. There's no indication of which input we're actually talking about. It would be better if this comment indicated that it is the content source of the LockedComponentSource
input to <TriggerLoader as Loader>::load_component
that we're concerned with. Something like:
This method is safe if it can be guaranteed that
<TriggerLoader as Loader>::load_component
will only ever be called with a trustedLockedComponentSource
.
The details about Component::deserialize
are fine to keep but should be secondary to the point above IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this could use a little wordsmithing but most consumers (by which I just mean spin shim 😅) probably won't have context for what LockedComponentSource
or even Loader::load_component
are doing.
Maybe something along the lines of:
Updates the TriggerLoader to load AOT precompiled components
**Warning: This feature may bypass important security guarantees of the Wasmtime
security sandbox if used incorrectly! Read this documentation carefully.**
Usually, components are compiled just-in-time from portable Wasm sources.
This method causes components to instead be loaded ahead-of-time as
Wasmtime-precompiled native executable binaries. Precompiled binaries must be
produced with a compatible Wasmtime engine using the same Wasmtime version and
compiler target settings - typically by a host with the same processor that will
be executing them. See the Wasmtime documentation for more information:
https://docs.rs/wasmtime/latest/wasmtime/struct.Module.html#method.deserialize
# Safety
This method is marked as `unsafe` because it enables potentially unsafe behavior if
used to load malformed or malicious precompiled binaries. Loading sources from an
incompatible Wasmtime engine will fail but is otherwise safe. **Precompiled binaries
must never be loaded from untrusted sources.**
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
most consumers (by which I just mean spin shim 😅) probably won't have context for what LockedComponentSource or even Loader::load_component are doing
This is true, but most Spin users don't need to make this choice. And if you're building a different consumer that would need to make this choice, you have to know these details in order to make the right choice.
Most people should read this and interpret this as just saying "here be dragons". Only those who really need to know will take the time to inform themselves about the details so they can make a decision about whether to enable this feature. How would one even know how to precompile binaries without understanding in detail about how they are eventually loaded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I combined both of your proposed texts, ensuring @rylev's constructive comment on what exact Spin structure must be trusted (LockedComponentSource
) owned the line of what makes it safe.
Signed-off-by: Kate Goldenring <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM from the perspective of a consumer (the containerd shim).
Thank you!
No description provided.