Skip to content

Build-time execution sandboxing #475

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

Closed
3 tasks done
edward-shen opened this issue Dec 21, 2021 · 6 comments
Closed
3 tasks done

Build-time execution sandboxing #475

edward-shen opened this issue Dec 21, 2021 · 6 comments
Labels
major-change A proposal to make a major change to rustc major-change-accepted A major change proposal that was accepted T-compiler Add this label so rfcbot knows to poll the compiler team

Comments

@edward-shen
Copy link

edward-shen commented Dec 21, 2021

Note: Inline revisions after initial proposal are denoted by square brackets. Addendums are entire sections added after initial proposal.

Proposal

This cross-team major change proposal intends to add support for sandboxed build scripts and proc macros (referred to as build-time execution from here on out) by achieving the following:

  • Adding a component to rustup for an isolating runtime.
  • Providing a minimal API for sandboxed build-time execution.
  • Executing sandboxed build-time execution during compilation.
  • Providing configuration options for rustc and cargo for sandboxed build-time execution.

The current idea is to introduce a minimum API that can cover a majority of build scripts of which usage must be explicitly declared (such as in the Cargo.toml). Any API not declared but used would result in a compilation error.

To ensure that sandboxing is a first-class feature, we also need to achieve two things: ensure the isolating runtime is ubiquitous and ensure that options are easily configured by the user. The former is possible with rustup, and requires little to no effort on their end as it should just be a new component. The latter will require changes from Cargo and the Rust compiler itself.

[ Unless better alternatives arise, the intended sandboxing implementation is a WebAssembly-based runtime. Wasmtime seems to be the best solution, followed by a stand-alone wasm runtime with Wasi. Using a WebAssembly-based runtime allows us to leverage existing sandboxing efforts in the browser space, which saves us a lot of work and likely provides better sandboxing than any hand-rolled attempt. ]

Finally, introduction of this feature should be gradual. We should introduce it as opt-in, and re-evaluate defaults at the next Rust edition. This allows us to introduce it as a feature of a minor Rust version rather than needing to wait until the next Rust edition.

For an opinionated overview of why I think this feature is worthwhile:

  • Sandboxing build time execution improves the security posture for CI. [ (See the Security Addendum for more info.) ]
  • An opt-in API for build-time execution supports better auditing. This allows for future features such as warning when there’s a capability change at build time or letting users decide what sort of build-time capabilities they feel comfortable with.
  • Promoting efforts into improving compiler features that are missing and thus require build scripts to work around. Reducing build script usage is an overall win in ergonomics, and establishing a formal boundary of what build scripts can do can help organize other efforts.
  • This builds foundations for other optimizations, such as [ reusable build artifacts (e.g. dtolany/watt) and reproducible builds. Isolating and restricting the build time capabilities allow us to reduce build-time execution to functions with explicitly declared inputs and outputs. These properties can be utilized then for reproducible builds or for cross-crate caching, which can drastically improve the compile time of macro-heavy dependencies. ]
  • As far as I’m aware, no other major language supports something like this. [ Some build systems do, such as Bazel, so we may also look for inspiration there, but no language offers first-party support. ] A successful implementation may encourage other languages to do the same, improving security across the industry.

These four objectives require support from a variety of teams so I’d like to get approval in some way from at least (but not limited to) the following teams and working groups:

  • Compiler team
  • Cargo team
  • Secure Code Working Group

Once approved, the plan is to gather feedback, draft up and push for necessary RFCs, and implement required features. Landing an isolating runtime should be the first priority, as we can then build off the API and compiler features from there. If a working group is necessary, then I can look into that as well. (I may need guidance on this!)

I believe this is the right time for Rust as a language to consider and work towards this. I believe the ecosystem is mature enough for us to have meaningful insight on how build-time execution is used while still being flexible enough for large new changes such as this.

Addendum A: C dependencies

Thanks @bjorn3 for this feedback!

A huge customer of build scripts are crates that have a direct reference to C libraries. This is a problematic section that is difficult to isolate without heavy consideration. Non-standard locations, access to large amounts of locations, and other C-isms make it difficult to do so without tedious work for the end-user. Bringing in a stand-alone C compiler (as seen with Zig) also has it's own set of challenges that seem unresolved at this point in time.

As a result, this MCP is not intended to support C dependencies and only targets pure-Rust dependencies. However, I do believe work done should always keep C dependencies in mind, and leave support for C dependencies for the future.

Addendum B: Threat Model

Thanks to @wesleywiser and @The 8472 for this feedback!

Note that while I primarily want to push for it as a security feature, I want to emphasize that there are benefits beyond security. As a result, even if this is a relatively niche situation, there are still benefits beyond security that should be considered as well. This is not intended to be a universal solution to build-time threats. In fact, this by itself can easily be bypassed, and that is fully acceptable.

We're specifically targeting malicious dependencies that try to exfiltrate data at build-time, via a malicious proc-macro or build script. We assume that the prod environment is sufficiently secured such that malicious actors aren't able to do anything if they get malicious code running (e.g. there's network mitigations in place).

We can't strictly apply the same security measures on our prod hosts as we do with our CI as there's necessary holes we need to make in our security for CI to be functional (network access, for example, to fetch resources). As a result, we need a mitigation lower in the stack to help with this; this is that mitigation.

This is a very weak layer of defense, and there are other ways for malware to bypass this, but the goal here is to block the lowest hanging fruit. Forcing malicious actors to defer to runtime or to tests (which a can be a good security followup), where other mitigations should be in place, is the entire goal. We can build upon this layer of defense later; but for now something is better than nothing, in my opinion. Plus, I believe that without this mitigation in place, other potentially stronger defenses may be rendered moot.

A notable follow-up on this would be test isolation: Most proper unit tests only need a temp file or directory at best, if they need any disk access at all. Networked unit tests also seem like an incredible exception; as a result, once this gets implemented, work to improve that area may be a natural extension.

Addendum C: Maintainence & Security Response

Thanks to @Florian Weimer, @Quy Nguyen, and @The 8472 for this feedback!

Sandboxing build-time execution also subtly changes the expectations that people have. Notably, as sandboxing becomes a proper Rust feature, so does any vulnerabilities. In other words, this feature would extend the surface of vulnerabilities that Rust needs to maintain and protect against as people will certainly assume that sandboxing will be secure. A poor implementation could be catastrophic for our users. As a result, even if Rust doesn't advertise this as a security feature, we will likely need to treat (at least some) bugs as security vulnerabilities to Rust themselves. This also means we need a faster response to these issues, which is a non-trivial maintenance cost over other new features.

As someone effectively external to the inner workings of Rust, I won't make any assumptions on how difficult it is to maintain such a feature. That being said, I do have faith in Rust's stabilization practices, so I believe what will likely happen is that this feature will have an extended stay as a nightly feature until we have significant confidence that there won't be security issues at launch (perhaps the Secure Code Working group could help with this). We could also require security oriented reviews of any sandboxing changes (to what extent is to be determined later). This can hopefully mitigate any huge issues after this feature is stable. Plus. the opt-in nature of this could allow us to gain additional feedback before maintaining a more aggressive stance on sandboxing, if we decide to do one.

I also see this feature as a foundational one for many other previously mentioned potential features. The initial cost of maintaining this feature as-is might be significant, but as more and more features are built upon it, I imagine the cost to maintenance becomes shared with dependent features. Therefore if maintainability is a concern, I believe considering it as an investment for future (much more valuable) wins is reasonable.

Addendum D: Scope

This is an authoritative list of what explicitly is not in scope of this MCP:

  • Pre-compiled binary artifacts. This adds significant scope to what already is a significant change. This can certainly be an extension to this once work is completed.
  • Sandboxing the testing harness and tests.
  • C dependency support, although we should keep this in mind during implementation.
  • Opt-out behavior. This MCP only extends to opt-in behavior. A MCP or RFC must be present for opt-out behavior, and will likely require an edition change as well.

Mentors or Reviewers

None yet

Process

The main points of the Major Change Process are as follows:

  • File an issue describing the proposal.
  • A compiler team member or contributor who is knowledgeable in the area can second by writing @rustbot second.
    • Finding a "second" suffices for internal changes. If however, you are proposing a new public-facing feature, such as a -C flag, then full team check-off is required.
    • Compiler team members can initiate a check-off via @rfcbot fcp merge on either the MCP or the PR.
  • Once an MCP is seconded, the Final Comment Period begins. If no objections are raised after 10 days, the MCP is considered approved.

You can read more about Major Change Proposals on forge.

Comments

  • 12-21-2021 14:57 PST: Addressed initial comments in Zulip, modified MCP to clarify and incorporate changes. Added Addendum A and B.
  • 12-21-2021 16:29 PST: Added test environment as potential future security-oriented work post-build-time execution sandboxing.
  • 12-26-2021 15:31 PST: Added Addendums C and D.

This issue is not meant to be used for technical discussion. There is a Zulip stream for that. Use this issue to leave procedural comments, such as volunteering to review, indicating that you second the proposal (or third, etc), or raising a concern that you would like to be addressed.

@edward-shen edward-shen added major-change A proposal to make a major change to rustc T-compiler Add this label so rfcbot knows to poll the compiler team labels Dec 21, 2021
@rustbot
Copy link
Collaborator

rustbot commented Dec 21, 2021

This issue is not meant to be used for technical discussion. There is a Zulip stream for that. Use this issue to leave procedural comments, such as volunteering to review, indicating that you second the proposal (or third, etc), or raising a concern that you would like to be addressed.

cc @rust-lang/compiler @rust-lang/compiler-contributors

@rustbot rustbot added the to-announce Announce this issue on triage meeting label Dec 21, 2021
@programmerjake
Copy link
Member

related, from a wasm running builds perspective:
WebAssembly/WASI#414
WebAssembly/WASI#459

@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Dec 23, 2021
@oli-obk
Copy link
Contributor

oli-obk commented May 24, 2022

@rustbot second

@rustbot rustbot added the final-comment-period The FCP has started, most (if not all) team members are in agreement label May 24, 2022
@apiraino
Copy link
Contributor

apiraino commented Jun 8, 2022

@rustbot label -final-comment-period +major-change-accepted

@apiraino apiraino closed this as completed Jun 8, 2022
@rustbot rustbot added major-change-accepted A major change proposal that was accepted to-announce Announce this issue on triage meeting and removed final-comment-period The FCP has started, most (if not all) team members are in agreement labels Jun 8, 2022
@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Jun 9, 2022
@tarcieri
Copy link

tarcieri commented May 9, 2024

Should the two unchecked checkboxes under "Process" be checked now?

@edward-shen
Copy link
Author

Sure -- I've ticked off those boxes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
major-change A proposal to make a major change to rustc major-change-accepted A major change proposal that was accepted T-compiler Add this label so rfcbot knows to poll the compiler team
Projects
None yet
Development

No branches or pull requests

6 participants