├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── README.txt └── sandboxer.c ├── build.rs ├── clippy.toml ├── fixtures ├── .gitignore ├── cargo_target_dir │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── lib.rs └── custom_build_name │ ├── Cargo.toml │ ├── custom_build_name.rs │ └── src │ └── lib.rs ├── rustfmt.toml ├── scripts ├── sandboxer.sh └── walk_dir.sh ├── src ├── linking.rs ├── main.rs ├── util │ ├── common.rs │ └── mod.rs └── wrapper.rs └── tests ├── build_scripts ├── dev_null.rs ├── inside_out_dir.rs ├── linux-sandboxer │ ├── config.toml │ ├── dev_null.stderr │ ├── inside_out_dir.stderr │ ├── outside_out_dir.stderr │ ├── ping.stderr │ ├── rerun_if_build_wrap_cmd_changed.stderr │ ├── rustc_version.stderr │ └── tiocsti.stderr ├── linux │ ├── config.toml │ ├── dev_null.stderr │ ├── inside_out_dir.stderr │ ├── outside_out_dir.stderr │ ├── ping.stderr │ ├── rerun_if_build_wrap_cmd_changed.stderr │ ├── rustc_version.stderr │ └── tiocsti.stderr ├── macos │ ├── config.toml │ ├── dev_null.stderr │ ├── inside_out_dir.stderr │ ├── outside_out_dir.stderr │ ├── ping.stderr │ ├── rerun_if_build_wrap_cmd_changed.stderr │ ├── rustc_version.stderr │ └── tiocsti.stderr ├── outside_out_dir.rs ├── ping.rs ├── rerun_if_build_wrap_cmd_changed.rs ├── rustc_version.rs └── tiocsti.rs ├── integration ├── allow.rs ├── build_scripts.rs ├── build_wrap_cmd_changed.rs ├── cargo_target_dir.rs ├── ci.rs ├── config.rs ├── config_allow.rs ├── custom_build_name.rs ├── dogfood.rs ├── enabled.rs ├── main.rs ├── third_party.rs └── util.rs ├── markdown_link_check.json ├── supply_chain.json └── third_party ├── aws-lc-fips-sys.txt ├── lief.txt ├── linux-sandboxer ├── aws-lc-fips-sys.stderr ├── config.toml ├── lief.stderr └── psm.stderr ├── linux ├── aws-lc-fips-sys.stderr ├── config.toml ├── lief.stderr └── psm.stderr ├── macos ├── aws-lc-fips-sys.stderr ├── config.toml ├── lief.stderr └── psm.stderr └── psm.txt /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | 8 | - package-ecosystem: cargo 9 | directory: / 10 | schedule: 11 | interval: weekly 12 | allow: 13 | - dependency-type: direct 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | merge_group: 5 | pull_request: 6 | schedule: 7 | - cron: "0 3 * * 6" # 6 = Saturday 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ci-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | env: 15 | CARGO_TERM_COLOR: always 16 | 17 | jobs: 18 | check-up-to-dateness: 19 | outputs: 20 | is-up-to-date: ${{ steps.main.outputs.is-up-to-date }} 21 | runs-on: ubuntu-latest 22 | steps: 23 | - id: main 24 | uses: trailofbits/check-up-to-dateness@v1 25 | 26 | test: 27 | needs: [check-up-to-dateness] 28 | 29 | if: needs.check-up-to-dateness.outputs.is-up-to-date != 'true' 30 | 31 | strategy: 32 | fail-fast: ${{ github.event_name == 'merge_group' }} 33 | matrix: 34 | environment: [ubuntu-latest, macos-15] 35 | 36 | runs-on: ${{ matrix.environment }} 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | 41 | - uses: actions/cache@v4 42 | with: 43 | path: | 44 | ~/.cargo/bin/ 45 | ~/.cargo/registry/index/ 46 | ~/.cargo/registry/cache/ 47 | ~/.cargo/git/db/ 48 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 49 | 50 | - name: Install tools 51 | run: | 52 | rustup update 53 | rustup install nightly 54 | rustup +nightly component add clippy 55 | cargo install cargo-dylint dylint-link || true 56 | cargo install cargo-supply-chain || true 57 | cargo install group-runner || true 58 | 59 | - name: Install Bubblewrap 60 | if: ${{ runner.os == 'Linux' }} 61 | run: sudo apt install bubblewrap 62 | 63 | - name: Install Bubblewrap profile on Ubuntu 64 | if: ${{ runner.os == 'Linux' }} 65 | run: | 66 | sudo apt install apparmor-profiles 67 | sudo cp /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d || true 68 | sudo systemctl reload apparmor 69 | 70 | # smoelius: Go is needed for the `aws-lc-fips-sys` third-party test. 71 | - name: Install Go on macOS 72 | if: ${{ runner.os == 'macOS' }} 73 | run: brew install go 74 | 75 | - name: Build 76 | run: cargo test --no-run 77 | 78 | - name: Test 79 | run: cargo test --config "target.'cfg(all())'.runner = 'group-runner'" 80 | 81 | all-checks: 82 | needs: [test] 83 | 84 | # smoelius: From "Defining prerequisite jobs" 85 | # (https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow#defining-prerequisite-jobs): 86 | # > If you would like a job to run even if a job it is dependent on did not succeed, use the 87 | # > `always()` conditional expression in `jobs..if`. 88 | if: ${{ always() }} 89 | 90 | runs-on: ubuntu-latest 91 | 92 | steps: 93 | - name: Check results 94 | if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} 95 | run: exit 1 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.5.1 4 | 5 | - Eliminate reliance on `once_cell` ([115](https://github.com/trailofbits/build-wrap/pull/115)) 6 | 7 | ## 0.5.0 8 | 9 | - FEATURE: Support `$HOME/.config/build-wrap/allow.txt`. A package whose name appears in this file will be built as though `BUILD_WRAP_ALLOW` were set to `1`. ([104](https://github.com/trailofbits/build-wrap/pull/104)) 10 | 11 | ## 0.4.1 12 | 13 | - Unset `CARGO_TARGET_DIR` when building the wrapper package ([aa4a646](https://github.com/trailofbits/build-wrap/commit/aa4a646d8eee4e209140f12fe47554f5c3e913a8)) 14 | 15 | ## 0.4.0 16 | 17 | - FEATURE: Rename the original build script and refer to it from the "wrapper" built script, rather than include the original build script as a byte array ([86](https://github.com/trailofbits/build-wrap/pull/86) and [89](https://github.com/trailofbits/build-wrap/pull/89)) 18 | 19 | ## 0.3.2 20 | 21 | - Update documentation ([41a6361](https://github.com/trailofbits/build-wrap/commit/41a6361466840db58c3853992ff0826d230040bc). [56aded5](https://github.com/trailofbits/build-wrap/commit/56aded59a8630bacfe8298bee759b459948fa374), and [f08ed71](https://github.com/trailofbits/build-wrap/commit/f08ed71f1f5c8857a4733196a2a0a692d7091ceb)) 22 | - Check for Bubblewrap AppArmor profile before declaring `build-wrap` enabled on Ubuntu 24.04 ([81](https://github.com/trailofbits/build-wrap/pull/81)) 23 | 24 | ## 0.3.1 25 | 26 | - Reduce error message verbosity ([58](https://github.com/trailofbits/build-wrap/pull/58)) 27 | 28 | ## 0.3.0 29 | 30 | - FEATURE: Show whether `build-wrap` is enabled in help message ([72a5991](https://github.com/trailofbits/build-wrap/commit/72a5991c7cdc55250f78692598cc9ff48e23d338)) 31 | - FEATURE: Add `BUILD_WRAP_ALLOW` environment variable. When set, if running a build script under `BUILD_WRAP_CMD` fails, the failure is reported and the build script is rerun normally. ([639b21b](https://github.com/trailofbits/build-wrap/commit/639b21b5fe1711967c969ba9ffd6afabe0ffa44d)) 32 | 33 | ## 0.2.1 34 | 35 | - If `TMPDIR` is set to a path in `/private`, then `PRIVATE_TMPDIR` is treated as though it is set to that path when `BUILD_WRAP_CMD` is expanded. This is needed for some build scripts that use [`cc-rs`](https://github.com/rust-lang/cc-rs). ([ff75d98](https://github.com/trailofbits/build-wrap/commit/ff75d98b2ea9ad63d8361e94c13ec0e6678d22e5)) 36 | 37 | ## 0.2.0 38 | 39 | - Change how the `BUILD_WRAP_CMD` environment variable is expanded ([500f5c1](https://github.com/trailofbits/build-wrap/commit/500f5c1f127697bfbe683e0278f6dd8be32e0bb5)) 40 | - Split at whitespace before replacing environment variables, instead of after 41 | - Allow escaping whitespace with a backslash (`\`) 42 | - Preliminary macOS support ([4b72e78](https://github.com/trailofbits/build-wrap/commit/4b72e784656e4eb31a3937ebc3d2ccc2a25123e9)) 43 | 44 | ## 0.1.1 45 | 46 | - Respect `CARGO` environment variable, if set ([3512a63](https://github.com/trailofbits/build-wrap/commit/3512a636868e1e871ce4544f5bd425fbcf88b444)) 47 | - `cd` into the directory in which the wrapper package is being built. This avoids any `.cargo/config.toml` that may be in ancestors of the directory from which `build-wrap` was invoked. ([57775ac](https://github.com/trailofbits/build-wrap/commit/57775acff06ab59eccf78e17c819f960954fc9b0)) 48 | 49 | ## 0.1.0 50 | 51 | - Initial release 52 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @smoelius 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.18" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.10" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 49 | dependencies = [ 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.6" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys", 61 | ] 62 | 63 | [[package]] 64 | name = "anyhow" 65 | version = "1.0.98" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 68 | 69 | [[package]] 70 | name = "assert_cmd" 71 | version = "2.0.17" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" 74 | dependencies = [ 75 | "anstyle", 76 | "bstr", 77 | "doc-comment", 78 | "libc", 79 | "predicates", 80 | "predicates-core", 81 | "predicates-tree", 82 | "wait-timeout", 83 | ] 84 | 85 | [[package]] 86 | name = "autocfg" 87 | version = "1.4.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 90 | 91 | [[package]] 92 | name = "bitflags" 93 | version = "2.9.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 96 | 97 | [[package]] 98 | name = "bstr" 99 | version = "1.11.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" 102 | dependencies = [ 103 | "memchr", 104 | "regex-automata", 105 | "serde", 106 | ] 107 | 108 | [[package]] 109 | name = "build-wrap" 110 | version = "0.5.1" 111 | dependencies = [ 112 | "anyhow", 113 | "assert_cmd", 114 | "cargo_metadata", 115 | "ctor", 116 | "home", 117 | "regex", 118 | "serde", 119 | "serde_json", 120 | "similar-asserts", 121 | "snapbox", 122 | "tempfile", 123 | "toml", 124 | "xdg", 125 | ] 126 | 127 | [[package]] 128 | name = "camino" 129 | version = "1.1.9" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" 132 | dependencies = [ 133 | "serde", 134 | ] 135 | 136 | [[package]] 137 | name = "cargo-platform" 138 | version = "0.2.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4" 141 | dependencies = [ 142 | "serde", 143 | ] 144 | 145 | [[package]] 146 | name = "cargo-util-schemas" 147 | version = "0.2.0" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "e63d2780ac94487eb9f1fea7b0d56300abc9eb488800854ca217f102f5caccca" 150 | dependencies = [ 151 | "semver", 152 | "serde", 153 | "serde-untagged", 154 | "serde-value", 155 | "thiserror 1.0.69", 156 | "toml", 157 | "unicode-xid", 158 | "url", 159 | ] 160 | 161 | [[package]] 162 | name = "cargo_metadata" 163 | version = "0.20.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "4f7835cfc6135093070e95eb2b53e5d9b5c403dc3a6be6040ee026270aa82502" 166 | dependencies = [ 167 | "camino", 168 | "cargo-platform", 169 | "cargo-util-schemas", 170 | "semver", 171 | "serde", 172 | "serde_json", 173 | "thiserror 2.0.6", 174 | ] 175 | 176 | [[package]] 177 | name = "cfg-if" 178 | version = "1.0.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 181 | 182 | [[package]] 183 | name = "colorchoice" 184 | version = "1.0.3" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 187 | 188 | [[package]] 189 | name = "console" 190 | version = "0.15.11" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 193 | dependencies = [ 194 | "encode_unicode", 195 | "libc", 196 | "once_cell", 197 | "windows-sys", 198 | ] 199 | 200 | [[package]] 201 | name = "ctor" 202 | version = "0.4.2" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "a4735f265ba6a1188052ca32d461028a7d1125868be18e287e756019da7607b5" 205 | dependencies = [ 206 | "ctor-proc-macro", 207 | "dtor", 208 | ] 209 | 210 | [[package]] 211 | name = "ctor-proc-macro" 212 | version = "0.0.5" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" 215 | 216 | [[package]] 217 | name = "difflib" 218 | version = "0.4.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 221 | 222 | [[package]] 223 | name = "displaydoc" 224 | version = "0.2.5" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 227 | dependencies = [ 228 | "proc-macro2", 229 | "quote", 230 | "syn", 231 | ] 232 | 233 | [[package]] 234 | name = "doc-comment" 235 | version = "0.3.3" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 238 | 239 | [[package]] 240 | name = "dtor" 241 | version = "0.0.6" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" 244 | dependencies = [ 245 | "dtor-proc-macro", 246 | ] 247 | 248 | [[package]] 249 | name = "dtor-proc-macro" 250 | version = "0.0.5" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" 253 | 254 | [[package]] 255 | name = "encode_unicode" 256 | version = "1.0.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 259 | 260 | [[package]] 261 | name = "equivalent" 262 | version = "1.0.1" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 265 | 266 | [[package]] 267 | name = "erased-serde" 268 | version = "0.4.6" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" 271 | dependencies = [ 272 | "serde", 273 | "typeid", 274 | ] 275 | 276 | [[package]] 277 | name = "errno" 278 | version = "0.3.12" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" 281 | dependencies = [ 282 | "libc", 283 | "windows-sys", 284 | ] 285 | 286 | [[package]] 287 | name = "fastrand" 288 | version = "2.3.0" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 291 | 292 | [[package]] 293 | name = "form_urlencoded" 294 | version = "1.2.1" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 297 | dependencies = [ 298 | "percent-encoding", 299 | ] 300 | 301 | [[package]] 302 | name = "getrandom" 303 | version = "0.3.3" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 306 | dependencies = [ 307 | "cfg-if", 308 | "libc", 309 | "r-efi", 310 | "wasi", 311 | ] 312 | 313 | [[package]] 314 | name = "hashbrown" 315 | version = "0.15.1" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" 318 | 319 | [[package]] 320 | name = "home" 321 | version = "0.5.11" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 324 | dependencies = [ 325 | "windows-sys", 326 | ] 327 | 328 | [[package]] 329 | name = "icu_collections" 330 | version = "2.0.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 333 | dependencies = [ 334 | "displaydoc", 335 | "potential_utf", 336 | "yoke", 337 | "zerofrom", 338 | "zerovec", 339 | ] 340 | 341 | [[package]] 342 | name = "icu_locale_core" 343 | version = "2.0.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 346 | dependencies = [ 347 | "displaydoc", 348 | "litemap", 349 | "tinystr", 350 | "writeable", 351 | "zerovec", 352 | ] 353 | 354 | [[package]] 355 | name = "icu_normalizer" 356 | version = "2.0.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 359 | dependencies = [ 360 | "displaydoc", 361 | "icu_collections", 362 | "icu_normalizer_data", 363 | "icu_properties", 364 | "icu_provider", 365 | "smallvec", 366 | "zerovec", 367 | ] 368 | 369 | [[package]] 370 | name = "icu_normalizer_data" 371 | version = "2.0.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 374 | 375 | [[package]] 376 | name = "icu_properties" 377 | version = "2.0.1" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 380 | dependencies = [ 381 | "displaydoc", 382 | "icu_collections", 383 | "icu_locale_core", 384 | "icu_properties_data", 385 | "icu_provider", 386 | "potential_utf", 387 | "zerotrie", 388 | "zerovec", 389 | ] 390 | 391 | [[package]] 392 | name = "icu_properties_data" 393 | version = "2.0.1" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 396 | 397 | [[package]] 398 | name = "icu_provider" 399 | version = "2.0.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 402 | dependencies = [ 403 | "displaydoc", 404 | "icu_locale_core", 405 | "stable_deref_trait", 406 | "tinystr", 407 | "writeable", 408 | "yoke", 409 | "zerofrom", 410 | "zerotrie", 411 | "zerovec", 412 | ] 413 | 414 | [[package]] 415 | name = "idna" 416 | version = "1.0.3" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 419 | dependencies = [ 420 | "idna_adapter", 421 | "smallvec", 422 | "utf8_iter", 423 | ] 424 | 425 | [[package]] 426 | name = "idna_adapter" 427 | version = "1.2.1" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 430 | dependencies = [ 431 | "icu_normalizer", 432 | "icu_properties", 433 | ] 434 | 435 | [[package]] 436 | name = "indexmap" 437 | version = "2.6.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 440 | dependencies = [ 441 | "equivalent", 442 | "hashbrown", 443 | ] 444 | 445 | [[package]] 446 | name = "is_terminal_polyfill" 447 | version = "1.70.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 450 | 451 | [[package]] 452 | name = "itoa" 453 | version = "1.0.13" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" 456 | 457 | [[package]] 458 | name = "libc" 459 | version = "0.2.172" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 462 | 463 | [[package]] 464 | name = "linux-raw-sys" 465 | version = "0.9.4" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 468 | 469 | [[package]] 470 | name = "litemap" 471 | version = "0.8.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 474 | 475 | [[package]] 476 | name = "memchr" 477 | version = "2.7.4" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 480 | 481 | [[package]] 482 | name = "normalize-line-endings" 483 | version = "0.3.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 486 | 487 | [[package]] 488 | name = "num-traits" 489 | version = "0.2.19" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 492 | dependencies = [ 493 | "autocfg", 494 | ] 495 | 496 | [[package]] 497 | name = "once_cell" 498 | version = "1.21.3" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 501 | 502 | [[package]] 503 | name = "ordered-float" 504 | version = "2.10.1" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" 507 | dependencies = [ 508 | "num-traits", 509 | ] 510 | 511 | [[package]] 512 | name = "percent-encoding" 513 | version = "2.3.1" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 516 | 517 | [[package]] 518 | name = "potential_utf" 519 | version = "0.1.2" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 522 | dependencies = [ 523 | "zerovec", 524 | ] 525 | 526 | [[package]] 527 | name = "predicates" 528 | version = "3.1.2" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" 531 | dependencies = [ 532 | "anstyle", 533 | "difflib", 534 | "predicates-core", 535 | ] 536 | 537 | [[package]] 538 | name = "predicates-core" 539 | version = "1.0.8" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" 542 | 543 | [[package]] 544 | name = "predicates-tree" 545 | version = "1.0.11" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" 548 | dependencies = [ 549 | "predicates-core", 550 | "termtree", 551 | ] 552 | 553 | [[package]] 554 | name = "proc-macro2" 555 | version = "1.0.92" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 558 | dependencies = [ 559 | "unicode-ident", 560 | ] 561 | 562 | [[package]] 563 | name = "quote" 564 | version = "1.0.37" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 567 | dependencies = [ 568 | "proc-macro2", 569 | ] 570 | 571 | [[package]] 572 | name = "r-efi" 573 | version = "5.2.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 576 | 577 | [[package]] 578 | name = "regex" 579 | version = "1.11.1" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 582 | dependencies = [ 583 | "aho-corasick", 584 | "memchr", 585 | "regex-automata", 586 | "regex-syntax", 587 | ] 588 | 589 | [[package]] 590 | name = "regex-automata" 591 | version = "0.4.9" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 594 | dependencies = [ 595 | "aho-corasick", 596 | "memchr", 597 | "regex-syntax", 598 | ] 599 | 600 | [[package]] 601 | name = "regex-syntax" 602 | version = "0.8.5" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 605 | 606 | [[package]] 607 | name = "rustix" 608 | version = "1.0.7" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" 611 | dependencies = [ 612 | "bitflags", 613 | "errno", 614 | "libc", 615 | "linux-raw-sys", 616 | "windows-sys", 617 | ] 618 | 619 | [[package]] 620 | name = "ryu" 621 | version = "1.0.18" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 624 | 625 | [[package]] 626 | name = "semver" 627 | version = "1.0.23" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 630 | dependencies = [ 631 | "serde", 632 | ] 633 | 634 | [[package]] 635 | name = "serde" 636 | version = "1.0.219" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 639 | dependencies = [ 640 | "serde_derive", 641 | ] 642 | 643 | [[package]] 644 | name = "serde-untagged" 645 | version = "0.1.7" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" 648 | dependencies = [ 649 | "erased-serde", 650 | "serde", 651 | "typeid", 652 | ] 653 | 654 | [[package]] 655 | name = "serde-value" 656 | version = "0.7.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" 659 | dependencies = [ 660 | "ordered-float", 661 | "serde", 662 | ] 663 | 664 | [[package]] 665 | name = "serde_derive" 666 | version = "1.0.219" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 669 | dependencies = [ 670 | "proc-macro2", 671 | "quote", 672 | "syn", 673 | ] 674 | 675 | [[package]] 676 | name = "serde_json" 677 | version = "1.0.140" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 680 | dependencies = [ 681 | "itoa", 682 | "memchr", 683 | "ryu", 684 | "serde", 685 | ] 686 | 687 | [[package]] 688 | name = "serde_spanned" 689 | version = "0.6.9" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" 692 | dependencies = [ 693 | "serde", 694 | ] 695 | 696 | [[package]] 697 | name = "similar" 698 | version = "2.6.0" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" 701 | dependencies = [ 702 | "bstr", 703 | "unicode-segmentation", 704 | ] 705 | 706 | [[package]] 707 | name = "similar-asserts" 708 | version = "1.7.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" 711 | dependencies = [ 712 | "console", 713 | "similar", 714 | ] 715 | 716 | [[package]] 717 | name = "smallvec" 718 | version = "1.15.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 721 | 722 | [[package]] 723 | name = "snapbox" 724 | version = "0.6.21" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b" 727 | dependencies = [ 728 | "anstream", 729 | "anstyle", 730 | "normalize-line-endings", 731 | "similar", 732 | "snapbox-macros", 733 | ] 734 | 735 | [[package]] 736 | name = "snapbox-macros" 737 | version = "0.3.10" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" 740 | dependencies = [ 741 | "anstream", 742 | ] 743 | 744 | [[package]] 745 | name = "stable_deref_trait" 746 | version = "1.2.0" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 749 | 750 | [[package]] 751 | name = "syn" 752 | version = "2.0.89" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" 755 | dependencies = [ 756 | "proc-macro2", 757 | "quote", 758 | "unicode-ident", 759 | ] 760 | 761 | [[package]] 762 | name = "synstructure" 763 | version = "0.13.2" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 766 | dependencies = [ 767 | "proc-macro2", 768 | "quote", 769 | "syn", 770 | ] 771 | 772 | [[package]] 773 | name = "tempfile" 774 | version = "3.20.0" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" 777 | dependencies = [ 778 | "fastrand", 779 | "getrandom", 780 | "once_cell", 781 | "rustix", 782 | "windows-sys", 783 | ] 784 | 785 | [[package]] 786 | name = "termtree" 787 | version = "0.4.1" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 790 | 791 | [[package]] 792 | name = "thiserror" 793 | version = "1.0.69" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 796 | dependencies = [ 797 | "thiserror-impl 1.0.69", 798 | ] 799 | 800 | [[package]] 801 | name = "thiserror" 802 | version = "2.0.6" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" 805 | dependencies = [ 806 | "thiserror-impl 2.0.6", 807 | ] 808 | 809 | [[package]] 810 | name = "thiserror-impl" 811 | version = "1.0.69" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 814 | dependencies = [ 815 | "proc-macro2", 816 | "quote", 817 | "syn", 818 | ] 819 | 820 | [[package]] 821 | name = "thiserror-impl" 822 | version = "2.0.6" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" 825 | dependencies = [ 826 | "proc-macro2", 827 | "quote", 828 | "syn", 829 | ] 830 | 831 | [[package]] 832 | name = "tinystr" 833 | version = "0.8.1" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 836 | dependencies = [ 837 | "displaydoc", 838 | "zerovec", 839 | ] 840 | 841 | [[package]] 842 | name = "toml" 843 | version = "0.8.23" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 846 | dependencies = [ 847 | "serde", 848 | "serde_spanned", 849 | "toml_datetime", 850 | "toml_edit", 851 | ] 852 | 853 | [[package]] 854 | name = "toml_datetime" 855 | version = "0.6.11" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" 858 | dependencies = [ 859 | "serde", 860 | ] 861 | 862 | [[package]] 863 | name = "toml_edit" 864 | version = "0.22.27" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" 867 | dependencies = [ 868 | "indexmap", 869 | "serde", 870 | "serde_spanned", 871 | "toml_datetime", 872 | "toml_write", 873 | "winnow", 874 | ] 875 | 876 | [[package]] 877 | name = "toml_write" 878 | version = "0.1.2" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" 881 | 882 | [[package]] 883 | name = "typeid" 884 | version = "1.0.3" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" 887 | 888 | [[package]] 889 | name = "unicode-ident" 890 | version = "1.0.14" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 893 | 894 | [[package]] 895 | name = "unicode-segmentation" 896 | version = "1.12.0" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 899 | 900 | [[package]] 901 | name = "unicode-xid" 902 | version = "0.2.6" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 905 | 906 | [[package]] 907 | name = "url" 908 | version = "2.5.4" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 911 | dependencies = [ 912 | "form_urlencoded", 913 | "idna", 914 | "percent-encoding", 915 | ] 916 | 917 | [[package]] 918 | name = "utf8_iter" 919 | version = "1.0.4" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 922 | 923 | [[package]] 924 | name = "utf8parse" 925 | version = "0.2.2" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 928 | 929 | [[package]] 930 | name = "wait-timeout" 931 | version = "0.2.0" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 934 | dependencies = [ 935 | "libc", 936 | ] 937 | 938 | [[package]] 939 | name = "wasi" 940 | version = "0.14.2+wasi-0.2.4" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 943 | dependencies = [ 944 | "wit-bindgen-rt", 945 | ] 946 | 947 | [[package]] 948 | name = "windows-sys" 949 | version = "0.59.0" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 952 | dependencies = [ 953 | "windows-targets", 954 | ] 955 | 956 | [[package]] 957 | name = "windows-targets" 958 | version = "0.52.6" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 961 | dependencies = [ 962 | "windows_aarch64_gnullvm", 963 | "windows_aarch64_msvc", 964 | "windows_i686_gnu", 965 | "windows_i686_gnullvm", 966 | "windows_i686_msvc", 967 | "windows_x86_64_gnu", 968 | "windows_x86_64_gnullvm", 969 | "windows_x86_64_msvc", 970 | ] 971 | 972 | [[package]] 973 | name = "windows_aarch64_gnullvm" 974 | version = "0.52.6" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 977 | 978 | [[package]] 979 | name = "windows_aarch64_msvc" 980 | version = "0.52.6" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 983 | 984 | [[package]] 985 | name = "windows_i686_gnu" 986 | version = "0.52.6" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 989 | 990 | [[package]] 991 | name = "windows_i686_gnullvm" 992 | version = "0.52.6" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 995 | 996 | [[package]] 997 | name = "windows_i686_msvc" 998 | version = "0.52.6" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1001 | 1002 | [[package]] 1003 | name = "windows_x86_64_gnu" 1004 | version = "0.52.6" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1007 | 1008 | [[package]] 1009 | name = "windows_x86_64_gnullvm" 1010 | version = "0.52.6" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1013 | 1014 | [[package]] 1015 | name = "windows_x86_64_msvc" 1016 | version = "0.52.6" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1019 | 1020 | [[package]] 1021 | name = "winnow" 1022 | version = "0.7.10" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" 1025 | dependencies = [ 1026 | "memchr", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "wit-bindgen-rt" 1031 | version = "0.39.0" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1034 | dependencies = [ 1035 | "bitflags", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "writeable" 1040 | version = "0.6.1" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 1043 | 1044 | [[package]] 1045 | name = "xdg" 1046 | version = "3.0.0" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5" 1049 | 1050 | [[package]] 1051 | name = "yoke" 1052 | version = "0.8.0" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 1055 | dependencies = [ 1056 | "serde", 1057 | "stable_deref_trait", 1058 | "yoke-derive", 1059 | "zerofrom", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "yoke-derive" 1064 | version = "0.8.0" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 1067 | dependencies = [ 1068 | "proc-macro2", 1069 | "quote", 1070 | "syn", 1071 | "synstructure", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "zerofrom" 1076 | version = "0.1.6" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 1079 | dependencies = [ 1080 | "zerofrom-derive", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "zerofrom-derive" 1085 | version = "0.1.6" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 1088 | dependencies = [ 1089 | "proc-macro2", 1090 | "quote", 1091 | "syn", 1092 | "synstructure", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "zerotrie" 1097 | version = "0.2.2" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 1100 | dependencies = [ 1101 | "displaydoc", 1102 | "yoke", 1103 | "zerofrom", 1104 | ] 1105 | 1106 | [[package]] 1107 | name = "zerovec" 1108 | version = "0.11.2" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 1111 | dependencies = [ 1112 | "yoke", 1113 | "zerofrom", 1114 | "zerovec-derive", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "zerovec-derive" 1119 | version = "0.11.1" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 1122 | dependencies = [ 1123 | "proc-macro2", 1124 | "quote", 1125 | "syn", 1126 | ] 1127 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "build-wrap" 3 | version = "0.5.1" 4 | authors = ["Samuel Moelius "] 5 | description = "Help protect against malicious build scripts" 6 | edition = "2021" 7 | license = "AGPL-3.0" 8 | repository = "https://github.com/trailofbits/build-wrap" 9 | 10 | # smoelius: This list of dependencies should match what is in src/wrapper.rs. 11 | [dependencies] 12 | anyhow = "1.0" 13 | home = "0.5" 14 | regex = "1.11" 15 | tempfile = "3.20" 16 | toml = "0.8" 17 | xdg = "3.0" 18 | 19 | [dev-dependencies] 20 | assert_cmd = "2.0" 21 | cargo_metadata = "0.20" 22 | ctor = "0.4" 23 | serde = "1.0" 24 | serde_json = "1.0" 25 | similar-asserts = "1.7" 26 | snapbox = "0.6" 27 | 28 | [lints.clippy] 29 | pedantic = { level = "warn", priority = -1 } 30 | let_underscore_untyped = "warn" 31 | missing_errors_doc = "allow" 32 | missing_panics_doc = "allow" 33 | 34 | [workspace] 35 | exclude = ["fixtures", "target"] 36 | 37 | [workspace.metadata.dylint] 38 | libraries = [ 39 | { git = "https://github.com/trailofbits/dylint", pattern = "examples/restriction/inconsistent_qualification" }, 40 | { git = "https://github.com/trailofbits/dylint", pattern = "examples/restriction/misleading_variable_name" }, 41 | ] 42 | 43 | [workspace.metadata.unmaintained] 44 | ignore = [ 45 | # https://github.com/alacritty/vte/pull/122#issuecomment-2579278540 46 | "utf8parse", 47 | "wit-bindgen-rt", 48 | ] 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # build-wrap 2 | 3 | A linker replacement to help protect against malicious build scripts 4 | 5 | `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 that `build-wrap` reads] and [How `build-wrap` works] for more information. 6 | 7 | ## Installation 8 | 9 | Installing `build-wrap` requires two steps: 10 | 11 | 1. Install `build-wrap` with Cargo: 12 | ```sh 13 | cargo install build-wrap 14 | ``` 15 | 2. Create a `.cargo/config.toml` file in your home directory with the following contents: 16 | ```toml 17 | [target.'cfg(all())'] 18 | linker = "build-wrap" 19 | ``` 20 | 21 | ### Ubuntu 24.04 22 | 23 | Ubuntu's default AppArmor profiles [changed with version 24.04]. The changes [affect Bubblewrap], which in turn affect `build-wrap`. Thus, installing `build-wrap` on Ubuntu 24.04 requires some additional steps: 24 | 25 | ```sh 26 | sudo apt install apparmor-profiles 27 | sudo cp /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d 28 | sudo systemctl reload apparmor 29 | ``` 30 | 31 | Note that following these additional steps, Bubblewrap still runs unprivileged. More information on AppArmor profiles can be found on [Ubuntu Server] and the [Ubuntu Community Wiki]. 32 | 33 | ## Environment variables that `build-wrap` reads 34 | 35 | Note that the below environment variables are read **when a build script is linked**. So, for example, changing `BUILD_WRAP_CMD` will not change the command used to execute already linked build scripts. 36 | 37 | - `BUILD_WRAP_ALLOW`: When set to a value other than `0`, `build-wrap` uses the following weakened strategy. If running a build script under `BUILD_WRAP_CMD` fails, report the failure and rerun the build script normally. 38 | 39 | Note that to see the reported failures, you must invoke Cargo with the `-vv` (["very verbose"]) flag, e.g.: 40 | 41 | ```sh 42 | BUILD_WRAP_ALLOW=1 cargo build -vv 43 | ``` 44 | 45 | If a package must always be built with this strategy, put the package's name in [`$HOME/.config/build-wrap/allow.txt`] (see below). 46 | 47 | - `BUILD_WRAP_CMD`: Command used to execute a build script. Linux default: 48 | 49 | - With comments: 50 | 51 | ```sh 52 | bwrap 53 | --ro-bind / / # Allow read-only access everywhere 54 | --dev-bind /dev /dev # Allow device access 55 | --bind {OUT_DIR} {OUT_DIR} # Allow write access to `OUT_DIR` 56 | --bind /tmp /tmp # Allow write access to /tmp 57 | --unshare-net # Deny network access 58 | {} # Build script path 59 | ``` 60 | 61 | - On one line (for copying-and-pasting): 62 | 63 | ```sh 64 | bwrap --ro-bind / / --dev-bind /dev /dev --bind {OUT_DIR} {OUT_DIR} --bind /tmp /tmp --unshare-net {} 65 | ``` 66 | 67 | Note that `bwrap` is [Bubblewrap]. 68 | 69 | macOS default: 70 | 71 | ```sh 72 | sandbox-exec -f {BUILD_WRAP_PROFILE_PATH} {} 73 | ``` 74 | 75 | See [Environment variables that `build-wrap` treats as set] regarding `BUILD_WRAP_PROFILE_PATH`. 76 | 77 | - `BUILD_WRAP_LD`: Linker to use. Default: `cc` 78 | 79 | - `BUILD_WRAP_PROFILE`: macOS only. `build-wrap` expands `BUILD_WRAP_PROFILE` [as it would `BUILD_WRAP_CMD`], and writes the results to a temporary file. `BUILD_WRAP_PROFILE_PATH` then expands to the absolute path of that temporary file. Default: 80 | 81 | ``` 82 | (version 1) 83 | (deny default) 84 | (allow file-read*) ;; Allow read-only access everywhere 85 | (allow file-write* (subpath "/dev")) ;; Allow write access to /dev 86 | (allow file-write* (subpath "{OUT_DIR}")) ;; Allow write access to `OUT_DIR` 87 | (allow file-write* (subpath "{TMPDIR}")) ;; Allow write access to `TMPDIR` 88 | (allow file-write* (subpath "{PRIVATE_TMPDIR}")) ;; Allow write access to `PRIVATE_TMPDIR` (see below) 89 | (allow process-exec) ;; Allow `exec` 90 | (allow process-fork) ;; Allow `fork` 91 | (allow sysctl-read) ;; Allow reading kernel state 92 | (deny network*) ;; Deny network access 93 | ``` 94 | 95 | ## `$HOME/.config/build-wrap/allow.txt` 96 | 97 | If a file at `$HOME/.config/build-wrap/allow.txt` exists, `build-wrap` treats each line as the name of a package. Such packages are built as though `BUILD_WRAP_ALLOW` were set to `1`. 98 | 99 | For example, [`svm-rs-builds`] downloads information about Solc releases when it is built. So if you build [`svm-rs`] frequently, you might do the following: 100 | 101 | ```sh 102 | mkdir -p "$HOME/.config/build-wrap" 103 | echo 'svm-rs-builds' > "$HOME/.config/build-wrap/allow.txt" 104 | ``` 105 | 106 | ## Environment variables that `build-wrap` treats as set 107 | 108 | Note that we say "treats as set" because these are considered only when [`BUILD_WRAP_CMD` is expanded]. 109 | 110 | - `BUILD_WRAP_PROFILE_PATH`: Expands to the absolute path of a temporary file containing the expanded contents of `BUILD_WRAP_PROFILE`. 111 | 112 | - `PRIVATE_TMPDIR`: If `TMPDIR` is set to a path in `/private` (as is typical on macOS), then `PRIVATE_TMPDIR` expands to that path. This is needed for some build scripts that use [`cc-rs`], though the exact reason it is needed is still unknown. 113 | 114 | ## How `BUILD_WRAP_CMD` is expanded 115 | 116 | - `{}` is replaced with the path of a renamed copy of the original build script. 117 | - `{VAR}` is replaced with the value of environment variable `VAR`. 118 | - `{{` is replaced with `{`. 119 | - `}}` is replaced with `}`. 120 | - `\` followed by a whitespace character is replaced with that whitespace character. 121 | - `\\` is replaced with `\`. 122 | 123 | ## How `build-wrap` works 124 | 125 | When invoked, `build-wrap` does the following: 126 | 127 | 1. Link normally using `BUILD_WRAP_LD`. 128 | 2. Parse the arguments to determine whether the output file is a build script. 129 | 3. If not, stop; otherwise, proceed. 130 | 4. Let `B` be the build script's original name. 131 | 5. Rename the build script to a fresh, unused name `B'`. 132 | 6. At `B`, create a "wrapped" version of the build script whose behavior is described next. 133 | 134 | The "wrapped" version of the build script does the following when invoked: 135 | 136 | 1. Expand `BUILD_WRAP_CMD` in the [manner described above], with `{}` expanding to `B'`. 137 | 2. Execute the expanded command. 138 | 139 | ## Goals 140 | 141 | - Aside from configuration and dealing with an occasional warning, `build-wrap` should not require a user to adjust their normal workflow. 142 | 143 | ["very verbose"]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script 144 | [Bubblewrap]: https://github.com/containers/bubblewrap 145 | [Environment variables that `build-wrap` reads]: #environment-variables-that-build-wrap-reads 146 | [Environment variables that `build-wrap` treats as set]: #environment-variables-that-build-wrap-treats-as-set 147 | [How `build-wrap` works]: #how-build-wrap-works 148 | [Ubuntu Community Wiki]: https://help.ubuntu.com/community/AppArmor 149 | [Ubuntu Server]: https://documentation.ubuntu.com/server/how-to/security/apparmor/ 150 | [`$HOME/.config/build-wrap/allow.txt`]: #homeconfigbuild-wrapallowtxt 151 | [`BUILD_WRAP_CMD` is expanded]: #how-build_wrap_cmd-is-expanded 152 | [`cc-rs`]: https://github.com/rust-lang/cc-rs 153 | [`sandbox-exec`]: https://keith.github.io/xcode-man-pages/sandbox-exec.1.html 154 | [`svm-rs-builds`]: https://github.com/alloy-rs/svm-rs/tree/master/crates/svm-builds 155 | [`svm-rs`]: https://github.com/alloy-rs/svm-rs 156 | [affect Bubblewrap]: https://github.com/containers/bubblewrap/issues/505#issuecomment-2093203129 157 | [as it would `BUILD_WRAP_CMD`]: #how-build_wrap_cmd-is-expanded 158 | [changed with version 24.04]: https://ubuntu.com/blog/ubuntu-23-10-restricted-unprivileged-user-namespaces 159 | [manner described above]: #how-build_wrap_cmd-is-expanded 160 | -------------------------------------------------------------------------------- /assets/README.txt: -------------------------------------------------------------------------------- 1 | The file sandboxer.c, in the same directory as this README, is from the following location: 2 | 3 | https://raw.githubusercontent.com/torvalds/linux/refs/tags/v6.8/samples/landlock/sandboxer.c 4 | 5 | The file is used to build the `sandboxer` executable, which is used by some tests. 6 | -------------------------------------------------------------------------------- /assets/sandboxer.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | /* 3 | * Simple Landlock sandbox manager able to launch a process restricted by a 4 | * user-defined filesystem access control policy. 5 | * 6 | * Copyright © 2017-2020 Mickaël Salaün 7 | * Copyright © 2020 ANSSI 8 | */ 9 | 10 | #define _GNU_SOURCE 11 | #define __SANE_USERSPACE_TYPES__ 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef landlock_create_ruleset 27 | static inline int 28 | landlock_create_ruleset(const struct landlock_ruleset_attr *const attr, 29 | const size_t size, const __u32 flags) 30 | { 31 | return syscall(__NR_landlock_create_ruleset, attr, size, flags); 32 | } 33 | #endif 34 | 35 | #ifndef landlock_add_rule 36 | static inline int landlock_add_rule(const int ruleset_fd, 37 | const enum landlock_rule_type rule_type, 38 | const void *const rule_attr, 39 | const __u32 flags) 40 | { 41 | return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, 42 | flags); 43 | } 44 | #endif 45 | 46 | #ifndef landlock_restrict_self 47 | static inline int landlock_restrict_self(const int ruleset_fd, 48 | const __u32 flags) 49 | { 50 | return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); 51 | } 52 | #endif 53 | 54 | #define ENV_FS_RO_NAME "LL_FS_RO" 55 | #define ENV_FS_RW_NAME "LL_FS_RW" 56 | #define ENV_TCP_BIND_NAME "LL_TCP_BIND" 57 | #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" 58 | #define ENV_DELIMITER ":" 59 | 60 | static int parse_path(char *env_path, const char ***const path_list) 61 | { 62 | int i, num_paths = 0; 63 | 64 | if (env_path) 65 | { 66 | num_paths++; 67 | for (i = 0; env_path[i]; i++) 68 | { 69 | if (env_path[i] == ENV_DELIMITER[0]) 70 | num_paths++; 71 | } 72 | } 73 | *path_list = malloc(num_paths * sizeof(**path_list)); 74 | for (i = 0; i < num_paths; i++) 75 | (*path_list)[i] = strsep(&env_path, ENV_DELIMITER); 76 | 77 | return num_paths; 78 | } 79 | 80 | /* clang-format off */ 81 | 82 | #define ACCESS_FILE ( \ 83 | LANDLOCK_ACCESS_FS_EXECUTE | \ 84 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ 85 | LANDLOCK_ACCESS_FS_READ_FILE | \ 86 | LANDLOCK_ACCESS_FS_TRUNCATE) 87 | 88 | /* clang-format on */ 89 | 90 | static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd, 91 | const __u64 allowed_access) 92 | { 93 | int num_paths, i, ret = 1; 94 | char *env_path_name; 95 | const char **path_list = NULL; 96 | struct landlock_path_beneath_attr path_beneath = { 97 | .parent_fd = -1, 98 | }; 99 | 100 | env_path_name = getenv(env_var); 101 | if (!env_path_name) 102 | { 103 | /* Prevents users to forget a setting. */ 104 | fprintf(stderr, "Missing environment variable %s\n", env_var); 105 | return 1; 106 | } 107 | env_path_name = strdup(env_path_name); 108 | unsetenv(env_var); 109 | num_paths = parse_path(env_path_name, &path_list); 110 | if (num_paths == 1 && path_list[0][0] == '\0') 111 | { 112 | /* 113 | * Allows to not use all possible restrictions (e.g. use 114 | * LL_FS_RO without LL_FS_RW). 115 | */ 116 | ret = 0; 117 | goto out_free_name; 118 | } 119 | 120 | for (i = 0; i < num_paths; i++) 121 | { 122 | struct stat statbuf; 123 | 124 | path_beneath.parent_fd = open(path_list[i], O_PATH | O_CLOEXEC); 125 | if (path_beneath.parent_fd < 0) 126 | { 127 | fprintf(stderr, "Failed to open \"%s\": %s\n", 128 | path_list[i], strerror(errno)); 129 | goto out_free_name; 130 | } 131 | if (fstat(path_beneath.parent_fd, &statbuf)) 132 | { 133 | close(path_beneath.parent_fd); 134 | goto out_free_name; 135 | } 136 | path_beneath.allowed_access = allowed_access; 137 | if (!S_ISDIR(statbuf.st_mode)) 138 | path_beneath.allowed_access &= ACCESS_FILE; 139 | if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, 140 | &path_beneath, 0)) 141 | { 142 | fprintf(stderr, 143 | "Failed to update the ruleset with \"%s\": %s\n", 144 | path_list[i], strerror(errno)); 145 | close(path_beneath.parent_fd); 146 | goto out_free_name; 147 | } 148 | close(path_beneath.parent_fd); 149 | } 150 | ret = 0; 151 | 152 | out_free_name: 153 | free(path_list); 154 | free(env_path_name); 155 | return ret; 156 | } 157 | 158 | static int populate_ruleset_net(const char *const env_var, const int ruleset_fd, 159 | const __u64 allowed_access) 160 | { 161 | int ret = 1; 162 | char *env_port_name, *strport; 163 | struct landlock_net_port_attr net_port = { 164 | .allowed_access = allowed_access, 165 | .port = 0, 166 | }; 167 | 168 | env_port_name = getenv(env_var); 169 | if (!env_port_name) 170 | return 0; 171 | env_port_name = strdup(env_port_name); 172 | unsetenv(env_var); 173 | 174 | while ((strport = strsep(&env_port_name, ENV_DELIMITER))) 175 | { 176 | net_port.port = atoi(strport); 177 | if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, 178 | &net_port, 0)) 179 | { 180 | fprintf(stderr, 181 | "Failed to update the ruleset with port \"%llu\": %s\n", 182 | net_port.port, strerror(errno)); 183 | goto out_free_name; 184 | } 185 | } 186 | ret = 0; 187 | 188 | out_free_name: 189 | free(env_port_name); 190 | return ret; 191 | } 192 | 193 | /* clang-format off */ 194 | 195 | #define ACCESS_FS_ROUGHLY_READ ( \ 196 | LANDLOCK_ACCESS_FS_EXECUTE | \ 197 | LANDLOCK_ACCESS_FS_READ_FILE | \ 198 | LANDLOCK_ACCESS_FS_READ_DIR) 199 | 200 | #define ACCESS_FS_ROUGHLY_WRITE ( \ 201 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ 202 | LANDLOCK_ACCESS_FS_REMOVE_DIR | \ 203 | LANDLOCK_ACCESS_FS_REMOVE_FILE | \ 204 | LANDLOCK_ACCESS_FS_MAKE_CHAR | \ 205 | LANDLOCK_ACCESS_FS_MAKE_DIR | \ 206 | LANDLOCK_ACCESS_FS_MAKE_REG | \ 207 | LANDLOCK_ACCESS_FS_MAKE_SOCK | \ 208 | LANDLOCK_ACCESS_FS_MAKE_FIFO | \ 209 | LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ 210 | LANDLOCK_ACCESS_FS_MAKE_SYM | \ 211 | LANDLOCK_ACCESS_FS_REFER | \ 212 | LANDLOCK_ACCESS_FS_TRUNCATE) 213 | 214 | /* clang-format on */ 215 | 216 | #define LANDLOCK_ABI_LAST 4 217 | 218 | int main(const int argc, char *const argv[], char *const *const envp) 219 | { 220 | const char *cmd_path; 221 | char *const *cmd_argv; 222 | int ruleset_fd, abi; 223 | char *env_port_name; 224 | __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ, 225 | access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE; 226 | 227 | struct landlock_ruleset_attr ruleset_attr = { 228 | .handled_access_fs = access_fs_rw, 229 | .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | 230 | LANDLOCK_ACCESS_NET_CONNECT_TCP, 231 | }; 232 | 233 | if (argc < 2) 234 | { 235 | fprintf(stderr, 236 | "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s " 237 | " [args]...\n\n", 238 | ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, 239 | ENV_TCP_CONNECT_NAME, argv[0]); 240 | fprintf(stderr, 241 | "Launch a command in a restricted environment.\n\n"); 242 | fprintf(stderr, 243 | "Environment variables containing paths and ports " 244 | "each separated by a colon:\n"); 245 | fprintf(stderr, 246 | "* %s: list of paths allowed to be used in a read-only way.\n", 247 | ENV_FS_RO_NAME); 248 | fprintf(stderr, 249 | "* %s: list of paths allowed to be used in a read-write way.\n\n", 250 | ENV_FS_RW_NAME); 251 | fprintf(stderr, 252 | "Environment variables containing ports are optional " 253 | "and could be skipped.\n"); 254 | fprintf(stderr, 255 | "* %s: list of ports allowed to bind (server).\n", 256 | ENV_TCP_BIND_NAME); 257 | fprintf(stderr, 258 | "* %s: list of ports allowed to connect (client).\n", 259 | ENV_TCP_CONNECT_NAME); 260 | fprintf(stderr, 261 | "\nexample:\n" 262 | "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" " 263 | "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " 264 | "%s=\"9418\" " 265 | "%s=\"80:443\" " 266 | "%s bash -i\n\n", 267 | ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, 268 | ENV_TCP_CONNECT_NAME, argv[0]); 269 | fprintf(stderr, 270 | "This sandboxer can use Landlock features " 271 | "up to ABI version %d.\n", 272 | LANDLOCK_ABI_LAST); 273 | return 1; 274 | } 275 | 276 | abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); 277 | if (abi < 0) 278 | { 279 | const int err = errno; 280 | 281 | perror("Failed to check Landlock compatibility"); 282 | switch (err) 283 | { 284 | case ENOSYS: 285 | fprintf(stderr, 286 | "Hint: Landlock is not supported by the current kernel. " 287 | "To support it, build the kernel with " 288 | "CONFIG_SECURITY_LANDLOCK=y and prepend " 289 | "\"landlock,\" to the content of CONFIG_LSM.\n"); 290 | break; 291 | case EOPNOTSUPP: 292 | fprintf(stderr, 293 | "Hint: Landlock is currently disabled. " 294 | "It can be enabled in the kernel configuration by " 295 | "prepending \"landlock,\" to the content of CONFIG_LSM, " 296 | "or at boot time by setting the same content to the " 297 | "\"lsm\" kernel parameter.\n"); 298 | break; 299 | } 300 | return 1; 301 | } 302 | 303 | /* Best-effort security. */ 304 | switch (abi) 305 | { 306 | case 1: 307 | /* 308 | * Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2 309 | * 310 | * Note: The "refer" operations (file renaming and linking 311 | * across different directories) are always forbidden when using 312 | * Landlock with ABI 1. 313 | * 314 | * If only ABI 1 is available, this sandboxer knowingly forbids 315 | * refer operations. 316 | * 317 | * If a program *needs* to do refer operations after enabling 318 | * Landlock, it can not use Landlock at ABI level 1. To be 319 | * compatible with different kernel versions, such programs 320 | * should then fall back to not restrict themselves at all if 321 | * the running kernel only supports ABI 1. 322 | */ 323 | ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; 324 | __attribute__((fallthrough)); 325 | case 2: 326 | /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */ 327 | ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; 328 | __attribute__((fallthrough)); 329 | case 3: 330 | /* Removes network support for ABI < 4 */ 331 | ruleset_attr.handled_access_net &= 332 | ~(LANDLOCK_ACCESS_NET_BIND_TCP | 333 | LANDLOCK_ACCESS_NET_CONNECT_TCP); 334 | fprintf(stderr, 335 | "Hint: You should update the running kernel " 336 | "to leverage Landlock features " 337 | "provided by ABI version %d (instead of %d).\n", 338 | LANDLOCK_ABI_LAST, abi); 339 | __attribute__((fallthrough)); 340 | case LANDLOCK_ABI_LAST: 341 | break; 342 | default: 343 | fprintf(stderr, 344 | "Hint: You should update this sandboxer " 345 | "to leverage Landlock features " 346 | "provided by ABI version %d (instead of %d).\n", 347 | abi, LANDLOCK_ABI_LAST); 348 | } 349 | access_fs_ro &= ruleset_attr.handled_access_fs; 350 | access_fs_rw &= ruleset_attr.handled_access_fs; 351 | 352 | /* Removes bind access attribute if not supported by a user. */ 353 | env_port_name = getenv(ENV_TCP_BIND_NAME); 354 | if (!env_port_name) 355 | { 356 | ruleset_attr.handled_access_net &= 357 | ~LANDLOCK_ACCESS_NET_BIND_TCP; 358 | } 359 | /* Removes connect access attribute if not supported by a user. */ 360 | env_port_name = getenv(ENV_TCP_CONNECT_NAME); 361 | if (!env_port_name) 362 | { 363 | ruleset_attr.handled_access_net &= 364 | ~LANDLOCK_ACCESS_NET_CONNECT_TCP; 365 | } 366 | 367 | ruleset_fd = 368 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 369 | if (ruleset_fd < 0) 370 | { 371 | perror("Failed to create a ruleset"); 372 | return 1; 373 | } 374 | 375 | if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) 376 | { 377 | goto err_close_ruleset; 378 | } 379 | if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) 380 | { 381 | goto err_close_ruleset; 382 | } 383 | 384 | if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd, 385 | LANDLOCK_ACCESS_NET_BIND_TCP)) 386 | { 387 | goto err_close_ruleset; 388 | } 389 | if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd, 390 | LANDLOCK_ACCESS_NET_CONNECT_TCP)) 391 | { 392 | goto err_close_ruleset; 393 | } 394 | 395 | if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) 396 | { 397 | perror("Failed to restrict privileges"); 398 | goto err_close_ruleset; 399 | } 400 | if (landlock_restrict_self(ruleset_fd, 0)) 401 | { 402 | perror("Failed to enforce ruleset"); 403 | goto err_close_ruleset; 404 | } 405 | close(ruleset_fd); 406 | 407 | cmd_path = argv[1]; 408 | cmd_argv = argv + 1; 409 | execvpe(cmd_path, cmd_argv, envp); 410 | fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path, 411 | strerror(errno)); 412 | fprintf(stderr, "Hint: access to the binary, the interpreter or " 413 | "shared libraries may be denied.\n"); 414 | return 1; 415 | 416 | err_close_ruleset: 417 | close(ruleset_fd); 418 | return 1; 419 | } -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::Path, process::Command}; 2 | 3 | fn main() { 4 | if cfg!(not(target_os = "linux")) { 5 | return; 6 | } 7 | let out_dir = env::var("OUT_DIR").unwrap(); 8 | let exe_path = Path::new(&out_dir).join("sandboxer"); 9 | let mut command = Command::new("cc"); 10 | command.args(["assets/sandboxer.c", "-o", &exe_path.to_string_lossy()]); 11 | let status = command.status().unwrap(); 12 | assert!(status.success()); 13 | } 14 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-methods = [ 2 | { path = "tempfile::tempdir", reason = "use `tests::util::tempdir`" }, 3 | ] 4 | -------------------------------------------------------------------------------- /fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target* 3 | -------------------------------------------------------------------------------- /fixtures/cargo_target_dir/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target-dir = "target-custom" 3 | -------------------------------------------------------------------------------- /fixtures/cargo_target_dir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo_target_dir" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | -------------------------------------------------------------------------------- /fixtures/cargo_target_dir/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!( 3 | "cargo:warning={}", 4 | std::env::current_exe().unwrap().display() 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /fixtures/cargo_target_dir/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fixtures/custom_build_name/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom_build_name" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | build = "custom_build_name.rs" 8 | -------------------------------------------------------------------------------- /fixtures/custom_build_name/custom_build_name.rs: -------------------------------------------------------------------------------- 1 | ../../tests/build_scripts/ping.rs -------------------------------------------------------------------------------- /fixtures/custom_build_name/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | comment_width = 100 2 | format_strings = true 3 | wrap_comments = true 4 | -------------------------------------------------------------------------------- /scripts/sandboxer.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # A wrapper around `sandboxer` to convert command line arguments into environment variables. 4 | # 5 | # If `sandboxer` is not accessible via `PATH`, this script tries to find one of the form: 6 | # 7 | # target/debug/build/build-wrap-*/out/sandboxer 8 | 9 | # set -x 10 | set -euo pipefail 11 | 12 | ARG0="$(basename $0)" 13 | 14 | if ! which sandboxer >/dev/null; then 15 | SCRIPTS="$(dirname "$(realpath "$0")")" 16 | 17 | pushd "$(realpath "$SCRIPTS"/..)" >/dev/null 18 | 19 | readarray -d ' ' CANDIDATES < <(echo target/debug/build/build-wrap-*/out/sandboxer) 20 | 21 | if [[ ${#CANDIDATES[@]} -ne 1 ]]; then 22 | echo "$ARG0: unexpected number of 'sandboxer' executables found: ${#CANDIDATES[@]}" >&2 23 | echo ${CANDIDATES[@]} | tr ' ' '\n' >&2 24 | echo >&2 25 | echo "Please run 'cargo clean && cargo build'." >&2 26 | exit 1 27 | fi 28 | 29 | SANDBOXER="${CANDIDATES[0]}" 30 | 31 | if [[ "$SANDBOXER" =~ \* ]]; then 32 | echo "$ARG0: failed to find 'sandboxer' executable; please run 'cargo build'" >&2 33 | exit 1 34 | fi 35 | 36 | export PATH="$PWD/$(dirname "$SANDBOXER"):$PATH" 37 | 38 | popd >/dev/null 39 | fi 40 | 41 | usage() { 42 | echo "usage: $ARG0 [--fs-ro=\"...\"] [--fs-rw=\"...\"] [--tcp-bind=\"...\"] [--tcp-connect=\"...\"] -- [args]" >&2 43 | } 44 | 45 | export LL_FS_RO= 46 | export LL_FS_RW= 47 | export LL_FS_TCP_BIND= 48 | export LL_FS_TCP_CONNECT= 49 | 50 | for ARG in "$@"; do 51 | case "$ARG" in 52 | --fs-ro=*) 53 | export LL_FS_RO="${ARG#*=}" 54 | shift 55 | ;; 56 | --fs-rw=*) 57 | export LL_FS_RW="${ARG#*=}" 58 | shift 59 | ;; 60 | --tcp-bind=*) 61 | export LL_TCP_BIND="${ARG#*=}" 62 | shift 63 | ;; 64 | --tcp-connect=*) 65 | export LL_TCP_CONNECT="${ARG#*=}" 66 | shift 67 | ;; 68 | --) 69 | shift 70 | break 71 | ;; 72 | *) 73 | echo "$0: unknown option: $ARG" >&2 74 | echo >&2 75 | usage 76 | exit 1 77 | ;; 78 | esac 79 | done 80 | 81 | if [[ "$#" -eq 0 ]]; then 82 | usage 83 | exit 84 | fi 85 | 86 | sandboxer "$@" 87 | -------------------------------------------------------------------------------- /scripts/walk_dir.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -x 4 | set -euo pipefail 5 | 6 | if [[ $# -ne 0 ]]; then 7 | echo "$0: expect no arguments" >&2 8 | exit 1 9 | fi 10 | 11 | WITH_CC="--config=target.'cfg(all())'.linker='cc'" 12 | WITH_BUILD_WRAP="--config=target.'cfg(all())'.linker='build-wrap'" 13 | 14 | DEFAULT_TOOLCHAIN="$(rustup default | grep -o '^[^ ]*')" 15 | 16 | echo -n > successes.txt 17 | echo -n > failures.txt 18 | 19 | DIR="$PWD" 20 | 21 | find . -name build.rs | 22 | while read X; do 23 | # smoelius: A previous iteration of this loop could have caused the file to go away. 24 | if [[ ! -f "$X" ]]; then 25 | continue 26 | fi 27 | 28 | Y="$(dirname "$X")" 29 | 30 | pushd "$Y" 31 | 32 | while true; do 33 | if ! (rustup which rustc | grep -w "$DEFAULT_TOOLCHAIN"); then 34 | break 35 | fi 36 | 37 | if ! cargo clean "$WITH_CC"; then 38 | break 39 | fi 40 | 41 | if ! cargo build "$WITH_CC"; then 42 | break 43 | fi 44 | 45 | cargo clean "$WITH_CC" 46 | 47 | if cargo build "$WITH_BUILD_WRAP"; then 48 | echo "$Y" >> "$DIR"/successes.txt 49 | break 50 | fi 51 | 52 | echo "$Y" >> "$DIR"/failures.txt 53 | 54 | break 55 | done 56 | 57 | popd 58 | done 59 | 60 | cat failures.txt 61 | -------------------------------------------------------------------------------- /src/linking.rs: -------------------------------------------------------------------------------- 1 | use crate::{util, wrapper, DEFAULT_CMD}; 2 | use anyhow::Result; 3 | use std::{ 4 | env::{var, var_os}, 5 | ffi::OsStr, 6 | fs::copy, 7 | path::{Path, PathBuf}, 8 | process::Command, 9 | }; 10 | 11 | pub fn link(args: &[String]) -> Result<()> { 12 | let linker = linker()?; 13 | 14 | let mut command = Command::new(&linker); 15 | command.args(&args[1..]); 16 | util::exec_forwarding_output(command, true)?; 17 | 18 | // smoelius: Don't wrap if `RUSTC_WRAPPER` or `RUSTC_WORKSPACE_WRAPPER` is set. That usually 19 | // means that Clippy or Dylint is being run. 20 | if var_os("RUSTC_WRAPPER").is_none() && var_os("RUSTC_WORKSPACE_WRAPPER").is_none() { 21 | if let Some(path) = output_path(args.iter()) { 22 | if is_build_script(&path) { 23 | wrap(&linker, &path)?; 24 | } 25 | } 26 | } 27 | 28 | Ok(()) 29 | } 30 | 31 | fn linker() -> Result { 32 | if var_os("BUILD_WRAP_LD").is_some() { 33 | var("BUILD_WRAP_LD").map_err(Into::into) 34 | } else { 35 | Ok(String::from(util::DEFAULT_LD)) 36 | } 37 | } 38 | 39 | fn output_path<'a, I>(mut iter: I) -> Option 40 | where 41 | I: Iterator, 42 | { 43 | while let Some(arg) = iter.next() { 44 | if arg == "-o" { 45 | if let Some(path) = iter.next() { 46 | return Some(path.into()); 47 | } 48 | } 49 | } 50 | 51 | None 52 | } 53 | 54 | fn is_build_script(path: &Path) -> bool { 55 | path.file_name() 56 | .and_then(OsStr::to_str) 57 | .is_some_and(|name| name.starts_with("build_script_")) 58 | } 59 | 60 | fn wrap(linker: &str, build_script_path: &Path) -> Result<()> { 61 | let wrapper_package = wrapper::package(build_script_path)?; 62 | 63 | let mut command = util::cargo_build(); 64 | if var_os("BUILD_WRAP_CMD").is_none() { 65 | command.env("BUILD_WRAP_CMD", DEFAULT_CMD); 66 | } 67 | // smoelius: When building the wrapper, do *not* use `build-wrap`. 68 | command.args([ 69 | "--config", 70 | &format!("target.'cfg(all())'.linker = '{linker}'"), 71 | ]); 72 | // smoelius: Unset `CARGO_TARGET_DIR` environment variable. 73 | command.env_remove("CARGO_TARGET_DIR"); 74 | // smoelius: `cd` into `wrapper_package`'s directory to avoid any `.cargo/config.toml` that may 75 | // be in ancestors of the current directory. 76 | command.current_dir(&wrapper_package); 77 | util::exec_forwarding_output(command, true)?; 78 | 79 | copy( 80 | wrapper_package 81 | .path() 82 | .join("target/debug/build_script_wrapper"), 83 | build_script_path, 84 | )?; 85 | 86 | Ok(()) 87 | } 88 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use regex::Regex; 3 | use std::{ 4 | collections::BTreeMap, 5 | env::{args, current_exe}, 6 | fs::read_to_string, 7 | io::{stdout, IsTerminal}, 8 | path::Path, 9 | str::FromStr, 10 | sync::LazyLock, 11 | }; 12 | 13 | mod linking; 14 | mod util; 15 | mod wrapper; 16 | 17 | const LINUX_DEFAULT_CMD: &str = "bwrap 18 | --ro-bind / / 19 | --dev-bind /dev /dev 20 | --bind {OUT_DIR} {OUT_DIR} 21 | --bind /tmp /tmp 22 | --unshare-net 23 | {}"; 24 | 25 | // smoelius: The following blog post is a useful `sandbox-exec` reference: 26 | // https://7402.org/blog/2020/macos-sandboxing-of-folder.html 27 | const MACOS_DEFAULT_CMD: &str = "sandbox-exec -f {BUILD_WRAP_PROFILE_PATH} {}"; 28 | 29 | const DEFAULT_CMD: &str = if cfg!(target_os = "linux") { 30 | LINUX_DEFAULT_CMD 31 | } else { 32 | MACOS_DEFAULT_CMD 33 | }; 34 | 35 | fn main() -> Result<()> { 36 | let args: Vec = args().collect(); 37 | 38 | run(&args) 39 | } 40 | 41 | fn run(args: &[String]) -> Result<()> { 42 | if args[1..] 43 | .iter() 44 | .all(|arg| matches!(arg.as_str(), "-h" | "--help")) 45 | { 46 | help(); 47 | return Ok(()); 48 | } 49 | 50 | linking::link(args) 51 | } 52 | 53 | static ENABLED: LazyLock<&str> = LazyLock::new(|| { 54 | if stdout().is_terminal() { 55 | "\x1b[1;32mENABLED\x1b[0m" 56 | } else { 57 | "ENABLED" 58 | } 59 | }); 60 | 61 | static DISABLED: LazyLock<&str> = LazyLock::new(|| { 62 | if stdout().is_terminal() { 63 | "\x1b[1;31mDISABLED\x1b[0m" 64 | } else { 65 | "DISABLED" 66 | } 67 | }); 68 | 69 | fn help() { 70 | println!( 71 | "{} {} 72 | 73 | A linker replacement to help protect against malicious build scripts 74 | ", 75 | env!("CARGO_PKG_NAME"), 76 | env!("CARGO_PKG_VERSION"), 77 | ); 78 | let result = enabled(); 79 | if matches!(result, Ok(true)) { 80 | let enabled = *ENABLED; 81 | println!("build-wrap is {enabled}"); 82 | return; 83 | } 84 | let disabled = *DISABLED; 85 | let msg = result 86 | .err() 87 | .map(|error| format!(": {error}")) 88 | .unwrap_or_default(); 89 | println!( 90 | r#"build-wrap is {disabled}{msg} 91 | 92 | To enable build-wrap, create a `.cargo/config.toml` file in your home directory with the following contents: 93 | 94 | ``` 95 | [target.'cfg(all())'] 96 | linker = "build-wrap" 97 | ```{}"#, 98 | if noble_numbat_or_later().unwrap_or(cfg!(target_os = "linux")) { 99 | " 100 | 101 | And install the Bubblewrap AppArmor profile with the following commands: 102 | 103 | ``` 104 | sudo apt install apparmor-profiles 105 | sudo cp /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d 106 | sudo systemctl reload apparmor 107 | ```" 108 | } else { 109 | "" 110 | } 111 | ); 112 | } 113 | 114 | static BWRAP_APPARMOR_PROFILE_PATH: LazyLock<&Path> = 115 | LazyLock::new(|| Path::new("/etc/apparmor.d/bwrap-userns-restrict")); 116 | 117 | fn enabled() -> Result { 118 | let current_exe = current_exe()?; 119 | let Some(home) = home::home_dir() else { 120 | bail!("failed to determine home directory"); 121 | }; 122 | let path_buf = home.join(".cargo/config.toml"); 123 | let contents = read_to_string(&path_buf) 124 | .with_context(|| format!("failed to read `{}`", path_buf.display()))?; 125 | let table = contents.parse::()?; 126 | let Some(linker) = table 127 | .get("target") 128 | .and_then(toml::Value::as_table) 129 | .and_then(|table| table.get("cfg(all())")) 130 | .and_then(toml::Value::as_table) 131 | .and_then(|table| table.get("linker")) 132 | .and_then(toml::Value::as_str) 133 | else { 134 | bail!("`config.toml` has unexpected contents"); 135 | }; 136 | let path = util::which(linker)?; 137 | if current_exe != path { 138 | return Ok(false); 139 | } 140 | if noble_numbat_or_later()? && !BWRAP_APPARMOR_PROFILE_PATH.try_exists()? { 141 | bail!("`{}` does not exist", BWRAP_APPARMOR_PROFILE_PATH.display()); 142 | } 143 | Ok(true) 144 | } 145 | 146 | static OS_RELEASE_PATH: LazyLock<&Path> = LazyLock::new(|| Path::new("/etc/os-release")); 147 | 148 | static VERSION_ID_RE: LazyLock = LazyLock::new(|| Regex::new(r"([0-9]+)\.[0-9]+").unwrap()); 149 | 150 | fn noble_numbat_or_later() -> Result { 151 | if !OS_RELEASE_PATH.try_exists()? { 152 | return Ok(false); 153 | } 154 | let map = parse_env_file(&OS_RELEASE_PATH)?; 155 | let Some(version_id) = map.get("VERSION_ID") else { 156 | bail!( 157 | "`{}` does not contain `VERSION_ID`", 158 | OS_RELEASE_PATH.display() 159 | ); 160 | }; 161 | let Some(captures) = VERSION_ID_RE.captures(version_id) else { 162 | bail!("failed to parse version id: {:?}", version_id); 163 | }; 164 | assert_eq!(2, captures.len()); 165 | let version_major = u64::from_str(captures.get(1).unwrap().as_str())?; 166 | Ok(version_major >= 24) 167 | } 168 | 169 | static ENV_LINE_RE: LazyLock = LazyLock::new(|| Regex::new("([A-Za-z0-9_]+)=(.*)").unwrap()); 170 | 171 | fn parse_env_file(path: &Path) -> Result> { 172 | let mut map = BTreeMap::new(); 173 | let contents = read_to_string(path)?; 174 | for line in contents.lines() { 175 | let Some(captures) = ENV_LINE_RE.captures(line) else { 176 | bail!("failed to parse line: {:?}", line); 177 | }; 178 | assert_eq!(3, captures.len()); 179 | let key = captures.get(1).unwrap().as_str(); 180 | let mut value = captures.get(2).unwrap().as_str(); 181 | if value.len() >= 2 && value.starts_with('"') && value.ends_with('"') { 182 | value = &value[1..value.len() - 1]; 183 | } 184 | map.insert(key.to_owned(), value.to_owned()); 185 | } 186 | Ok(map) 187 | } 188 | 189 | #[cfg(test)] 190 | mod test { 191 | use regex::Regex; 192 | 193 | #[test] 194 | fn help() { 195 | super::run(&["build-wrap".to_owned(), "--help".to_owned()]).unwrap(); 196 | } 197 | 198 | #[test] 199 | fn version() { 200 | super::run(&["build-wrap".to_owned(), "--version".to_owned()]).unwrap(); 201 | } 202 | 203 | #[test] 204 | fn readme_contains_linux_default_cmd_with_comments() { 205 | super::util::assert_readme_contains_code_block( 206 | super::LINUX_DEFAULT_CMD.lines().map(str::trim_start), 207 | Some("sh"), 208 | ); 209 | } 210 | 211 | #[test] 212 | fn readme_contains_linux_default_cmd_on_one_line() { 213 | let re = Regex::new("\\s+").unwrap(); 214 | let cmd = re.replace_all(super::LINUX_DEFAULT_CMD, " "); 215 | super::util::assert_readme_contains_code_block(std::iter::once(cmd), Some("sh")); 216 | } 217 | 218 | #[test] 219 | fn readme_contains_macos_default_cmd() { 220 | super::util::assert_readme_contains_code_block( 221 | std::iter::once(super::MACOS_DEFAULT_CMD), 222 | Some("sh"), 223 | ); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/util/common.rs: -------------------------------------------------------------------------------- 1 | //! This file is included verbatim in the wrapper build script's src/main.rs file. 2 | 3 | use anyhow::{anyhow, bail, ensure, Context, Result}; 4 | use std::{ 5 | env, 6 | fs::{canonicalize, read_to_string}, 7 | io::Write, 8 | os::unix::ffi::OsStrExt, 9 | path::Path, 10 | process::{Command, Output, Stdio}, 11 | str::Utf8Error, 12 | sync::LazyLock, 13 | }; 14 | 15 | #[allow(dead_code)] 16 | const DEFAULT_PROFILE: &str = r#"(version 1) 17 | (deny default) 18 | (allow file-read*) ;; Allow read-only access everywhere 19 | (allow file-write* (subpath "/dev")) ;; Allow write access to /dev 20 | (allow file-write* (subpath "{OUT_DIR}")) ;; Allow write access to `OUT_DIR` 21 | (allow file-write* (subpath "{TMPDIR}")) ;; Allow write access to `TMPDIR` 22 | (allow file-write* (subpath "{PRIVATE_TMPDIR}")) ;; Allow write access to `PRIVATE_TMPDIR` (see below) 23 | (allow process-exec) ;; Allow `exec` 24 | (allow process-fork) ;; Allow `fork` 25 | (allow sysctl-read) ;; Allow reading kernel state 26 | (deny network*) ;; Deny network access 27 | "#; 28 | 29 | /// Executes `command`, forwards its output to stdout and stderr, and optionally checks whether 30 | /// `command` succeeded. 31 | /// 32 | /// Called by [`exec_sibling`]. Since this file is included in the wrapper build script's 33 | /// src/main.rs file, `exec_forwarding_output` should appear here, alongside [`exec_sibling`]. 34 | /// 35 | /// # Errors 36 | /// 37 | /// If `command` cannot be executed, or if `failure_is_error` is true and `command` failed. 38 | pub fn exec_forwarding_output(mut command: Command, failure_is_error: bool) -> Result { 39 | command.stdout(Stdio::piped()); 40 | command.stderr(Stdio::piped()); 41 | 42 | let output = command.output()?; 43 | 44 | // smoelius: Stdout *must* be forwarded. 45 | // See: https://doc.rust-lang.org/cargo/reference/build-scripts.html#life-cycle-of-a-build-script 46 | // `print!` and `eprint!` are used so that `libtest` will capture them. 47 | print!("{}", String::from_utf8_lossy(&output.stdout)); 48 | eprint!("{}", String::from_utf8_lossy(&output.stderr)); 49 | 50 | std::io::stdout().flush()?; 51 | std::io::stderr().flush()?; 52 | 53 | if !output.status.success() { 54 | if failure_is_error { 55 | bail!("command failed: {command:?}"); 56 | } 57 | eprintln!("command failed: {command:?}"); 58 | } 59 | 60 | Ok(output) 61 | } 62 | 63 | /// Essentially the body of the wrapper build script's `main` function. Not called by `build-wrap` 64 | /// itself. 65 | #[allow(dead_code)] 66 | fn exec_sibling(sibling_path_as_str: &str) -> Result<()> { 67 | let current_exe = env::current_exe()?; 68 | 69 | let parent = current_exe 70 | .parent() 71 | .ok_or_else(|| anyhow!("failed to get `current_exe` parent"))?; 72 | 73 | let sibling_path = Path::new(sibling_path_as_str); 74 | 75 | assert!(sibling_path.starts_with(parent)); 76 | 77 | // smoelius: The `BUILD_WRAP_CMD` used is the one set when set when the wrapper build script is 78 | // compiled, not when it is run. So if the wrapped build script prints the following and the 79 | // environment variable changes, those facts alone will not cause the wrapper build script 80 | // to be rebuilt: 81 | // ``` 82 | // cargo:rerun-if-env-changed=BUILD_WRAP_CMD 83 | // ``` 84 | // They will cause the wrapped build script to be rerun, however. 85 | let expanded_args = split_and_expand(sibling_path)?; 86 | 87 | let allow_enabled = enabled("BUILD_WRAP_ALLOW") || package_name_allowed(); 88 | 89 | let mut command = Command::new(&expanded_args[0]); 90 | command.args(&expanded_args[1..]); 91 | let output = exec_forwarding_output(command, !allow_enabled)?; 92 | 93 | // smoelius: We should arrive at this `if` with `!output.status.success()` only when 94 | // `BUILD_WRAP_ALLOW` is enabled. 95 | if !output.status.success() { 96 | debug_assert!(allow_enabled); 97 | let command = Command::new(sibling_path); 98 | let _: Output = exec_forwarding_output(command, true)?; 99 | } 100 | 101 | Ok(()) 102 | } 103 | 104 | pub trait ToUtf8 { 105 | fn to_utf8(&self) -> std::result::Result<&str, Utf8Error>; 106 | } 107 | 108 | impl> ToUtf8 for T { 109 | fn to_utf8(&self) -> std::result::Result<&str, Utf8Error> { 110 | std::str::from_utf8(self.as_ref().as_os_str().as_bytes()) 111 | } 112 | } 113 | 114 | pub fn split_and_expand(build_script_path: &Path) -> Result> { 115 | let cmd = 116 | option_env!("BUILD_WRAP_CMD").ok_or_else(|| anyhow!("`BUILD_WRAP_CMD` is undefined"))?; 117 | let args = split_escaped(cmd)?; 118 | let expanded_args = args 119 | .into_iter() 120 | .map(|arg| expand(&arg, Some(build_script_path))) 121 | .collect::>>()?; 122 | eprintln!("expanded `BUILD_WRAP_CMD`: {:#?}", &expanded_args); 123 | ensure!( 124 | !expanded_args.is_empty(), 125 | "expanded `BUILD_WRAP_CMD` is empty or all whitespace" 126 | ); 127 | 128 | Ok(expanded_args) 129 | } 130 | 131 | fn split_escaped(mut s: &str) -> Result> { 132 | let mut v = vec![String::new()]; 133 | 134 | while let Some(i) = s.find(|c: char| c.is_ascii_whitespace() || c == '\\') { 135 | debug_assert!(!v.is_empty()); 136 | // smoelius: Only the last string in `v` can be empty. 137 | debug_assert!(v 138 | .iter() 139 | .position(String::is_empty) 140 | .is_none_or(|i| i == v.len() - 1)); 141 | 142 | let c = s.as_bytes()[i]; 143 | 144 | v.last_mut().unwrap().push_str(&s[..i]); 145 | 146 | s = &s[i + 1..]; 147 | 148 | // smoelius: `i` shouldn't be needed anymore. 149 | #[allow(unused_variables, clippy::let_unit_value)] 150 | let i = (); 151 | 152 | if c.is_ascii_whitespace() { 153 | if !v.last().unwrap().is_empty() { 154 | v.push(String::new()); 155 | } 156 | continue; 157 | } 158 | 159 | // smoelius: If the previous `if` fails, then `c` must be a backslash. 160 | if !s.is_empty() { 161 | let c = s.as_bytes()[0]; 162 | // smoelius: Verify that `c` is a legally escapable character before subslicing `s`. 163 | ensure!( 164 | c.is_ascii_whitespace() || c == b'\\', 165 | "illegally escaped character" 166 | ); 167 | s = &s[1..]; 168 | v.last_mut().unwrap().push(c as char); 169 | continue; 170 | } 171 | 172 | bail!("trailing backslash"); 173 | } 174 | 175 | // smoelius: Push whatever is left. 176 | v.last_mut().unwrap().push_str(s); 177 | 178 | if v.last().unwrap().is_empty() { 179 | v.pop(); 180 | } 181 | 182 | debug_assert!(!v.iter().any(String::is_empty)); 183 | 184 | Ok(v) 185 | } 186 | 187 | fn expand(mut cmd: &str, build_script_path: Option<&Path>) -> Result { 188 | let build_script_path_as_str = build_script_path 189 | .map(|path| path.to_utf8().map(ToOwned::to_owned)) 190 | .transpose()?; 191 | 192 | let mut buf = String::new(); 193 | 194 | while let Some(i) = cmd.find(['{', '}']) { 195 | let c = cmd.as_bytes()[i]; 196 | 197 | buf.push_str(&cmd[..i]); 198 | 199 | cmd = &cmd[i + 1..]; 200 | 201 | // smoelius: `i` shouldn't be needed anymore. 202 | #[allow(unused_variables, clippy::let_unit_value)] 203 | let i = (); 204 | 205 | // smoelius: Escaped `{` or `}`? 206 | if !cmd.is_empty() && cmd.as_bytes()[0] == c { 207 | buf.push(c as char); 208 | cmd = &cmd[1..]; 209 | continue; 210 | } 211 | 212 | if c == b'{' { 213 | if let Some(j) = cmd.find('}') { 214 | if j == 0 { 215 | let s = build_script_path_as_str 216 | .as_ref() 217 | .ok_or_else(|| anyhow!("build script path is unavailable"))?; 218 | buf.push_str(s); 219 | } else { 220 | let key = &cmd[..j]; 221 | let value = var(key) 222 | .with_context(|| format!("environment variable `{key}` not found"))?; 223 | buf.push_str(&value); 224 | } 225 | cmd = &cmd[j + 1..]; 226 | continue; 227 | } 228 | } 229 | 230 | bail!("unbalanced '{}'", c as char); 231 | } 232 | 233 | // smoelius: Push whatever is left. 234 | buf.push_str(cmd); 235 | 236 | Ok(buf) 237 | } 238 | 239 | #[cfg(target_os = "macos")] 240 | static BUILD_WRAP_PROFILE_PATH: LazyLock = LazyLock::new(|| { 241 | let tempfile = tempfile::NamedTempFile::new().unwrap(); 242 | let (mut file, temp_path) = tempfile.into_parts(); 243 | let profile = var("BUILD_WRAP_PROFILE").unwrap_or(DEFAULT_PROFILE.to_owned()); 244 | let expanded_profile = expand(&profile, None).unwrap(); 245 | file.write_all(expanded_profile.as_bytes()).unwrap(); 246 | let path = temp_path.keep().unwrap(); 247 | path.to_utf8().map(ToOwned::to_owned).unwrap() 248 | }); 249 | 250 | static PRIVATE_TMPDIR: LazyLock> = LazyLock::new(|| { 251 | var("TMPDIR").ok().and_then(|value| { 252 | let path = canonicalize(value).ok()?; 253 | if path.starts_with("/private") { 254 | path.to_utf8().map(ToOwned::to_owned).ok() 255 | } else { 256 | None 257 | } 258 | }) 259 | }); 260 | 261 | fn var(key: &str) -> Result { 262 | #[cfg(target_os = "macos")] 263 | if key == "BUILD_WRAP_PROFILE_PATH" { 264 | return Ok(BUILD_WRAP_PROFILE_PATH.clone()); 265 | } 266 | 267 | if key == "PRIVATE_TMPDIR" { 268 | return PRIVATE_TMPDIR.clone().ok_or(env::VarError::NotPresent); 269 | } 270 | 271 | env::var(key) 272 | } 273 | 274 | fn enabled(name: &str) -> bool { 275 | env::var(name).is_ok_and(|value| value != "0") 276 | } 277 | 278 | static ALLOWED_PACKAGE_NAMES: LazyLock> = LazyLock::new(|| { 279 | let base_directories = xdg::BaseDirectories::new(); 280 | let Some(allowed) = base_directories.find_config_file("build-wrap/allow.txt") else { 281 | return Vec::new(); 282 | }; 283 | let contents = read_to_string(allowed).unwrap(); 284 | contents.lines().map(ToOwned::to_owned).collect() 285 | }); 286 | 287 | fn package_name_allowed() -> bool { 288 | let Ok(package_name) = env::var("CARGO_PKG_NAME") else { 289 | return false; 290 | }; 291 | ALLOWED_PACKAGE_NAMES.contains(&package_name) 292 | } 293 | 294 | #[cfg(test)] 295 | pub use test::assert_readme_contains_code_block; 296 | 297 | #[cfg(test)] 298 | mod test { 299 | use anyhow::Result; 300 | use std::{env::set_var, fs::read_to_string, path::Path}; 301 | 302 | #[test] 303 | fn expand_cmd() { 304 | set_var("KEY", "VALUE"); 305 | 306 | let successes = [ 307 | ("left path right", "{}"), 308 | ("left VALUE right", "{KEY}"), 309 | ("left { right", "{{"), 310 | ("left } right", "}}"), 311 | ]; 312 | 313 | let failures = [ 314 | ("environment variable `UNKNOWN` not found", "{UNKNOWN}"), 315 | ("unbalanced '{'", "{"), 316 | ("unbalanced '}'", "}"), 317 | ]; 318 | 319 | for (expected, s) in successes { 320 | assert_eq!(expected, surround_and_expand(s).unwrap()); 321 | } 322 | 323 | for (expected, s) in failures { 324 | assert_eq!(expected, surround_and_expand(s).unwrap_err().to_string()); 325 | } 326 | } 327 | 328 | fn surround_and_expand(s: &str) -> Result { 329 | let cmd = String::from("left ") + s + " right"; 330 | super::expand(&cmd, Some(Path::new("path"))) 331 | } 332 | 333 | #[test] 334 | fn readme_contains_default_profile() { 335 | assert_readme_contains_code_block(super::DEFAULT_PROFILE.lines(), None); 336 | } 337 | 338 | pub fn assert_readme_contains_code_block( 339 | lines: impl Iterator>, 340 | language: Option<&str>, 341 | ) { 342 | let delimited_lines = std::iter::once(format!("```{}", language.unwrap_or_default())) 343 | .chain(lines.map(|s| s.as_ref().to_owned())) 344 | .chain(std::iter::once(String::from("```"))) 345 | .collect::>(); 346 | let size = delimited_lines.len(); 347 | let readme = read_to_string("README.md").unwrap(); 348 | let readme_lines = readme 349 | .lines() 350 | .map(|line| { 351 | let index = line.find('#').unwrap_or(line.len()); 352 | line[..index].trim() 353 | }) 354 | .collect::>(); 355 | assert!(readme_lines.windows(size).any(|w| w == delimited_lines)); 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{ensure, Result}; 2 | use std::{env, ffi::OsString, fs::canonicalize, path::PathBuf, process::Command}; 3 | 4 | mod common; 5 | pub use common::{exec_forwarding_output, ToUtf8}; 6 | 7 | #[cfg(test)] 8 | #[allow(unused_imports)] 9 | pub use common::assert_readme_contains_code_block; 10 | 11 | pub const DEFAULT_LD: &str = "cc"; 12 | 13 | #[must_use] 14 | pub fn cargo_build() -> Command { 15 | // smoelius: Respect `CARGO` environment variable, if set. 16 | let cargo = env::var_os("CARGO").unwrap_or(OsString::from("cargo")); 17 | 18 | let mut command = Command::new(cargo); 19 | command.arg("build"); 20 | 21 | // smoelius: Show build script (e.g., wrapper) output. 22 | // See: https://github.com/rust-lang/cargo/issues/985#issuecomment-258311111 23 | command.arg("-vv"); 24 | 25 | // smoelius: Show linker output. 26 | // See: https://stackoverflow.com/a/71866183 27 | // smoelius: Disabling this in the interest of reducing error message verbosity. Note that this 28 | // change affects only `build-wrap`'s building of wrapped build scripts. 29 | // command.env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info"); 30 | 31 | command 32 | } 33 | 34 | // smoelius: The present module is imported by tests/integration/util.rs. The next `allow` prevents 35 | // a "function `which` is never used" warning in that module. 36 | #[allow(dead_code)] 37 | pub fn which(filename: &str) -> Result { 38 | let mut command = Command::new("which"); 39 | let output = command.arg(filename).output()?; 40 | ensure!(output.status.success(), "command failed: {command:?}"); 41 | 42 | let stdout = std::str::from_utf8(&output.stdout)?; 43 | let path = canonicalize(stdout.trim_end())?; 44 | Ok(path) 45 | } 46 | -------------------------------------------------------------------------------- /src/wrapper.rs: -------------------------------------------------------------------------------- 1 | use crate::util::ToUtf8; 2 | use anyhow::{anyhow, Result}; 3 | use std::{ 4 | fs::{create_dir, rename, write}, 5 | path::Path, 6 | }; 7 | use tempfile::{tempdir, NamedTempFile, TempDir}; 8 | 9 | #[allow(clippy::disallowed_methods)] 10 | pub fn package(build_script_path: &Path) -> Result { 11 | let parent = build_script_path 12 | .parent() 13 | .ok_or_else(|| anyhow!("failed to get `build_script_path` parent"))?; 14 | 15 | let temp_file = NamedTempFile::new_in(parent)?; 16 | 17 | let (_file, sibling_path) = temp_file.keep()?; 18 | 19 | rename(build_script_path, &sibling_path)?; 20 | 21 | let sibling_path_as_str = sibling_path.to_utf8()?; 22 | 23 | let tempdir = tempdir()?; 24 | 25 | write(tempdir.path().join("Cargo.toml"), CARGO_TOML)?; 26 | create_dir(tempdir.path().join("src"))?; 27 | write( 28 | tempdir.path().join("src/main.rs"), 29 | main_rs(sibling_path_as_str), 30 | )?; 31 | 32 | Ok(tempdir) 33 | } 34 | 35 | // smoelius: The dependencies listed here must be sufficient to compile util/common.rs. 36 | const CARGO_TOML: &str = r#" 37 | [package] 38 | name = "build_script_wrapper" 39 | version = "0.1.0" 40 | edition = "2024" 41 | publish = false 42 | 43 | [dependencies] 44 | anyhow = "1.0" 45 | tempfile = "3.20" 46 | xdg = "3.0" 47 | "#; 48 | 49 | /// A wrapper build script's src/main.rs consists of the following: 50 | /// 51 | /// - the contents of util/common.rs (included verbatim) 52 | /// - the path of the renamed original build script (`PATH`) 53 | /// - a `main` function 54 | /// 55 | /// See [`package`]. 56 | fn main_rs(sibling_path_as_str: &str) -> Vec { 57 | [ 58 | COMMON_RS, 59 | format!( 60 | r#" 61 | const PATH: &str = "{sibling_path_as_str}"; 62 | 63 | fn main() -> Result<()> {{ 64 | exec_sibling(PATH) 65 | }} 66 | "#, 67 | ) 68 | .as_bytes(), 69 | ] 70 | .concat() 71 | } 72 | 73 | const COMMON_RS: &[u8] = include_bytes!("util/common.rs"); 74 | -------------------------------------------------------------------------------- /tests/build_scripts/dev_null.rs: -------------------------------------------------------------------------------- 1 | use std::process::{exit, Command, ExitStatus, Stdio}; 2 | 3 | fn main() { 4 | let status: ExitStatus = Command::new("echo").stdout(Stdio::null()).status().unwrap(); 5 | let code = status.code().unwrap(); 6 | exit(code); 7 | } 8 | -------------------------------------------------------------------------------- /tests/build_scripts/inside_out_dir.rs: -------------------------------------------------------------------------------- 1 | use std::{env::var_os, fs::write, path::PathBuf}; 2 | 3 | fn main() { 4 | let out_dir = var_os("OUT_DIR").unwrap(); 5 | let path = PathBuf::from(out_dir); 6 | write(path.join("INSIDE_OUT_DIR"), []).unwrap(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/build_scripts/linux-sandboxer/config.toml: -------------------------------------------------------------------------------- 1 | target_os = "linux" 2 | 3 | build_wrap_cmd = "sandboxer.sh --fs-ro=/ --fs-rw=/dev:{OUT_DIR}:/tmp --tcp-connect=0 -- {}" 4 | -------------------------------------------------------------------------------- /tests/build_scripts/linux-sandboxer/dev_null.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/linux-sandboxer/dev_null.stderr -------------------------------------------------------------------------------- /tests/build_scripts/linux-sandboxer/inside_out_dir.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/linux-sandboxer/inside_out_dir.stderr -------------------------------------------------------------------------------- /tests/build_scripts/linux-sandboxer/outside_out_dir.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | [..]message: "Permission denied"[..] 3 | ... 4 | -------------------------------------------------------------------------------- /tests/build_scripts/linux-sandboxer/ping.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | ping: socket: Operation not permitted 3 | ... 4 | -------------------------------------------------------------------------------- /tests/build_scripts/linux-sandboxer/rerun_if_build_wrap_cmd_changed.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/linux-sandboxer/rerun_if_build_wrap_cmd_changed.stderr -------------------------------------------------------------------------------- /tests/build_scripts/linux-sandboxer/rustc_version.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/linux-sandboxer/rustc_version.stderr -------------------------------------------------------------------------------- /tests/build_scripts/linux-sandboxer/tiocsti.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | libc::ioctl: Inappropriate ioctl for device 3 | ... 4 | -------------------------------------------------------------------------------- /tests/build_scripts/linux/config.toml: -------------------------------------------------------------------------------- 1 | target_os = "linux" 2 | -------------------------------------------------------------------------------- /tests/build_scripts/linux/dev_null.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/linux/dev_null.stderr -------------------------------------------------------------------------------- /tests/build_scripts/linux/inside_out_dir.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/linux/inside_out_dir.stderr -------------------------------------------------------------------------------- /tests/build_scripts/linux/outside_out_dir.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | [..]message: "Read-only file system"[..] 3 | ... 4 | -------------------------------------------------------------------------------- /tests/build_scripts/linux/ping.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | ping: socket: Operation not permitted 3 | ... 4 | -------------------------------------------------------------------------------- /tests/build_scripts/linux/rerun_if_build_wrap_cmd_changed.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/linux/rerun_if_build_wrap_cmd_changed.stderr -------------------------------------------------------------------------------- /tests/build_scripts/linux/rustc_version.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/linux/rustc_version.stderr -------------------------------------------------------------------------------- /tests/build_scripts/linux/tiocsti.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | libc::ioctl: Inappropriate ioctl for device 3 | ... 4 | -------------------------------------------------------------------------------- /tests/build_scripts/macos/config.toml: -------------------------------------------------------------------------------- 1 | target_os = "macos" 2 | -------------------------------------------------------------------------------- /tests/build_scripts/macos/dev_null.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/macos/dev_null.stderr -------------------------------------------------------------------------------- /tests/build_scripts/macos/inside_out_dir.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/macos/inside_out_dir.stderr -------------------------------------------------------------------------------- /tests/build_scripts/macos/outside_out_dir.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | [..]message: "Operation not permitted"[..] 3 | ... 4 | -------------------------------------------------------------------------------- /tests/build_scripts/macos/ping.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | ping: sendto: Operation not permitted 3 | ... 4 | -------------------------------------------------------------------------------- /tests/build_scripts/macos/rerun_if_build_wrap_cmd_changed.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/macos/rerun_if_build_wrap_cmd_changed.stderr -------------------------------------------------------------------------------- /tests/build_scripts/macos/rustc_version.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/build_scripts/macos/rustc_version.stderr -------------------------------------------------------------------------------- /tests/build_scripts/macos/tiocsti.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | libc::ioctl: Operation not permitted 3 | ... 4 | -------------------------------------------------------------------------------- /tests/build_scripts/outside_out_dir.rs: -------------------------------------------------------------------------------- 1 | use std::{env::var_os, fs::write, path::PathBuf}; 2 | 3 | fn main() { 4 | let out_dir = var_os("OUT_DIR").unwrap(); 5 | let path = PathBuf::from(out_dir).parent().unwrap().to_path_buf(); 6 | write(path.join("OUTSIDE_OUT_DIR"), "x").unwrap(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/build_scripts/ping.rs: -------------------------------------------------------------------------------- 1 | use std::process::{exit, Command, ExitStatus}; 2 | 3 | fn main() { 4 | let status: ExitStatus = Command::new("ping") 5 | .args(["-c", "1", "-v", "127.0.0.1"]) 6 | .status() 7 | .unwrap(); 8 | let code = status.code().unwrap(); 9 | exit(code); 10 | } 11 | -------------------------------------------------------------------------------- /tests/build_scripts/rerun_if_build_wrap_cmd_changed.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-env-changed=BUILD_WRAP_CMD"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/build_scripts/rustc_version.rs: -------------------------------------------------------------------------------- 1 | // smoelius: Copied from: 2 | // https://github.com/djc/rustc-version-rs/blob/9cdb26683edb35a31573f5322594ef07e43aa142/README.md?plain=1#L37-L65 3 | 4 | // This could be a cargo build script 5 | 6 | use rustc_version::{version, version_meta, Channel, Version}; 7 | 8 | fn main() { 9 | // Assert we haven't travelled back in time 10 | assert!(version().unwrap().major >= 1); 11 | 12 | // Set cfg flags depending on release channel 13 | match version_meta().unwrap().channel { 14 | Channel::Stable => { 15 | println!("cargo:rustc-cfg=RUSTC_IS_STABLE"); 16 | } 17 | Channel::Beta => { 18 | println!("cargo:rustc-cfg=RUSTC_IS_BETA"); 19 | } 20 | Channel::Nightly => { 21 | println!("cargo:rustc-cfg=RUSTC_IS_NIGHTLY"); 22 | } 23 | Channel::Dev => { 24 | println!("cargo:rustc-cfg=RUSTC_IS_DEV"); 25 | } 26 | } 27 | 28 | // Check for a minimum version 29 | if version().unwrap() >= Version::parse("1.4.0").unwrap() { 30 | println!("cargo:rustc-cfg=compiler_has_important_bugfix"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/build_scripts/tiocsti.rs: -------------------------------------------------------------------------------- 1 | // smoelius: Based on: https://github.com/containers/bubblewrap/issues/142 2 | // Thanks to @maxammann for the pointer. 3 | 4 | macro_rules! i8_ptr { 5 | ($expr:expr) => { 6 | $expr as *const _ as *const i8 7 | }; 8 | } 9 | 10 | fn main() { 11 | let cmd = "id\n"; 12 | for c in cmd.chars() { 13 | if unsafe { libc::ioctl(libc::STDIN_FILENO, libc::TIOCSTI, i8_ptr!(&c)) } < 0 { 14 | unsafe { libc::perror(i8_ptr!(b"libc::ioctl\0")) }; 15 | panic!(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/integration/allow.rs: -------------------------------------------------------------------------------- 1 | use crate::util; 2 | 3 | #[test] 4 | fn allow() { 5 | let temp_package = util::temp_package(Some("tests/build_scripts/ping.rs"), []).unwrap(); 6 | 7 | for allow in [false, true] { 8 | let mut command = util::build_with_build_wrap(); 9 | if allow { 10 | command.env("BUILD_WRAP_ALLOW", "1"); 11 | } 12 | command.current_dir(&temp_package); 13 | 14 | let output = util::exec_forwarding_output(command, false).unwrap(); 15 | // smoelius: The command should succeed precisely when `BUILD_WRAP_ALLOW` is enabled. 16 | assert_eq!(allow, output.status.success()); 17 | let stderr = std::str::from_utf8(&output.stderr).unwrap(); 18 | assert!(stderr.contains("command failed")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/integration/build_scripts.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | config, 3 | util::{test_case, TestCase}, 4 | }; 5 | use std::io::Write; 6 | 7 | #[test] 8 | fn build_scripts() { 9 | config::for_each_test_case("tests/build_scripts", |build_wrap_cmd, path, stderr| { 10 | #[allow(clippy::explicit_write)] 11 | writeln!( 12 | std::io::stderr(), 13 | "running build script test: {}", 14 | path.display() 15 | ) 16 | .unwrap(); 17 | 18 | test_case(build_wrap_cmd, &TestCase::BuildScript(path), stderr); 19 | 20 | Ok(()) 21 | }) 22 | .unwrap(); 23 | } 24 | -------------------------------------------------------------------------------- /tests/integration/build_wrap_cmd_changed.rs: -------------------------------------------------------------------------------- 1 | use crate::util; 2 | use std::process::Command; 3 | 4 | #[test] 5 | fn build_wrap_cmd_changed() { 6 | let temp_package = util::temp_package( 7 | Some("tests/build_scripts/rerun_if_build_wrap_cmd_changed.rs"), 8 | [], 9 | ) 10 | .unwrap(); 11 | 12 | // smoelius: Build with default `BUILD_WRAP_CMD`. 13 | 14 | let mut command = util::build_with_build_wrap(); 15 | command.current_dir(&temp_package); 16 | 17 | exec_and_check_stderr(command, false, "] real "); 18 | 19 | // smoelius: Build with `BUILD_WRAP_CMD` set to `time -p {}`. 20 | 21 | let mut command = util::build_with_build_wrap(); 22 | command.env("BUILD_WRAP_CMD", "time -p {}"); 23 | command.current_dir(&temp_package); 24 | 25 | exec_and_check_stderr(command, false, "] real "); 26 | 27 | // smoelius: Clean and build again with `BUILD_WRAP_CMD` set to `time -p {}`. 28 | 29 | let status = Command::new("cargo") 30 | .args(["clean", "--quiet"]) 31 | .current_dir(&temp_package) 32 | .status() 33 | .unwrap(); 34 | assert!(status.success()); 35 | 36 | let mut command = util::build_with_build_wrap(); 37 | command.env("BUILD_WRAP_CMD", "time -p {}"); 38 | command.current_dir(&temp_package); 39 | 40 | exec_and_check_stderr(command, true, "] real "); 41 | } 42 | 43 | fn exec_and_check_stderr(command: Command, should_contain: bool, needle: &str) { 44 | let output = util::exec_forwarding_output(command, false).unwrap(); 45 | let stderr = std::str::from_utf8(&output.stderr).unwrap(); 46 | assert!( 47 | output.status.success(), 48 | "command failed:\n```\n{stderr}\n```" 49 | ); 50 | 51 | let contains = stderr.contains(needle); 52 | assert_eq!( 53 | should_contain, contains, 54 | "unexpected stderr contents:\n```\n{stderr}\n```" 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /tests/integration/cargo_target_dir.rs: -------------------------------------------------------------------------------- 1 | use crate::util; 2 | use std::{path::Path, process::Command}; 3 | 4 | const DIR: &str = "fixtures/cargo_target_dir"; 5 | 6 | #[test] 7 | fn cargo_target_dir() { 8 | let command = util::build_with_default_linker(); 9 | test_build(command); 10 | 11 | // smoelius: When `build-wrap` builds the wrapper package, it expects the target directory to be 12 | // `target`. So building the wrapper package in `fixtures/cargo_target_dir` would fail because 13 | // it contains a `.cargo/config.toml` that sets the target directory to `target-custom`. 14 | 15 | // smoelius: The build script in `fixtures/cargo_target_dir` prints the path of the current 16 | // executable, i.e., the wrapped, original build script. Previously, this was unpacked into 17 | // `std::env::temp_dir()`. However, `build-wrap` now renames the original build script so that 18 | // it is a sibling of the wrapper build script. Hence, when this test is run, the current 19 | // executable should be in `target-custom` alongside the wrapper build script. 20 | let command = util::build_with_build_wrap(); 21 | test_build(command); 22 | 23 | // smoelius: Building the wrapper package should similarly succeed if `CARGO_TARGET_DIR` is set. 24 | // Note that the `fixtures/cargo_target_dir` directory must be cleaned for it to be rebuilt. 25 | let status = Command::new("cargo") 26 | .arg("clean") 27 | .current_dir(DIR) 28 | .status() 29 | .unwrap(); 30 | assert!(status.success()); 31 | 32 | let mut command = util::build_with_build_wrap(); 33 | command.env("CARGO_TARGET_DIR", "target-custom"); 34 | test_build(command); 35 | } 36 | 37 | fn test_build(mut command: Command) { 38 | command.current_dir(DIR); 39 | let output = util::exec_forwarding_output(command, true).unwrap(); 40 | let stderr = std::str::from_utf8(&output.stderr).unwrap(); 41 | let expected_dir = Path::new(env!("CARGO_MANIFEST_DIR")) 42 | .join(DIR) 43 | .join("target-custom/debug"); 44 | assert!(stderr.lines().any(|line| line.starts_with(&format!( 45 | "warning: cargo_target_dir@0.1.0: {}/", 46 | trim_trailing_slashes(&expected_dir.to_string_lossy()) 47 | )))); 48 | } 49 | 50 | fn trim_trailing_slashes(s: &str) -> &str { 51 | s.trim_end_matches('/') 52 | } 53 | -------------------------------------------------------------------------------- /tests/integration/ci.rs: -------------------------------------------------------------------------------- 1 | use crate::util; 2 | use assert_cmd::assert::OutputAssertExt; 3 | use cargo_metadata::MetadataCommand; 4 | use regex::Regex; 5 | use similar_asserts::SimpleDiff; 6 | use std::{ 7 | env::var, 8 | fs::{read_to_string, write}, 9 | path::Path, 10 | process::{Command, ExitStatus}, 11 | str::FromStr, 12 | }; 13 | 14 | #[test] 15 | fn clippy() { 16 | // smoelius: Using the actual target directory causes it to contain multiple `sandboxer` 17 | // executables, which confuses scripts/sandboxer.sh. So use a subdirectory of the target 18 | // directory instead. 19 | let metadata = MetadataCommand::new().no_deps().exec().unwrap(); 20 | 21 | Command::new("cargo") 22 | .args([ 23 | "+nightly", 24 | "clippy", 25 | "--all-features", 26 | "--all-targets", 27 | "--target-dir", 28 | metadata.target_directory.join("clippy").as_str(), 29 | "--", 30 | "--deny=warnings", 31 | ]) 32 | .assert() 33 | .success(); 34 | } 35 | 36 | #[test] 37 | fn dylint() { 38 | Command::new("cargo") 39 | .args(["dylint", "--all", "--", "--all-features", "--all-targets"]) 40 | .env("DYLINT_RUSTFLAGS", "--deny warnings") 41 | .assert() 42 | .success(); 43 | } 44 | 45 | #[test] 46 | fn markdown_link_check() { 47 | let tempdir = util::tempdir().unwrap(); 48 | 49 | // smoelius: Pin `markdown-link-check` to version 3.11 until the following issue is resolved: 50 | // https://github.com/tcort/markdown-link-check/issues/304 51 | Command::new("npm") 52 | .args(["install", "markdown-link-check@3.11"]) 53 | .current_dir(&tempdir) 54 | .assert() 55 | .success(); 56 | 57 | let config = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/markdown_link_check.json"); 58 | 59 | let readme_md = Path::new(env!("CARGO_MANIFEST_DIR")).join("README.md"); 60 | 61 | Command::new("npx") 62 | .args([ 63 | "markdown-link-check", 64 | "--config", 65 | &config.to_string_lossy(), 66 | &readme_md.to_string_lossy(), 67 | ]) 68 | .current_dir(&tempdir) 69 | .assert() 70 | .success(); 71 | } 72 | 73 | #[test] 74 | fn readme_reference_links_are_sorted() { 75 | let re = Regex::new(r"^\[[^\]]*\]:").unwrap(); 76 | let readme = read_to_string("README.md").unwrap(); 77 | let links = readme 78 | .lines() 79 | .filter(|line| re.is_match(line)) 80 | .collect::>(); 81 | let mut links_sorted = links.clone(); 82 | links_sorted.sort_unstable(); 83 | assert_eq!(links_sorted, links); 84 | } 85 | 86 | #[test] 87 | fn readme_reference_links_are_used() { 88 | let re = Regex::new(r"(?m)^(\[[^\]]*\]):").unwrap(); 89 | let readme = read_to_string("README.md").unwrap(); 90 | for captures in re.captures_iter(&readme) { 91 | assert_eq!(2, captures.len()); 92 | let m = captures.get(1).unwrap(); 93 | assert!( 94 | readme[..m.start()].contains(m.as_str()), 95 | "{} is unused", 96 | m.as_str() 97 | ); 98 | } 99 | } 100 | 101 | #[test] 102 | fn supply_chain() { 103 | let mut command = Command::new("cargo"); 104 | command.args(["supply-chain", "update", "--cache-max-age=0s"]); 105 | let _: ExitStatus = command.status().unwrap(); 106 | 107 | let mut command = Command::new("cargo"); 108 | command.args(["supply-chain", "json", "--no-dev"]); 109 | let assert = command.assert().success(); 110 | 111 | let stdout_actual = std::str::from_utf8(&assert.get_output().stdout).unwrap(); 112 | let mut value = serde_json::Value::from_str(stdout_actual).unwrap(); 113 | remove_avatars(&mut value); 114 | let stdout_normalized = serde_json::to_string_pretty(&value).unwrap(); 115 | 116 | let path_buf = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/supply_chain.json"); 117 | 118 | if enabled("BLESS") { 119 | write(path_buf, stdout_normalized).unwrap(); 120 | } else { 121 | let stdout_expected = read_to_string(&path_buf).unwrap(); 122 | 123 | assert!( 124 | stdout_expected == stdout_normalized, 125 | "{}", 126 | SimpleDiff::from_str(&stdout_expected, &stdout_normalized, "left", "right") 127 | ); 128 | } 129 | } 130 | 131 | fn remove_avatars(value: &mut serde_json::Value) { 132 | match value { 133 | serde_json::Value::Null 134 | | serde_json::Value::Bool(_) 135 | | serde_json::Value::Number(_) 136 | | serde_json::Value::String(_) => {} 137 | serde_json::Value::Array(array) => { 138 | for value in array { 139 | remove_avatars(value); 140 | } 141 | } 142 | serde_json::Value::Object(object) => { 143 | object.retain(|key, value| { 144 | if key == "avatar" { 145 | return false; 146 | } 147 | remove_avatars(value); 148 | true 149 | }); 150 | } 151 | } 152 | } 153 | 154 | fn enabled(key: &str) -> bool { 155 | var(key).is_ok_and(|value| value != "0") 156 | } 157 | -------------------------------------------------------------------------------- /tests/integration/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use serde::Deserialize; 3 | use std::{ 4 | env::{consts, var_os}, 5 | fs::{read_dir, read_to_string}, 6 | path::Path, 7 | }; 8 | 9 | #[derive(Debug, Deserialize)] 10 | pub struct Config { 11 | /// The operating system for which the stderr files are intended 12 | target_os: String, 13 | 14 | /// Value of `BUILD_WRAP_CMD` 15 | build_wrap_cmd: Option, 16 | } 17 | 18 | pub fn for_each_test_case( 19 | dir: impl AsRef, 20 | f: impl Fn(Option<&str>, &Path, &str) -> Result<()>, 21 | ) -> Result<()> { 22 | let mut dirs = Vec::new(); 23 | let mut test_cases = Vec::new(); 24 | 25 | for result in read_dir(dir)? { 26 | let entry = result?; 27 | let path = entry.path(); 28 | if path.is_dir() { 29 | dirs.push(path); 30 | } else { 31 | test_cases.push(path); 32 | } 33 | } 34 | 35 | dirs.sort(); 36 | test_cases.sort(); 37 | 38 | for dir in dirs { 39 | let config_toml_path = dir.join("config.toml"); 40 | let contents = read_to_string(&config_toml_path) 41 | .with_context(|| format!("failed to read `{}`", config_toml_path.display()))?; 42 | let config = toml::from_str::(&contents)?; 43 | if config.target_os != consts::OS { 44 | continue; 45 | } 46 | for test_case in &test_cases { 47 | let file_stem = test_case 48 | .file_stem() 49 | .unwrap_or_else(|| panic!("`{}` has no file stem", test_case.display())); 50 | if let Some(testname) = var_os("TESTNAME") { 51 | if file_stem != testname { 52 | return Ok(()); 53 | } 54 | } 55 | let stderr_path = dir.join(file_stem).with_extension("stderr"); 56 | let stderr = read_to_string(&stderr_path) 57 | .with_context(|| format!("failed to read `{}`", stderr_path.display()))?; 58 | f(config.build_wrap_cmd.as_deref(), test_case, &stderr)?; 59 | } 60 | } 61 | 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /tests/integration/config_allow.rs: -------------------------------------------------------------------------------- 1 | use crate::util; 2 | use std::fs::{create_dir_all, write}; 3 | 4 | #[test] 5 | fn config_allow() { 6 | let temp_package = util::temp_package(Some("tests/build_scripts/ping.rs"), []).unwrap(); 7 | 8 | let home = util::tempdir().unwrap(); 9 | let config_build_wrap = home.path().join(".config/build-wrap"); 10 | create_dir_all(&config_build_wrap).unwrap(); 11 | write(config_build_wrap.join("allow.txt"), "temp-package\n").unwrap(); 12 | 13 | for allow in [false, true] { 14 | let mut command = util::build_with_build_wrap(); 15 | command.env_remove("XDG_CONFIG_HOME"); 16 | if allow { 17 | command.env("HOME", home.path()); 18 | } 19 | command.current_dir(&temp_package); 20 | 21 | let output = util::exec_forwarding_output(command, false).unwrap(); 22 | // smoelius: The command should succeed precisely when `HOME` is set. 23 | assert_eq!(allow, output.status.success()); 24 | let stderr = std::str::from_utf8(&output.stderr).unwrap(); 25 | assert!(stderr.contains("command failed")); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/integration/custom_build_name.rs: -------------------------------------------------------------------------------- 1 | use crate::util; 2 | use std::{path::Path, process::Command}; 3 | 4 | #[test] 5 | fn custom_build_name() { 6 | let dir = Path::new("fixtures/custom_build_name"); 7 | 8 | let status = Command::new("cargo") 9 | .args(["clean", "--quiet"]) 10 | .current_dir(dir) 11 | .status() 12 | .unwrap(); 13 | assert!(status.success()); 14 | 15 | let mut command = util::build_with_build_wrap(); 16 | command.current_dir(dir); 17 | 18 | let output = util::exec_forwarding_output(command, false).unwrap(); 19 | assert!(!output.status.success()); 20 | 21 | let stderr = std::str::from_utf8(&output.stderr).unwrap(); 22 | let syscall = if cfg!(target_os = "linux") { 23 | "socket" 24 | } else { 25 | "sendto" 26 | }; 27 | assert!( 28 | stderr.contains(&format!("ping: {syscall}: Operation not permitted")), 29 | "stderr does not contain expected string:\n```\n{stderr}\n```", 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /tests/integration/dogfood.rs: -------------------------------------------------------------------------------- 1 | use crate::util; 2 | 3 | #[test] 4 | fn dogfood() { 5 | let mut command = util::build_with_build_wrap(); 6 | command.env("BUILD_WRAP_CMD", "time -p {}"); 7 | 8 | let output = util::exec_forwarding_output(command, true).unwrap(); 9 | let stderr = std::str::from_utf8(&output.stderr).unwrap(); 10 | let lines = stderr.lines().collect::>(); 11 | assert!( 12 | lines.windows(3).any(|w| { 13 | assert_eq!(3, w.len()); 14 | w[0].contains("] real ") && w[1].contains("] user ") && w[2].contains("] sys") 15 | }), 16 | "failed to find `time` output" 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tests/integration/enabled.rs: -------------------------------------------------------------------------------- 1 | use crate::util; 2 | use assert_cmd::cargo::CommandCargoExt; 3 | use cargo_metadata::MetadataCommand; 4 | use std::{ 5 | fs::{create_dir_all, write}, 6 | process::Command, 7 | }; 8 | 9 | #[test] 10 | fn enabled() { 11 | let home = util::tempdir().unwrap(); 12 | let cargo_home = home.path().join(".cargo"); 13 | create_dir_all(&cargo_home).unwrap(); 14 | write( 15 | cargo_home.join("config.toml"), 16 | r#"[target.'cfg(all())'] 17 | linker = "build-wrap""#, 18 | ) 19 | .unwrap(); 20 | 21 | for set_path in [false, true] { 22 | let mut command = Command::cargo_bin("build-wrap").unwrap(); 23 | command.env("HOME", home.path()); 24 | if set_path { 25 | let metadata = MetadataCommand::new().no_deps().exec().unwrap(); 26 | let target_debug = metadata.target_directory.join("debug").into_std_path_buf(); 27 | util::prepend_to_path(&mut command, target_debug).unwrap(); 28 | } 29 | exec_and_check_stdout( 30 | command, 31 | &format!( 32 | "build-wrap is {}", 33 | if set_path { "ENABLED" } else { "DISABLED" } 34 | ), 35 | ); 36 | } 37 | } 38 | 39 | fn exec_and_check_stdout(mut command: Command, prefix: &str) { 40 | let output = command.output().unwrap(); 41 | assert!(output.status.success()); 42 | let stdout = std::str::from_utf8(&output.stdout).unwrap(); 43 | assert!( 44 | stdout.lines().any(|line| line.starts_with(prefix)), 45 | "unexpected stdout: ```\n{stdout}\n```", 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /tests/integration/main.rs: -------------------------------------------------------------------------------- 1 | mod allow; 2 | mod build_scripts; 3 | mod build_wrap_cmd_changed; 4 | mod cargo_target_dir; 5 | mod ci; 6 | mod config; 7 | mod config_allow; 8 | mod custom_build_name; 9 | mod dogfood; 10 | mod enabled; 11 | mod third_party; 12 | mod util; 13 | -------------------------------------------------------------------------------- /tests/integration/third_party.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | config, 3 | util::{test_case, TestCase, ToUtf8}, 4 | }; 5 | use anyhow::{ensure, Result}; 6 | use assert_cmd::Command; 7 | use std::{ 8 | fs::read_to_string, 9 | io::Write, 10 | path::{Path, PathBuf}, 11 | }; 12 | 13 | #[test] 14 | fn third_party() { 15 | warn_if_go_build_exists(); 16 | 17 | config::for_each_test_case("tests/third_party", |build_wrap_cmd, path, stderr| { 18 | #[allow(clippy::explicit_write)] 19 | writeln!( 20 | std::io::stderr(), 21 | "running third-party test: {}", 22 | path.display() 23 | ) 24 | .unwrap(); 25 | 26 | let file_stem = path.file_stem().unwrap(); 27 | let name = file_stem.to_utf8().unwrap(); 28 | 29 | let version = parse_version_file(path); 30 | 31 | test_case( 32 | build_wrap_cmd, 33 | &TestCase::ThirdParty(name, &version), 34 | stderr, 35 | ); 36 | 37 | Ok(()) 38 | }) 39 | .unwrap(); 40 | } 41 | 42 | fn parse_version_file(path: &Path) -> String { 43 | let contents = read_to_string(path).unwrap(); 44 | 45 | contents 46 | .lines() 47 | .map(|line| { 48 | let i = line.find('#').unwrap_or(line.len()); 49 | &line[..i] 50 | }) 51 | .collect::>() 52 | .join("") 53 | .trim() 54 | .to_owned() 55 | } 56 | 57 | fn warn_if_go_build_exists() { 58 | let Ok(go_build_path) = go_build_path() else { 59 | return; 60 | }; 61 | 62 | if go_build_path.try_exists().unwrap_or(false) { 63 | #[allow(clippy::explicit_write)] 64 | writeln!( 65 | std::io::stderr(), 66 | "`go-build` exists at `{}`; some third-party tests may fail!", 67 | go_build_path.display() 68 | ) 69 | .unwrap(); 70 | } 71 | } 72 | 73 | fn go_build_path() -> Result { 74 | let output = Command::new("go").args(["env", "GOCACHE"]).output()?; 75 | ensure!(output.status.success()); 76 | let stdout = std::str::from_utf8(&output.stdout)?; 77 | Ok(PathBuf::from(stdout.trim_end())) 78 | } 79 | -------------------------------------------------------------------------------- /tests/integration/util.rs: -------------------------------------------------------------------------------- 1 | //! This file must remain in the tests folder to ensure that `CARGO_BIN_EXE_build-wrap` is defined 2 | //! at compile time. See [Environment variables Cargo sets for crates]. 3 | //! 4 | //! [Environment variables Cargo sets for crates]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates 5 | 6 | // smoelius: Use this module with `pub` to avoid "unused ..." warnings. 7 | // See: https://users.rust-lang.org/t/invalid-dead-code-warning-for-submodule-in-integration-test/80259/2 8 | 9 | use anyhow::{Context, Result}; 10 | use cargo_metadata::{Metadata, MetadataCommand}; 11 | use snapbox::assert_data_eq; 12 | use std::{ 13 | env, 14 | ffi::OsString, 15 | fs::{copy, create_dir, write, OpenOptions}, 16 | io::Write, 17 | path::{Path, PathBuf}, 18 | process::Command, 19 | sync::LazyLock, 20 | }; 21 | 22 | #[path = "../../src/util/mod.rs"] 23 | mod main_util; 24 | pub use main_util::*; 25 | 26 | #[ctor::ctor] 27 | fn initialize() { 28 | env::set_var("CARGO_TERM_COLOR", "never"); 29 | } 30 | 31 | #[must_use] 32 | pub fn build_with_build_wrap() -> Command { 33 | let build_wrap = env!("CARGO_BIN_EXE_build-wrap"); 34 | 35 | let mut command = cargo_build(); 36 | command.args([ 37 | "--config", 38 | &format!("target.'cfg(all())'.linker = '{build_wrap}'"), 39 | ]); 40 | 41 | command 42 | } 43 | 44 | #[must_use] 45 | pub fn build_with_default_linker() -> Command { 46 | let mut command = cargo_build(); 47 | command.args([ 48 | "--config", 49 | &format!("target.'cfg(all())'.linker = '{DEFAULT_LD}'"), 50 | ]); 51 | 52 | command 53 | } 54 | 55 | pub fn temp_package<'a, 'b>( 56 | build_script_path: Option>, 57 | dependencies: impl IntoIterator, 58 | ) -> Result { 59 | let tempdir = tempdir()?; 60 | 61 | write(tempdir.path().join("Cargo.toml"), CARGO_TOML)?; 62 | if let Some(build_script_path) = build_script_path { 63 | copy(build_script_path, tempdir.path().join("build.rs"))?; 64 | } 65 | create_dir(tempdir.path().join("src"))?; 66 | write(tempdir.path().join("src/lib.rs"), "")?; 67 | 68 | let mut iter = dependencies.into_iter().peekable(); 69 | 70 | if iter.peek().is_some() { 71 | let mut file = OpenOptions::new() 72 | .append(true) 73 | .open(tempdir.path().join("Cargo.toml"))?; 74 | writeln!(file, "\n[dependencies]")?; 75 | for (name, version) in iter { 76 | writeln!(file, r#"{name} = "={version}""#)?; 77 | } 78 | } 79 | 80 | Ok(tempdir) 81 | } 82 | 83 | const CARGO_TOML: &str = r#" 84 | [package] 85 | name = "temp-package" 86 | version = "0.1.0" 87 | edition = "2021" 88 | publish = false 89 | 90 | [build-dependencies] 91 | libc = { version = "0.2", optional = true } 92 | rustc_version = { version = "0.4", optional = true } 93 | "#; 94 | 95 | static METADATA: LazyLock = 96 | LazyLock::new(|| MetadataCommand::new().no_deps().exec().unwrap()); 97 | 98 | /// Creates a temporary directory in `build-wrap`'s target directory. 99 | /// 100 | /// Useful if you want to verify that writing outside of the temporary directory is forbidden, but 101 | /// `/tmp` is writeable, for example. 102 | pub fn tempdir() -> Result { 103 | tempfile::tempdir_in(&METADATA.target_directory).map_err(Into::into) 104 | } 105 | 106 | #[derive(Debug)] 107 | pub enum TestCase<'a> { 108 | BuildScript(&'a Path), 109 | ThirdParty(&'a str, &'a str), 110 | } 111 | 112 | pub fn test_case(build_wrap_cmd: Option<&str>, test_case: &TestCase, stderr_expected: &str) { 113 | let temp_package = match *test_case { 114 | TestCase::BuildScript(path) => temp_package(Some(path), []), 115 | TestCase::ThirdParty(name, version) => temp_package(None::<&Path>, [(name, version)]), 116 | } 117 | .unwrap(); 118 | 119 | let mut command = build_with_build_wrap(); 120 | // smoelius: `--all-features` to enable optional build dependencies. 121 | command.arg("--all-features"); 122 | if let Some(build_wrap_cmd) = build_wrap_cmd { 123 | prepend_scripts_to_path(&mut command).unwrap(); 124 | command.env("BUILD_WRAP_CMD", build_wrap_cmd); 125 | } 126 | command.current_dir(&temp_package); 127 | 128 | let output = exec_forwarding_output(command, false).unwrap(); 129 | assert_eq!( 130 | stderr_expected.is_empty(), 131 | output.status.success(), 132 | "{test_case:?} failed in `{}`", 133 | temp_package.path().display() 134 | ); 135 | 136 | if stderr_expected.is_empty() { 137 | return; 138 | } 139 | 140 | let stderr_actual = std::str::from_utf8(&output.stderr).unwrap(); 141 | assert_data_eq!(stderr_actual, stderr_expected); 142 | } 143 | 144 | // smoelius: `prepend_scripts_to_path` allows `BUILD_WRAP_CMD`s to refer to files in the scripts 145 | // directory. 146 | fn prepend_scripts_to_path(command: &mut Command) -> Result<()> { 147 | let scripts = Path::new(env!("CARGO_MANIFEST_DIR")).join("scripts"); 148 | prepend_to_path(command, scripts)?; 149 | Ok(()) 150 | } 151 | 152 | pub fn prepend_to_path(command: &mut Command, path: PathBuf) -> Result<()> { 153 | let paths = prepend_path(path)?; 154 | command.env("PATH", paths); 155 | Ok(()) 156 | } 157 | 158 | pub fn prepend_path(path: PathBuf) -> Result { 159 | let paths = env::var_os("PATH").with_context(|| "`PATH` is unset")?; 160 | let paths_split = env::split_paths(&paths); 161 | let paths_chained = std::iter::once(path).chain(paths_split); 162 | let paths_joined = env::join_paths(paths_chained)?; 163 | Ok(paths_joined) 164 | } 165 | -------------------------------------------------------------------------------- /tests/markdown_link_check.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | { 4 | "pattern": "^https://(.*\\.)?ubuntu\\.com/" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tests/supply_chain.json: -------------------------------------------------------------------------------- 1 | { 2 | "crates_io_crates": { 3 | "aho-corasick": [ 4 | { 5 | "id": 189, 6 | "kind": "user", 7 | "login": "BurntSushi", 8 | "name": "Andrew Gallant" 9 | } 10 | ], 11 | "anstyle": [ 12 | { 13 | "id": 460, 14 | "kind": "team", 15 | "login": "github:rust-cli:maintainers", 16 | "name": "Maintainers" 17 | }, 18 | { 19 | "id": 6743, 20 | "kind": "user", 21 | "login": "epage", 22 | "name": "Ed Page" 23 | } 24 | ], 25 | "anyhow": [ 26 | { 27 | "id": 3618, 28 | "kind": "user", 29 | "login": "dtolnay", 30 | "name": "David Tolnay" 31 | } 32 | ], 33 | "bitflags": [ 34 | { 35 | "id": 3, 36 | "kind": "user", 37 | "login": "huonw", 38 | "name": "Huon Wilson" 39 | }, 40 | { 41 | "id": 51, 42 | "kind": "team", 43 | "login": "github:rust-lang-nursery:libs", 44 | "name": "libs" 45 | }, 46 | { 47 | "id": 3204, 48 | "kind": "user", 49 | "login": "KodrAus", 50 | "name": "Ashley Mannix" 51 | }, 52 | { 53 | "id": 4416, 54 | "kind": "team", 55 | "login": "github:bitflags:owners", 56 | "name": "owners" 57 | } 58 | ], 59 | "cfg-if": [ 60 | { 61 | "id": 1, 62 | "kind": "user", 63 | "login": "alexcrichton", 64 | "name": "Alex Crichton" 65 | }, 66 | { 67 | "id": 11, 68 | "kind": "team", 69 | "login": "github:rust-lang:libs", 70 | "name": "libs" 71 | }, 72 | { 73 | "id": 55123, 74 | "kind": "user", 75 | "login": "rust-lang-owner", 76 | "name": null 77 | } 78 | ], 79 | "equivalent": [ 80 | { 81 | "id": 539, 82 | "kind": "user", 83 | "login": "cuviper", 84 | "name": "Josh Stone" 85 | } 86 | ], 87 | "errno": [ 88 | { 89 | "id": 684, 90 | "kind": "user", 91 | "login": "lambda-fairy", 92 | "name": "Chris Wong" 93 | }, 94 | { 95 | "id": 6825, 96 | "kind": "user", 97 | "login": "sunfishcode", 98 | "name": "Dan Gohman" 99 | } 100 | ], 101 | "fastrand": [ 102 | { 103 | "id": 3341, 104 | "kind": "team", 105 | "login": "github:smol-rs:admins", 106 | "name": "admins" 107 | }, 108 | { 109 | "id": 33035, 110 | "kind": "user", 111 | "login": "taiki-e", 112 | "name": "Taiki Endo" 113 | } 114 | ], 115 | "getrandom": [ 116 | { 117 | "id": 563, 118 | "kind": "team", 119 | "login": "github:rust-random:maintainers", 120 | "name": "maintainers" 121 | }, 122 | { 123 | "id": 1234, 124 | "kind": "user", 125 | "login": "dhardy", 126 | "name": "Diggory Hardy" 127 | } 128 | ], 129 | "hashbrown": [ 130 | { 131 | "id": 2915, 132 | "kind": "user", 133 | "login": "Amanieu", 134 | "name": "Amanieu d'Antras" 135 | }, 136 | { 137 | "id": 55123, 138 | "kind": "user", 139 | "login": "rust-lang-owner", 140 | "name": null 141 | } 142 | ], 143 | "home": [ 144 | { 145 | "id": 443, 146 | "kind": "user", 147 | "login": "brson", 148 | "name": "Brian Anderson" 149 | }, 150 | { 151 | "id": 3959, 152 | "kind": "user", 153 | "login": "LucioFranco", 154 | "name": "Lucio Franco" 155 | }, 156 | { 157 | "id": 6202, 158 | "kind": "user", 159 | "login": "ehuss", 160 | "name": "Eric Huss" 161 | }, 162 | { 163 | "id": 45329, 164 | "kind": "user", 165 | "login": "kinnison", 166 | "name": "Daniel Silverstone" 167 | }, 168 | { 169 | "id": 55123, 170 | "kind": "user", 171 | "login": "rust-lang-owner", 172 | "name": null 173 | } 174 | ], 175 | "indexmap": [ 176 | { 177 | "id": 356, 178 | "kind": "user", 179 | "login": "bluss", 180 | "name": "bluss" 181 | }, 182 | { 183 | "id": 539, 184 | "kind": "user", 185 | "login": "cuviper", 186 | "name": "Josh Stone" 187 | } 188 | ], 189 | "libc": [ 190 | { 191 | "id": 3, 192 | "kind": "user", 193 | "login": "huonw", 194 | "name": "Huon Wilson" 195 | }, 196 | { 197 | "id": 11, 198 | "kind": "team", 199 | "login": "github:rust-lang:libs", 200 | "name": "libs" 201 | }, 202 | { 203 | "id": 3623, 204 | "kind": "team", 205 | "login": "github:rust-lang:libc", 206 | "name": "libc" 207 | }, 208 | { 209 | "id": 4333, 210 | "kind": "user", 211 | "login": "joshtriplett", 212 | "name": "Josh Triplett" 213 | }, 214 | { 215 | "id": 5725, 216 | "kind": "user", 217 | "login": "gnzlbg", 218 | "name": "gnzlbg" 219 | }, 220 | { 221 | "id": 51017, 222 | "kind": "user", 223 | "login": "JohnTitor", 224 | "name": "Yuki Okushi" 225 | }, 226 | { 227 | "id": 55123, 228 | "kind": "user", 229 | "login": "rust-lang-owner", 230 | "name": null 231 | } 232 | ], 233 | "linux-raw-sys": [ 234 | { 235 | "id": 6825, 236 | "kind": "user", 237 | "login": "sunfishcode", 238 | "name": "Dan Gohman" 239 | } 240 | ], 241 | "memchr": [ 242 | { 243 | "id": 189, 244 | "kind": "user", 245 | "login": "BurntSushi", 246 | "name": "Andrew Gallant" 247 | } 248 | ], 249 | "once_cell": [ 250 | { 251 | "id": 2699, 252 | "kind": "user", 253 | "login": "matklad", 254 | "name": "Alex Kladov" 255 | }, 256 | { 257 | "id": 5906, 258 | "kind": "user", 259 | "login": "vorner", 260 | "name": "Michal 'vorner' Vaner" 261 | } 262 | ], 263 | "proc-macro2": [ 264 | { 265 | "id": 3618, 266 | "kind": "user", 267 | "login": "dtolnay", 268 | "name": "David Tolnay" 269 | } 270 | ], 271 | "quote": [ 272 | { 273 | "id": 3618, 274 | "kind": "user", 275 | "login": "dtolnay", 276 | "name": "David Tolnay" 277 | } 278 | ], 279 | "r-efi": [ 280 | { 281 | "id": 12177, 282 | "kind": "user", 283 | "login": "dvdhrm", 284 | "name": "David Rheinsberg" 285 | } 286 | ], 287 | "regex": [ 288 | { 289 | "id": 1, 290 | "kind": "user", 291 | "login": "alexcrichton", 292 | "name": "Alex Crichton" 293 | }, 294 | { 295 | "id": 11, 296 | "kind": "team", 297 | "login": "github:rust-lang:libs", 298 | "name": "libs" 299 | }, 300 | { 301 | "id": 19, 302 | "kind": "team", 303 | "login": "github:rust-lang-nursery:regex-owners", 304 | "name": "regex-owners" 305 | }, 306 | { 307 | "id": 189, 308 | "kind": "user", 309 | "login": "BurntSushi", 310 | "name": "Andrew Gallant" 311 | } 312 | ], 313 | "regex-automata": [ 314 | { 315 | "id": 189, 316 | "kind": "user", 317 | "login": "BurntSushi", 318 | "name": "Andrew Gallant" 319 | } 320 | ], 321 | "regex-syntax": [ 322 | { 323 | "id": 1, 324 | "kind": "user", 325 | "login": "alexcrichton", 326 | "name": "Alex Crichton" 327 | }, 328 | { 329 | "id": 11, 330 | "kind": "team", 331 | "login": "github:rust-lang:libs", 332 | "name": "libs" 333 | }, 334 | { 335 | "id": 19, 336 | "kind": "team", 337 | "login": "github:rust-lang-nursery:regex-owners", 338 | "name": "regex-owners" 339 | }, 340 | { 341 | "id": 189, 342 | "kind": "user", 343 | "login": "BurntSushi", 344 | "name": "Andrew Gallant" 345 | } 346 | ], 347 | "rustix": [ 348 | { 349 | "id": 6825, 350 | "kind": "user", 351 | "login": "sunfishcode", 352 | "name": "Dan Gohman" 353 | } 354 | ], 355 | "serde": [ 356 | { 357 | "id": 3618, 358 | "kind": "user", 359 | "login": "dtolnay", 360 | "name": "David Tolnay" 361 | }, 362 | { 363 | "id": 8138, 364 | "kind": "team", 365 | "login": "github:serde-rs:publish", 366 | "name": "publish" 367 | } 368 | ], 369 | "serde_derive": [ 370 | { 371 | "id": 3618, 372 | "kind": "user", 373 | "login": "dtolnay", 374 | "name": "David Tolnay" 375 | }, 376 | { 377 | "id": 8138, 378 | "kind": "team", 379 | "login": "github:serde-rs:publish", 380 | "name": "publish" 381 | } 382 | ], 383 | "serde_spanned": [ 384 | { 385 | "id": 6743, 386 | "kind": "user", 387 | "login": "epage", 388 | "name": "Ed Page" 389 | }, 390 | { 391 | "id": 6908, 392 | "kind": "team", 393 | "login": "github:toml-rs:maintainers", 394 | "name": "Maintainers" 395 | } 396 | ], 397 | "syn": [ 398 | { 399 | "id": 3618, 400 | "kind": "user", 401 | "login": "dtolnay", 402 | "name": "David Tolnay" 403 | } 404 | ], 405 | "tempfile": [ 406 | { 407 | "id": 280, 408 | "kind": "user", 409 | "login": "Stebalien", 410 | "name": "Steven Allen" 411 | }, 412 | { 413 | "id": 3204, 414 | "kind": "user", 415 | "login": "KodrAus", 416 | "name": "Ashley Mannix" 417 | } 418 | ], 419 | "toml": [ 420 | { 421 | "id": 6202, 422 | "kind": "user", 423 | "login": "ehuss", 424 | "name": "Eric Huss" 425 | }, 426 | { 427 | "id": 6743, 428 | "kind": "user", 429 | "login": "epage", 430 | "name": "Ed Page" 431 | }, 432 | { 433 | "id": 6908, 434 | "kind": "team", 435 | "login": "github:toml-rs:maintainers", 436 | "name": "Maintainers" 437 | } 438 | ], 439 | "toml_datetime": [ 440 | { 441 | "id": 6743, 442 | "kind": "user", 443 | "login": "epage", 444 | "name": "Ed Page" 445 | }, 446 | { 447 | "id": 6908, 448 | "kind": "team", 449 | "login": "github:toml-rs:maintainers", 450 | "name": "Maintainers" 451 | } 452 | ], 453 | "toml_edit": [ 454 | { 455 | "id": 6743, 456 | "kind": "user", 457 | "login": "epage", 458 | "name": "Ed Page" 459 | }, 460 | { 461 | "id": 6908, 462 | "kind": "team", 463 | "login": "github:toml-rs:maintainers", 464 | "name": "Maintainers" 465 | }, 466 | { 467 | "id": 7434, 468 | "kind": "user", 469 | "login": "ordian", 470 | "name": null 471 | } 472 | ], 473 | "toml_write": [ 474 | { 475 | "id": 6743, 476 | "kind": "user", 477 | "login": "epage", 478 | "name": "Ed Page" 479 | }, 480 | { 481 | "id": 6908, 482 | "kind": "team", 483 | "login": "github:toml-rs:maintainers", 484 | "name": "Maintainers" 485 | } 486 | ], 487 | "unicode-ident": [ 488 | { 489 | "id": 3618, 490 | "kind": "user", 491 | "login": "dtolnay", 492 | "name": "David Tolnay" 493 | } 494 | ], 495 | "wasi": [ 496 | { 497 | "id": 1, 498 | "kind": "user", 499 | "login": "alexcrichton", 500 | "name": "Alex Crichton" 501 | }, 502 | { 503 | "id": 6825, 504 | "kind": "user", 505 | "login": "sunfishcode", 506 | "name": "Dan Gohman" 507 | } 508 | ], 509 | "windows-sys": [ 510 | { 511 | "id": 64539, 512 | "kind": "user", 513 | "login": "kennykerr", 514 | "name": "Kenny Kerr" 515 | } 516 | ], 517 | "windows-targets": [ 518 | { 519 | "id": 64539, 520 | "kind": "user", 521 | "login": "kennykerr", 522 | "name": "Kenny Kerr" 523 | } 524 | ], 525 | "windows_aarch64_gnullvm": [ 526 | { 527 | "id": 64539, 528 | "kind": "user", 529 | "login": "kennykerr", 530 | "name": "Kenny Kerr" 531 | } 532 | ], 533 | "windows_aarch64_msvc": [ 534 | { 535 | "id": 64539, 536 | "kind": "user", 537 | "login": "kennykerr", 538 | "name": "Kenny Kerr" 539 | } 540 | ], 541 | "windows_i686_gnu": [ 542 | { 543 | "id": 64539, 544 | "kind": "user", 545 | "login": "kennykerr", 546 | "name": "Kenny Kerr" 547 | } 548 | ], 549 | "windows_i686_gnullvm": [ 550 | { 551 | "id": 64539, 552 | "kind": "user", 553 | "login": "kennykerr", 554 | "name": "Kenny Kerr" 555 | } 556 | ], 557 | "windows_i686_msvc": [ 558 | { 559 | "id": 64539, 560 | "kind": "user", 561 | "login": "kennykerr", 562 | "name": "Kenny Kerr" 563 | } 564 | ], 565 | "windows_x86_64_gnu": [ 566 | { 567 | "id": 64539, 568 | "kind": "user", 569 | "login": "kennykerr", 570 | "name": "Kenny Kerr" 571 | } 572 | ], 573 | "windows_x86_64_gnullvm": [ 574 | { 575 | "id": 64539, 576 | "kind": "user", 577 | "login": "kennykerr", 578 | "name": "Kenny Kerr" 579 | } 580 | ], 581 | "windows_x86_64_msvc": [ 582 | { 583 | "id": 64539, 584 | "kind": "user", 585 | "login": "kennykerr", 586 | "name": "Kenny Kerr" 587 | } 588 | ], 589 | "winnow": [ 590 | { 591 | "id": 6743, 592 | "kind": "user", 593 | "login": "epage", 594 | "name": "Ed Page" 595 | } 596 | ], 597 | "wit-bindgen-rt": [ 598 | { 599 | "id": 1, 600 | "kind": "user", 601 | "login": "alexcrichton", 602 | "name": "Alex Crichton" 603 | }, 604 | { 605 | "id": 2633, 606 | "kind": "team", 607 | "login": "github:bytecodealliance:wasmtime-publish", 608 | "name": "wasmtime-publish" 609 | } 610 | ], 611 | "xdg": [ 612 | { 613 | "id": 654, 614 | "kind": "user", 615 | "login": "whitequark", 616 | "name": "whitequark" 617 | } 618 | ] 619 | }, 620 | "not_audited": { 621 | "foreign_crates": [], 622 | "local_crates": [ 623 | "build-wrap" 624 | ] 625 | } 626 | } -------------------------------------------------------------------------------- /tests/third_party/aws-lc-fips-sys.txt: -------------------------------------------------------------------------------- 1 | # smoelius: https://github.com/rust-lang/docs.rs/issues/2513#issuecomment-2132648674 2 | 3 | # smoelius: Pinning to version 0.12.14 as the bug seems to have been fixed with version 0.12.15: 4 | # https://github.com/aws/aws-lc-rs/commit/c236deb20dc969bc1fcfe3458aa6031790efcec9 5 | 6 | 0.12.14 7 | -------------------------------------------------------------------------------- /tests/third_party/lief.txt: -------------------------------------------------------------------------------- 1 | # smoelius: https://github.com/rust-lang/docs.rs/issues/2563#issuecomment-2241723588 2 | 3 | 0.15 4 | -------------------------------------------------------------------------------- /tests/third_party/linux-sandboxer/aws-lc-fips-sys.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | failed to initialize build cache at [..]/.cache/go-build: mkdir [..]/.cache[..]: permission denied 3 | ... 4 | -------------------------------------------------------------------------------- /tests/third_party/linux-sandboxer/config.toml: -------------------------------------------------------------------------------- 1 | target_os = "linux" 2 | 3 | build_wrap_cmd = "sandboxer.sh --fs-ro=/ --fs-rw=/dev:{OUT_DIR}:/tmp --tcp-connect=0 -- {}" 4 | -------------------------------------------------------------------------------- /tests/third_party/linux-sandboxer/lief.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | failed to download LIEF cache: reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("github.com")), port: None, path: "/lief-project/LIEF/releases/download/[..]/LIEF-rs-x86_64-unknown-linux-gnu.zip", query: None, fragment: None }, source: hyper::Error(Connect, ConnectError("tcp connect error", Os { code: 13, kind: PermissionDenied, message: "Permission denied" })) } 3 | ... 4 | -------------------------------------------------------------------------------- /tests/third_party/linux-sandboxer/psm.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/third_party/linux-sandboxer/psm.stderr -------------------------------------------------------------------------------- /tests/third_party/linux/aws-lc-fips-sys.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | failed to initialize build cache at [..]/.cache/go-build: mkdir [..]/.cache[..]: read-only file system 3 | ... 4 | -------------------------------------------------------------------------------- /tests/third_party/linux/config.toml: -------------------------------------------------------------------------------- 1 | target_os = "linux" 2 | -------------------------------------------------------------------------------- /tests/third_party/linux/lief.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | failed to download LIEF cache: reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("github.com")), port: None, path: "/lief-project/LIEF/releases/download/[..]/LIEF-rs-x86_64-unknown-linux-gnu.zip", query: None, fragment: None }, source: hyper::Error(Connect, ConnectError("dns error", Custom { kind: Uncategorized, error: "failed to lookup address information: Temporary failure in name resolution" })) } 3 | ... 4 | -------------------------------------------------------------------------------- /tests/third_party/linux/psm.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/third_party/linux/psm.stderr -------------------------------------------------------------------------------- /tests/third_party/macos/aws-lc-fips-sys.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | failed to initialize build cache at [..]/Library/Caches/go-build: mkdir [..]/Library/Caches/go-build: operation not permitted 3 | ... 4 | -------------------------------------------------------------------------------- /tests/third_party/macos/config.toml: -------------------------------------------------------------------------------- 1 | target_os = "macos" 2 | -------------------------------------------------------------------------------- /tests/third_party/macos/lief.stderr: -------------------------------------------------------------------------------- 1 | ... 2 | thread 'reqwest-internal-sync-runtime' panicked at [..]: 3 | Attempted to create a NULL object. 4 | note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 5 | 6 | thread 'main' panicked at [..]: 7 | event loop thread panicked 8 | ... 9 | -------------------------------------------------------------------------------- /tests/third_party/macos/psm.stderr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/build-wrap/bec54077c0cec97e112549d9491682c90b151146/tests/third_party/macos/psm.stderr -------------------------------------------------------------------------------- /tests/third_party/psm.txt: -------------------------------------------------------------------------------- 1 | # smoelius: `psm` (https://crates.io/crates/psm) is an example of a package that requires write 2 | # access to `PRIVATE_TMPDIR` to build. 3 | 4 | 0.1 5 | --------------------------------------------------------------------------------