Skip to content

Commit

Permalink
macOS prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
smoelius committed Apr 5, 2024
1 parent 500f5c1 commit 4b72e78
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 8 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
strategy:
fail-fast: ${{ github.event_name == 'merge_group' }}
matrix:
environment: [ubuntu-latest]
environment: [ubuntu-latest, macos-latest]

runs-on: ${{ matrix.environment }}

Expand All @@ -71,11 +71,14 @@ jobs:

- name: Install tools
run: |
sudo apt install bubblewrap
rustup install nightly
rustup +nightly component add clippy
cargo install group-runner || true
- name: Install Bubblewrap
if: ${{ matrix.environment == 'ubuntu-latest' }}
run: sudo apt install bubblewrap

- name: Build
run: cargo test --no-run

Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A linker replacement to help protect against malicious build scripts

`build-wrap` "re-links" a build script so that it is executed under another command. By default the command is [Bubblewrap], though this is configurable. See [Environment variables] and [How it works] for more information.
`build-wrap` "re-links" a build script so that it is executed under another command. By default, the command is [Bubblewrap] (Linux) or [`sandbox-exec`] (macOS), though this is configurable. See [Environment variables] and [How it works] for more information.

## Installation

Expand All @@ -20,7 +20,7 @@ Installing `build-wrap` requires two steps:

## Environment variables

- `BUILD_WRAP_CMD`: Command used to execute a build script. Default:
- `BUILD_WRAP_CMD`: Command used to execute a build script. Linux default:

```sh
bwrap
Expand All @@ -34,6 +34,25 @@ Installing `build-wrap` requires two steps:

Note that `bwrap` is [Bubblewrap].

macOS default:

```sh
sandbox-exec -p
(version\ 1)\
(deny\ default)\
(allow\ file-read*)\ # Allow read-only access everywhere
(allow\ file-write*\ (subpath\ "/dev"))\ # Allow write access to /dev
(allow\ file-write*\ (subpath\ "{OUT_DIR}"))\ # Allow write access to `OUT_DIR`
(allow\ file-write*\ (subpath\ "{TMPDIR}"))\ # Allow write access to `TMPDIR`
(allow\ process-exec)\ # Allow `exec`
(allow\ process-fork)\ # Allow `fork`
(allow\ sysctl-read)\ # Allow reading kernel state
(deny\ network*) # Deny network access
{} # Build script path
```

Note that `(version\ 1)\ ... (deny\ network*)` expands to a single string (see [How `BUILD_WRAP_CMD` is expanded] below).

- `BUILD_WRAP_LD`: Linker to use. Default: `cc`

Note that the above environment variables are read **when the build script is linked**. So, for example, changing `BUILD_WRAP_CMD` will not change the command used to execute already linked build scripts.
Expand Down Expand Up @@ -65,4 +84,6 @@ Given a build script `B`, its "wrapped" version `B'` contains a copy of `B` and
[Bubblewrap]: https://github.com/containers/bubblewrap
[Environment variables]: #environment-variables
[How it works]: #how-it-works
[How `BUILD_WRAP_CMD` is expanded]: #how-build_wrap_cmd-is-expanded
[`sandbox-exec`]: https://keith.github.io/xcode-man-pages/sandbox-exec.1.html
[manner described above]: #how-build_wrap_cmd-is-expanded
29 changes: 27 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,38 @@ use std::{
mod util;
mod wrapper;

const DEFAULT_CMD: &str = "bwrap
const DEFAULT_CMD: &str = if cfg!(target_os = "linux") {
"bwrap
--ro-bind / /
--dev-bind /dev /dev
--bind {OUT_DIR} {OUT_DIR}
--bind /tmp /tmp
--unshare-net
{}";
{}"
} else {
// smoelius: The following blog post is a useful `sandbox-exec` reference:
// https://7402.org/blog/2020/macos-sandboxing-of-folder.html
// smoelius: The following package does not build with the current `sandbox-exec` command:
// https://crates.io/crates/psm
// Adding the following line fixes the problem:
// ```
// (allow\ file-write*\ (subpath\ "/private{TMPDIR}"))\
// ```
// However, I don't want to pollute the command for this one package. This issue requires
// further investigation.
r#"sandbox-exec -p
(version\ 1)\
(deny\ default)\
(allow\ file-read*)\
(allow\ file-write*\ (subpath\ "/dev"))\
(allow\ file-write*\ (subpath\ "{OUT_DIR}"))\
(allow\ file-write*\ (subpath\ "{TMPDIR}"))\
(allow\ process-exec)\
(allow\ process-fork)\
(allow\ sysctl-read)\
(deny\ network*)
{}"#
};

fn main() -> Result<()> {
let args: Vec<String> = args().collect();
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions tests/cases/outside_out_dir.macos.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
message: "Operation not permitted"
File renamed without changes.
1 change: 1 addition & 0 deletions tests/cases/ping.macos.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ping: sendto: Operation not permitted
File renamed without changes.
1 change: 1 addition & 0 deletions tests/cases/tiocsti.macos.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libc::ioctl: Operation not permitted
7 changes: 6 additions & 1 deletion tests/custom_build_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ fn custom_build_name() {
assert!(!output.status.success());

let stderr = std::str::from_utf8(&output.stderr).unwrap();
let syscall = if cfg!(target_os = "linux") {
"socket"
} else {
"sendto"
};
assert!(
stderr.contains("ping: socket: Operation not permitted"),
stderr.contains(&format!("ping: {syscall}: Operation not permitted")),
"stderr does not contain expected string:\n```\n{stderr}\n```",
);
}
9 changes: 8 additions & 1 deletion tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ fn integration() {
}

fn test_case(path: &Path) {
let stderr_path = path.with_extension("stderr");
let mut stderr_path = path.with_extension("stderr");
if !stderr_path.exists() {
stderr_path = if cfg!(target_os = "linux") {
path.with_extension("linux.stderr")
} else {
path.with_extension("macos.stderr")
}
}
let expected_stderr_substring = read_to_string(stderr_path).unwrap();

let temp_package = util::temp_package(path).unwrap();
Expand Down

0 comments on commit 4b72e78

Please sign in to comment.