Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
devel:languages:rust
cargo-auditable
cargo-auditable-0.6.2~0.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File cargo-auditable-0.6.2~0.obscpio of Package cargo-auditable
07070100000000000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002000000000cargo-auditable-0.6.2~0/.github07070100000001000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002A00000000cargo-auditable-0.6.2~0/.github/workflows07070100000002000081A400000000000000000000000165D39F85000001E2000000000000000000000000000000000000003400000000cargo-auditable-0.6.2~0/.github/workflows/check.ymlname: Check on: pull_request: {} push: branches: master jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Run cargo check run: cargo check --locked - name: Run cargo clippy run: cargo clippy -- -D warnings - name: Check format run: cargo fmt -- --check 07070100000003000081A400000000000000000000000165D39F8500000718000000000000000000000000000000000000003400000000cargo-auditable-0.6.2~0/.github/workflows/linux.ymlname: Linux on: pull_request: {} push: branches: master jobs: test: strategy: matrix: platform: - ubuntu-latest toolchain: - stable runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - uses: actions/cache@v1 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} - uses: actions/cache@v1 with: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} profile: minimal override: true # multiple additional targets are not supported, so we invoke the action multiple times # Feature request: https://github.com/actions-rs/toolchain/issues/165 - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} profile: minimal target: "i686-unknown-linux-gnu" - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} profile: minimal target: "x86_64-unknown-linux-musl" - name: "Test on the native x86_64-unknown-linux-gnu" run: cargo test --all-features --workspace - name: "Test cross-compiling to x86_64-unknown-linux-musl" env: AUDITABLE_TEST_TARGET: "x86_64-unknown-linux-musl" run: cargo test --all-features --workspace - name: "Install the 32-bit GCC toolchain" run: sudo apt-get install gcc-multilib - name: "Test cross-compiling to i686-unknown-linux-gnu" env: AUDITABLE_TEST_TARGET: "i686-unknown-linux-gnu" run: cargo test --all-features --workspace 07070100000004000081A400000000000000000000000165D39F8500000306000000000000000000000000000000000000003200000000cargo-auditable-0.6.2~0/.github/workflows/mac.ymlname: Mac on: pull_request: {} push: branches: master jobs: test: strategy: matrix: platform: - macos-latest toolchain: - stable runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - uses: actions/cache@v1 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} - uses: actions/cache@v1 with: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} profile: minimal override: true - run: cargo test --all-features --workspace 07070100000005000081A400000000000000000000000165D39F850000042C000000000000000000000000000000000000003600000000cargo-auditable-0.6.2~0/.github/workflows/windows.ymlname: Windows on: pull_request: {} push: branches: master jobs: test: strategy: matrix: platform: - windows-latest toolchain: - stable runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - uses: actions/cache@v1 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} - uses: actions/cache@v1 with: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} profile: minimal override: true target: "x86_64-pc-windows-gnu" - name: "Test on the native x86_64-pc-windows-mscv" run: cargo test --all-features --workspace - name: "Test when cross-compiling to x86_64-pc-windows-gnu" env: AUDITABLE_TEST_TARGET: "x86_64-pc-windows-gnu" run: cargo test --all-features --workspace 07070100000006000081A400000000000000000000000165D39F8500000007000000000000000000000000000000000000002300000000cargo-auditable-0.6.2~0/.gitignoretarget 07070100000007000081A400000000000000000000000165D39F8500001572000000000000000000000000000000000000002B00000000cargo-auditable-0.6.2~0/CODE_OF_CONDUCT.md # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at rust-mods@rust-lang.org. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations 07070100000008000081A400000000000000000000000165D39F85000003E5000000000000000000000000000000000000002800000000cargo-auditable-0.6.2~0/CONTRIBUTING.md# Contributing to `cargo auditable` We're happy to accept contributions! We're looking for issue reports and general feedback, **not just code.** If `cargo auditable` doesn't work for you or doesn't fulfill all your requirements, please let us know! If you're planning a big change to the code and would like to check with us first, please [open an issue](https://github.com/rust-secure-code/cargo-auditable/issues/new). If you need help, or would like to chat with us, please talk to us in [`#wg-secure-code` on Rust Zulip](https://rust-lang.zulipchat.com/#narrow/stream/146229-wg-secure-code). ## Tips and tricks To avoid running `cargo install` every time you want to rebuild and test a change, you can invoke the binary directly. So instead of this: ``` cargo install --path . cargo auditable FLAGS ``` you can use ``` cargo build --release target/release/cargo-auditable auditable FLAGS ``` which does not replace the stable version of `cargo auditable` that you may have installed. 07070100000009000081A400000000000000000000000165D39F8500002EAB000000000000000000000000000000000000002300000000cargo-auditable-0.6.2~0/Cargo.lock# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", "once_cell", "version_check", ] [[package]] name = "auditable-extract" version = "0.3.2" dependencies = [ "binfarce", ] [[package]] name = "auditable-info" version = "0.7.0" dependencies = [ "auditable-extract", "auditable-serde", "miniz_oxide", "serde_json", ] [[package]] name = "auditable-serde" version = "0.6.1" dependencies = [ "cargo-lock", "cargo_metadata", "schemars", "semver", "serde", "serde_json", "topological-sort", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "binfarce" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18464ccbb85e5dede30d70cc7676dc9950a0fb7dbf595a43d765be9123c616a2" [[package]] name = "camino" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" dependencies = [ "serde", ] [[package]] name = "cargo-auditable" version = "0.6.2" dependencies = [ "auditable-info", "auditable-serde", "cargo_metadata", "miniz_oxide", "object", "pico-args", "serde", "serde_json", "which", ] [[package]] name = "cargo-lock" version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e11c675378efb449ed3ce8de78d75d0d80542fc98487c26aba28eb3b82feac72" dependencies = [ "semver", "serde", "toml", "url", ] [[package]] name = "cargo-platform" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36" dependencies = [ "camino", "cargo-platform", "semver", "serde", "serde_json", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "dyn-clone" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "form_urlencoded" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ "percent-encoding", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] [[package]] name = "idna" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", "hashbrown 0.12.3", ] [[package]] name = "itoa" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "libc" version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "miniz_oxide" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "object" version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "crc32fast", "hashbrown 0.13.2", "indexmap", "memchr", ] [[package]] name = "once_cell" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" [[package]] name = "percent-encoding" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pico-args" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "proc-macro2" version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "schemars" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" dependencies = [ "dyn-clone", "schemars_derive", "serde", "serde_json", ] [[package]] name = "schemars_derive" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", "syn", ] [[package]] name = "semver" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" dependencies = [ "serde", ] [[package]] name = "serde" version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_derive_internals" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" dependencies = [ "serde", ] [[package]] name = "syn" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "toml" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "topological-sort" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" [[package]] name = "unicode-bidi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "url" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "which" version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", "libc", "once_cell", ] [[package]] name = "winnow" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" dependencies = [ "memchr", ] 0707010000000A000081A400000000000000000000000165D39F8500000078000000000000000000000000000000000000002300000000cargo-auditable-0.6.2~0/Cargo.toml[workspace] members = [ "auditable-info", "auditable-extract", "auditable-serde", "cargo-auditable", ] 0707010000000B000081A400000000000000000000000165D39F8500002A5F000000000000000000000000000000000000002700000000cargo-auditable-0.6.2~0/LICENSE-APACHE Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 0707010000000C000081A400000000000000000000000165D39F850000042E000000000000000000000000000000000000002400000000cargo-auditable-0.6.2~0/LICENSE-MITCopyright (c) 2020 Sergey "Shnatsel" Davidoff Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 0707010000000D000081A400000000000000000000000165D39F8500000EE8000000000000000000000000000000000000002300000000cargo-auditable-0.6.2~0/PARSING.md## Parsing the data embedded by `cargo auditable` Since the format simply uses Zlib and JSON, implementing a parser should be trivial. This is a barebones parser written in Python: ```python3 import lief, zlib, json binary = lief.parse("/path/to/file") audit_data_section = next(filter(lambda section: section.name == ".dep-v0", binary.sections)) json_string = zlib.decompress(audit_data_section.content) audit_data = json.loads(json_string) ``` On Linux you can even kludge together a parser for Linux binaries in the shell, if you can't use [`rust-audit-info`](rust-audit-info/README.md): ```bash objcopy --dump-section .dep-v0=/dev/stdout $1 | pigz -zd - ``` ### Step 0: Check if a parser already exists The following parsing libraries are available: - [`auditable-info`](https://docs.rs/auditable-info/) in Rust - [`go-rustaudit`](https://github.com/microsoft/go-rustaudit) in Go We also provide a standalone binary [`rust-audit-info`](rust-audit-info/README.md) that can be called as a subprocess from any language. It will handle all the binary wrangling for you and output the JSON. ### Step 1: Obtain the compressed data from the binary Use your language's recommended ELF/Mach-O/PE parser to extract the `.dep-v0` section from the executable. On Apple platforms (in Mach-O format) this section is in the `__DATA` segment; other formats do not have the concept of segments. ### Step 2: Decompress the data The data is [Zlib](https://en.wikipedia.org/wiki/Zlib)-compressed. Simply decompress it. If you want to protect your process from memory exhaustion, limit the size of the output to avoid [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). 8 MiB should be more than enough to hold any legitimate audit data. ### Step 3: Deserialize the JSON Parse the decompressed data to JSON. A well-formed JSON is guaranteed to be UTF-8; rejecting non-UTF-8 data is valid behavior for the parser. The JSON schema is available [here](cargo-auditable.schema.json). ### Security considerations #### Reconstructing the dependency tree If your use case calls not just for obtaining the versions of the crates used in the build, but also for reconstructing the dependency tree, you need to validate the data first. The format technically allows encoding the following invalid states: 1. Zero root packages 1. More than one root package 1. Cyclic dependencies Before you walk the dependency tree, make sure that the dependency graph does not contain cycles - for example, by performing [topological sorting](https://en.wikipedia.org/wiki/Topological_sorting) - and that there is only one package with `root: true`. (We have experimented with formats that do not allow encoding cyclic dependencies, but they turned out no easier to work with - the same issues occur and have to be dealt with, just in different places. They were also less amenable to compression.) #### Binary parsing Many binary parsing libraries are not designed with security in mind, and were never expected to be exposed to malicious input. This makes them [trivially exploitable](https://lcamtuf.blogspot.com/2014/10/psa-dont-run-strings-on-untrusted-files.html) for arbitrary code execution. Binary parsing in particular is a hotbed for memory safety bugs. If the ELF/PE/Mach-O parser in your language is a big old pile of C, consider using our Rust library instead, which was specifically designed for resilience to malicious inputs. It is implemented in 100% safe Rust, including all dependencies, so it is not susceptible to such issues. You can do that either by calling [`rust-audit-info`](rust-audit-info/README.md) as a subprocess, or by writing bindings to the [`auditable-info`](https://docs.rs/auditable-info/) library crate using the bindigns generator for your language - just google "call Rust from $LANGUAGE".0707010000000E000081A400000000000000000000000165D39F8500001851000000000000000000000000000000000000002200000000cargo-auditable-0.6.2~0/README.md## cargo-auditable Know the exact crate versions used to build your Rust executable. Audit binaries for known bugs or security vulnerabilities in production, at scale, with zero bookkeeping. This works by embedding data about the dependency tree in JSON format into a dedicated linker section of the compiled executable. Linux, Windows and Mac OS are officially supported. All other ELF targets should work, but are not tested on CI. WASM is currently not supported, but patches are welcome. The end goal is to get Cargo itself to encode this information in binaries. There is an RFC for an implementation within Cargo, for which this project paves the way: https://github.com/rust-lang/rfcs/pull/2801 ## Usage ```bash # Install the tools cargo install cargo-auditable cargo-audit # Build your project with dependency lists embedded in the binaries cargo auditable build --release # Scan the binary for vulnerabilities cargo audit bin target/release/your-project ``` `cargo auditable` works with any Cargo command. All arguments are passed to `cargo` as-is. ## FAQ ### Doesn't this bloat my binary? In a word, no. The embedded dependency list uses under 4kB even on large dependency trees with 400+ entries. This typically translates to between 1/1000 and 1/10,000 of the size of the binary. ### Can I make `cargo` always build with `cargo auditable`? Yes! For example, on Linux/macOS/etc add this to your `.bashrc`: ```bash alias cargo="cargo auditable" ``` If you're using a shell other than bash, or if using an alias is not an option, [see here.](REPLACING_CARGO.md) ### Is there any tooling to consume this data? #### Vulnerability reporting * [cargo audit](https://crates.io/crates/cargo-audit) v0.17.3+ can detect this data in binaries and report on vulnerabilities. See [here](https://github.com/rustsec/rustsec/tree/main/cargo-audit#cargo-audit-bin-subcommand) for details. * [trivy](https://github.com/aquasecurity/trivy) v0.31.0+ detects this data in binaries and reports on vulnerabilities. See the [v0.31.0 release notes](https://github.com/aquasecurity/trivy/discussions/2716) for an end-to-end example. #### Recovering the dependency list * [syft](https://github.com/anchore/syft) v0.53.0+ has experimental support for detecting this data in binaries. When used on images or directories, Rust audit support must be enabled by adding the `--catalogers all` CLI option, e.g `syft --catalogers all <container image containing Rust auditable binary>`. * [rust-audit-info](https://crates.io/crates/rust-audit-info) recovers the dependency list from a binary and prints it in JSON. It is also interoperable with existing tooling that consumes Cargo.lock via the [JSON-to-TOML convertor](auditable-serde/examples/json-to-toml.rs). However, we recommend supporting the format natively; the format is designed to be [very easy to parse](PARSING.md), even if your language does not have a library for that yet. ### Can I read this data using a tool written in a different language? Yes. The data format is designed for interoperability with alternative implementations. In fact, parsing it only takes [5 lines of Python](PARSING.md). See [here](PARSING.md) for documentation on parsing the data. ### What is the data format, exactly? The data format is described by the JSON schema [here](cargo-auditable.schema.json). The JSON is Zlib-compressed and placed in a linker section named `.dep-v0`. You can find more info about parsing it [here](PARSING.md). ### What about embedded platforms? Embedded platforms where you cannot spare a byte should not add anything in the executable. Instead they should record the hash of every executable in a database and associate the hash with its Cargo.lock, compiler and LLVM version, build date, etc. This would make for an excellent Cargo wrapper or plugin. Since that can be done in a 5-line shell script, writing that tool is left as an exercise to the reader. ### Does this impact reproducible builds? The data format is specifically designed not to disrupt reproducible builds. It contains no timestamps, and the generated JSON is sorted to make sure it is identical between compilations. If anything, this *helps* with reproducible builds, since you know all the versions for a given binary now. ### Does this disclose any sensitive information? No. All URLs and file paths are redacted, but the crate names and versions are recorded as-is. At present panic messages already disclose all this info and more. Also, chances are that you're legally obligated have to disclose use of specific open-source crates anyway, since MIT and many other licenses require it. ### What about recording the compiler version? The compiler itself [will start embedding it soon.](https://github.com/rust-lang/rust/pull/97550) On older versions it's already there in the debug info. On Unix you can run `strings your_executable | grep 'rustc version'` to see it. ### What about keeping track of versions of statically linked C libraries? Good question. I don't think they are exposed in any reasonable way right now. Would be a great addition, but not required for the initial launch. We can add it later in a backwards-compatible way. Adopting [the `-src` crate convention](https://internals.rust-lang.org/t/statically-linked-c-c-libraries/17175?u=shnatsel) would make it happen naturally, and will have other benefits as well, so that's probably the best route. ### Does this protect against supply chain attacks? No. Use [`cargo-vet`](https://github.com/mozilla/cargo-vet) or [`cargo-crev`](https://github.com/crev-dev/cargo-crev) for that. [Software Bills of Materials](https://en.wikipedia.org/wiki/Software_supply_chain) (SBOMs) do not prevent supply chain attacks. They cannot even be used to assess the impact of such an attack after it is discovered, because any malicious library worth its bytes will remove itself from the SBOM. This applies to nearly every language and build system, not just Rust and Cargo. Do not rely on SBOMs when dealing with supply chain attacks! ### What is blocking uplifting this into Cargo? Cargo itself is [currently in a feature freeze](https://blog.rust-lang.org/inside-rust/2022/03/31/cargo-team-changes.html). 0707010000000F000081A400000000000000000000000165D39F8500000570000000000000000000000000000000000000002B00000000cargo-auditable-0.6.2~0/REPLACING_CARGO.md# Using `cargo auditable` as a drop-in replacement for `cargo` **Note:** This document describes Unix-like systems, but similar approaches can be applied to Windows as well. Pull requests adding recipes for Windows are welcome. The recommended way is to use a shell alias: ```bash alias cargo="cargo auditable" ``` When entered into the shell, it will only persist for the duration of the session. To make the change permanent, add it to your shell's configuration file (`.bashrc` for bash, `.zshrc` for zsh, `.config/fish/config.fish` for fish). ## When `alias` is not an option In some cases using shell aliases is not an option, e.g. in certain restricted build environments. In this case you can use a different approach: 1. Run `which cargo` to locate the Cargo binary 2. Copy the snippet provided below and replace '/path/to/cargo' with the path you got at step 1 3. Save it to a file named `cargo` 4. Run `chmod +x cargo` to make the script executable 5. Prepend the path to the directory where you saved the script to your `PATH` environment variable. For example, if you saved the script as `$HOME/.bin/cargo`, you need to add `$HOME/.bin/` to your `PATH`. The exact way to do this varies depending on the shell; in bash it's `export PATH="$HOME/.bin/:$PATH"` ```bash #!/bin/sh export CARGO='/path/to/real/cargo' # replace this with your path cargo-auditable auditable "$@" ```07070100000010000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002A00000000cargo-auditable-0.6.2~0/auditable-extract07070100000011000081A400000000000000000000000165D39F8500000008000000000000000000000000000000000000003500000000cargo-auditable-0.6.2~0/auditable-extract/.gitignore/target 07070100000012000081A400000000000000000000000165D39F85000001DD000000000000000000000000000000000000003500000000cargo-auditable-0.6.2~0/auditable-extract/Cargo.toml[package] name = "auditable-extract" version = "0.3.2" authors = ["Sergey \"Shnatsel\" Davidoff <shnatsel@gmail.com>"] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-secure-code/cargo-auditable" description = "Extract the dependency trees embedded in binaries by `cargo auditable`" categories = ["encoding"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] binfarce = "0.2" 07070100000013000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002E00000000cargo-auditable-0.6.2~0/auditable-extract/src07070100000014000081A400000000000000000000000165D39F850000146A000000000000000000000000000000000000003500000000cargo-auditable-0.6.2~0/auditable-extract/src/lib.rs#![forbid(unsafe_code)] //! Extracts the dependency tree information embedded in executables by //! [`cargo auditable`](https://github.com/rust-secure-code/cargo-auditable). //! //! This crate parses platform-specific binary formats ([ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format), //! [PE](https://en.wikipedia.org/wiki/Portable_Executable), //! [Mach-O](https://en.wikipedia.org/wiki/Mach-O)) and obtains the compressed audit data. //! //! Unlike other binary parsing crates, it is specifically designed to be resilient to malicious input. //! It 100% safe Rust (including all dependencies) and performs no heap allocations. //! //! ## Usage //! //! **Note:** this is a low-level crate that only implements binary parsing. It rarely should be used directly. //! You probably want the higher-level [`auditable-info`](https://docs.rs/auditable-info) crate instead. //! //! The following snippet demonstrates full extraction pipeline, including decompression //! using the safe-Rust [`miniz_oxide`](http://docs.rs/miniz_oxide/) and optional JSON parsing //! via [`auditable-serde`](http://docs.rs/auditable-serde/): //! //! ```rust,ignore //! use std::io::{Read, BufReader}; //! use std::{error::Error, fs::File, str::FromStr}; //! //! fn main() -> Result<(), Box<dyn Error>> { //! // Read the input //! let f = File::open("target/release/hello-world")?; //! let mut f = BufReader::new(f); //! let mut input_binary = Vec::new(); //! f.read_to_end(&mut input_binary)?; //! // Extract the compressed audit data //! let compressed_audit_data = auditable_extract::raw_auditable_data(&input_binary)?; //! // Decompress it with your Zlib implementation of choice. We recommend miniz_oxide //! use miniz_oxide::inflate::decompress_to_vec_zlib; //! let decompressed_data = decompress_to_vec_zlib(&compressed_audit_data) //! .map_err(|_| "Failed to decompress audit data")?; //! let decompressed_data = String::from_utf8(decompressed_data)?; //! println!("{}", decompressed_data); //! // Parse the audit data to Rust data structures //! let dependency_tree = auditable_serde::VersionInfo::from_str(&decompressed_data); //! Ok(()) //! } //! ``` use binfarce::Format; /// Extracts the Zlib-compressed dependency info from an executable. /// /// This function does not allocate any memory on the heap and can be safely given untrusted input. pub fn raw_auditable_data(data: &[u8]) -> Result<&[u8], Error> { match binfarce::detect_format(data) { Format::Elf32 { byte_order } => { let section = binfarce::elf32::parse(data, byte_order)? .section_with_name(".dep-v0")? .ok_or(Error::NoAuditData)?; Ok(data.get(section.range()?).ok_or(Error::UnexpectedEof)?) } Format::Elf64 { byte_order } => { let section = binfarce::elf64::parse(data, byte_order)? .section_with_name(".dep-v0")? .ok_or(Error::NoAuditData)?; Ok(data.get(section.range()?).ok_or(Error::UnexpectedEof)?) } Format::Macho => { let parsed = binfarce::macho::parse(data)?; let section = parsed.section_with_name("__DATA", ".dep-v0")?; let section = section.ok_or(Error::NoAuditData)?; Ok(data.get(section.range()?).ok_or(Error::UnexpectedEof)?) } Format::PE => { let parsed = binfarce::pe::parse(data)?; let section = parsed .section_with_name(".dep-v0")? .ok_or(Error::NoAuditData)?; Ok(data.get(section.range()?).ok_or(Error::UnexpectedEof)?) } _ => Err(Error::NotAnExecutable), } } #[derive(Debug, Copy, Clone)] pub enum Error { NoAuditData, NotAnExecutable, UnexpectedEof, MalformedFile, SymbolsSectionIsMissing, SectionIsMissing, UnexpectedSectionType, } impl std::error::Error for Error {} impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let message = match self { Error::NoAuditData => "No audit data found in the executable", Error::NotAnExecutable => "Not an executable file", Error::UnexpectedEof => "Unexpected end of file", Error::MalformedFile => "Malformed executable file", Error::SymbolsSectionIsMissing => "Symbols section missing from executable", Error::SectionIsMissing => "Section is missing from executable", Error::UnexpectedSectionType => "Unexpected executable section type", }; write!(f, "{message}") } } impl From<binfarce::ParseError> for Error { fn from(e: binfarce::ParseError) -> Self { match e { binfarce::ParseError::MalformedInput => Error::MalformedFile, binfarce::ParseError::UnexpectedEof => Error::UnexpectedEof, binfarce::ParseError::SymbolsSectionIsMissing => Error::SymbolsSectionIsMissing, binfarce::ParseError::SectionIsMissing(_) => Error::SectionIsMissing, binfarce::ParseError::UnexpectedSectionType { .. } => Error::UnexpectedSectionType, } } } 07070100000015000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002700000000cargo-auditable-0.6.2~0/auditable-info07070100000016000081A400000000000000000000000165D39F85000001A1000000000000000000000000000000000000003400000000cargo-auditable-0.6.2~0/auditable-info/CHANGELOG.md# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.7.0] - 2023-04-27 ### Changed - Upgraded to `auditable-serde` v0.6.x, the only change is an upgrade to `cargo-lock` v9.x ### Added - This changelog file 07070100000017000081A400000000000000000000000165D39F8500000330000000000000000000000000000000000000003200000000cargo-auditable-0.6.2~0/auditable-info/Cargo.toml[package] name = "auditable-info" version = "0.7.0" authors = ["Sergey \"Shnatsel\" Davidoff <shnatsel@gmail.com>"] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-secure-code/cargo-auditable" description = "High-level crate to extract the dependency trees embedded in binaries by `cargo auditable`." categories = ["encoding"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] auditable-extract = {version = "0.3.0", path = "../auditable-extract"} miniz_oxide = { version = "0.6.2", features = ["std"] } auditable-serde = {version = "0.6.0", path = "../auditable-serde", optional = true} serde_json = { version = "1.0.57", optional = true } [features] serde = ["serde_json", "auditable-serde"] default = ["serde"] 07070100000018000081A400000000000000000000000165D39F8500000618000000000000000000000000000000000000003100000000cargo-auditable-0.6.2~0/auditable-info/README.mdHigh-level crate to extract the dependency trees embedded in binaries by [`cargo auditable`](https://crates.io/crates/cargo-auditable). Deserializes them to a JSON string or Rust data structures, at your option. ### Features - Binary parsing designed from the ground up for resilience to malicious inputs. - 100% memory-safe Rust, including all dependencies. (There is some `unsafe` in `serde_json` and its dependencies, but only in serialization, which isn't used here). - Cross-platform, portable, easy to cross-compile. Runs on [any Rust target with `std`](https://doc.rust-lang.org/stable/rustc/platform-support.html). - Parses binaries from any supported platform, not just the platform it's running on. - Supports setting size limits for both input and output, to protect against [OOMs](https://en.wikipedia.org/wiki/Out_of_memory) and [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). ### Usage ```rust // Uses the default limits: 1GiB input file size, 8MiB audit data size let info = audit_info_from_file(&PathBuf::from("path/to/file"), Default::default())?; ``` Functions to load the data from a `Read` instance or from `&[u8]` are also provided, see the [documentation](https://docs.rs/auditable-info). ### Alternatives [`rust-audit-info`](https://crates.io/crates/rust-audit-info) is a command-line interface to this crate. If you need an even lower-level interface than the one provided by this crate, use the [`auditable-extract`](http://docs.rs/auditable-extract/) and [`auditable-serde`](http://docs.rs/auditable-serde/) crates. 07070100000019000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002B00000000cargo-auditable-0.6.2~0/auditable-info/src0707010000001A000081A400000000000000000000000165D39F8500000AF6000000000000000000000000000000000000003400000000cargo-auditable-0.6.2~0/auditable-info/src/error.rs#[derive(Debug)] pub enum Error { NoAuditData, InputLimitExceeded, OutputLimitExceeded, Io(std::io::Error), BinaryParsing(auditable_extract::Error), Decompression(miniz_oxide::inflate::DecompressError), #[cfg(feature = "serde")] Json(serde_json::Error), Utf8(std::str::Utf8Error), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::NoAuditData => write!(f, "No audit data found in the binary! Was it built with 'cargo auditable'?"), Error::InputLimitExceeded => write!(f, "The input file is too large. Increase the input size limit to scan it."), Error::OutputLimitExceeded => write!(f, "Audit data size is over the specified limit. Increase the output size limit to scan it."), Error::Io(e) => write!(f, "Failed to read the binary: {e}"), Error::BinaryParsing(e) => write!(f, "Failed to parse the binary: {e}"), Error::Decompression(e) => write!(f, "Failed to decompress audit data: {e}"), #[cfg(feature = "serde")] Error::Json(e) => write!(f, "Failed to deserialize audit data from JSON: {e}"), Error::Utf8(e) => write!(f, "Invalid UTF-8 in audit data: {e}"), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::NoAuditData => None, Error::InputLimitExceeded => None, Error::OutputLimitExceeded => None, Error::Io(e) => Some(e), Error::BinaryParsing(e) => Some(e), Error::Decompression(e) => Some(e), #[cfg(feature = "serde")] Error::Json(e) => Some(e), Error::Utf8(e) => Some(e), } } } impl From<std::io::Error> for Error { fn from(e: std::io::Error) -> Self { Self::Io(e) } } impl From<auditable_extract::Error> for Error { fn from(e: auditable_extract::Error) -> Self { match e { auditable_extract::Error::NoAuditData => Error::NoAuditData, other_err => Self::BinaryParsing(other_err), } } } impl From<miniz_oxide::inflate::DecompressError> for Error { fn from(e: miniz_oxide::inflate::DecompressError) -> Self { match e.status { miniz_oxide::inflate::TINFLStatus::HasMoreOutput => Error::OutputLimitExceeded, _ => Error::Decompression(e), } } } impl From<std::string::FromUtf8Error> for Error { fn from(e: std::string::FromUtf8Error) -> Self { Self::Utf8(e.utf8_error()) } } #[cfg(feature = "serde")] impl From<serde_json::Error> for Error { fn from(e: serde_json::Error) -> Self { Self::Json(e) } } 0707010000001B000081A400000000000000000000000165D39F8500002050000000000000000000000000000000000000003200000000cargo-auditable-0.6.2~0/auditable-info/src/lib.rs#![forbid(unsafe_code)] //! High-level crate to extract the dependency trees embedded in binaries by [`cargo auditable`](https://crates.io/crates/cargo-auditable). //! //! Deserializes them to a JSON string or Rust data structures, at your option. //! //! ```rust, ignore //! // Uses the default limits: 1GiB input file size, 8MiB audit data size //! let info = audit_info_from_file(&PathBuf::from("path/to/file"), Default::default())?; //! ``` //! Functions to load the data from a `Read` instance or from `&[u8]` are also provided. //! //! If you need an even lower-level interface than the one provided by this crate, //! use the [`auditable-extract`](http://docs.rs/auditable-extract/) and //! [`auditable-serde`](http://docs.rs/auditable-serde/) crates. use auditable_extract::raw_auditable_data; #[cfg(feature = "serde")] use auditable_serde::VersionInfo; use miniz_oxide::inflate::decompress_to_vec_zlib_with_limit; use std::fs::File; use std::io::{BufRead, BufReader, Read}; use std::path::Path; mod error; pub use crate::error::Error; /// Loads audit info from the specified binary compiled with `cargo auditable`. /// /// The entire file is loaded into memory. The RAM usage limit can be configured using the [`Limits`] struct. /// /// ```rust, ignore /// // Uses the default limits: 1GiB input file size, 8MiB audit data size /// let info = audit_info_from_file(&PathBuf::from("path/to/file"), Default::default())?; /// ``` /// /// The data is validated to only have a single root package and not contain any circular dependencies. #[cfg(feature = "serde")] pub fn audit_info_from_file(path: &Path, limits: Limits) -> Result<VersionInfo, Error> { Ok(serde_json::from_str(&json_from_file(path, limits)?)?) } /// Extracts the audit data from the specified binary and returns the JSON string. /// This is useful if you want to forward the data somewhere instead of parsing it to Rust data structures. /// /// If you want to obtain the Zlib-compressed data instead, /// use the [`auditable-extract`](https://docs.rs/auditable-extract/) crate directly. pub fn json_from_file(path: &Path, limits: Limits) -> Result<String, Error> { let file = File::open(path)?; let mut reader = BufReader::new(file); json_from_reader(&mut reader, limits) } /// Loads audit info from the binary loaded from an arbitrary reader, e.g. the standard input. /// /// ```rust, ignore /// let stdin = io::stdin(); /// let mut handle = stdin.lock(); /// // Uses the default limits: 1GiB input file size, 8MiB audit data size /// let info = audit_info_from_reader(&mut handle, Default::default())?; /// ``` /// /// The data is validated to only have a single root package and not contain any circular dependencies. #[cfg(feature = "serde")] pub fn audit_info_from_reader<T: BufRead>( reader: &mut T, limits: Limits, ) -> Result<VersionInfo, Error> { Ok(serde_json::from_str(&json_from_reader(reader, limits)?)?) } /// Extracts the audit data and returns the JSON string. /// This is useful if you want to forward the data somewhere instead of parsing it to Rust data structures. /// /// If you want to obtain the Zlib-compressed data instead, /// use the [`auditable-extract`](https://docs.rs/auditable-extract/) crate directly. pub fn json_from_reader<T: BufRead>(reader: &mut T, limits: Limits) -> Result<String, Error> { let compressed_data = get_compressed_audit_data(reader, limits)?; let decompressed_data = decompress_to_vec_zlib_with_limit(&compressed_data, limits.decompressed_json_size)?; Ok(String::from_utf8(decompressed_data)?) } // Factored into its own function for ease of unit testing, // and also so that the large allocation of the input file is dropped // before we start decompressing the data to minimize peak memory usage fn get_compressed_audit_data<T: BufRead>(reader: &mut T, limits: Limits) -> Result<Vec<u8>, Error> { // In case you're wondering why the check for the limit is weird like that: // When .take() returns EOF, it doesn't tell you if that's because it reached the limit // or because the underlying reader ran out of data. // And we need to return an error when the reader is over limit, else we'll truncate the audit data. // So it would be reasonable to run `into_inner()` and check if that reader has any data remaining... // But readers can return EOF sporadically - a reader may return EOF, // then get more data and return bytes again instead of EOF! // So instead we read as many bytes as the limit allows, plus one. // If we've read the limit-plus-one bytes, that means the underlying reader was at least one byte over the limit. // That way we avoid any time-of-check/time-of-use issues. let incremented_limit = u64::saturating_add(limits.input_file_size as u64, 1); let mut f = reader.take(incremented_limit); let mut input_binary = Vec::new(); f.read_to_end(&mut input_binary)?; if input_binary.len() as u64 == incremented_limit { Err(Error::InputLimitExceeded)? } let compressed_audit_data = raw_auditable_data(&input_binary)?; if compressed_audit_data.len() > limits.decompressed_json_size { Err(Error::OutputLimitExceeded)?; } Ok(compressed_audit_data.to_owned()) } /// The input slice should contain the entire binary. /// This function is useful if you have already loaded the binary to memory, e.g. via memory-mapping. #[cfg(feature = "serde")] pub fn audit_info_from_slice( input_binary: &[u8], decompressed_json_size_limit: usize, ) -> Result<VersionInfo, Error> { Ok(serde_json::from_str(&json_from_slice( input_binary, decompressed_json_size_limit, )?)?) } /// The input slice should contain the entire binary. /// This function is useful if you have already loaded the binary to memory, e.g. via memory-mapping. /// /// Returns the decompressed audit data. /// This is useful if you want to forward the data somewhere instead of parsing it to Rust data structures. /// /// If you want to obtain the Zlib-compressed data instead, /// use the [`auditable-extract`](https://docs.rs/auditable-extract/) crate directly. pub fn json_from_slice( input_binary: &[u8], decompressed_json_size_limit: usize, ) -> Result<String, Error> { let compressed_audit_data = raw_auditable_data(input_binary)?; if compressed_audit_data.len() > decompressed_json_size_limit { Err(Error::OutputLimitExceeded)?; } let decompressed_data = decompress_to_vec_zlib_with_limit(compressed_audit_data, decompressed_json_size_limit)?; Ok(String::from_utf8(decompressed_data)?) } /// Protects against [denial-of-service attacks](https://en.wikipedia.org/wiki/Denial-of-service_attack) /// via infinite input streams or [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb), /// which would otherwise use up all your memory and crash your machine. /// /// If the limit is exceeded, an error is returned and no further deserialization is attempted. /// /// The default limits are **1 GiB** for the `input_file_size` and **8 MiB** for `decompressed_json_size`. /// /// Note that the `decompressed_json_size` is only enforced on the level of the *serialized* JSON, i.e. a string. /// We do not enforce that `serde_json` does not consume more memory when deserializing JSON to Rust data structures. /// Unfortunately Rust does not provide APIs for that. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct Limits { pub input_file_size: usize, pub decompressed_json_size: usize, } impl Default for Limits { fn default() -> Self { Self { input_file_size: 1024 * 1024 * 1024, // 1GiB decompressed_json_size: 1024 * 1024 * 8, // 8MiB } } } #[cfg(test)] mod tests { use super::*; #[test] fn input_file_limits() { let limits = Limits { input_file_size: 128, decompressed_json_size: 99999, }; let fake_data = vec![0; 1024]; let mut reader = std::io::Cursor::new(fake_data); let result = get_compressed_audit_data(&mut reader, limits); assert!(result.is_err()); assert!(result .unwrap_err() .to_string() .contains("The input file is too large")); } } 0707010000001C000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002800000000cargo-auditable-0.6.2~0/auditable-serde0707010000001D000081A400000000000000000000000165D39F8500000436000000000000000000000000000000000000003500000000cargo-auditable-0.6.2~0/auditable-serde/CHANGELOG.md# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.6.1] - 2024-02-19 ### Fixed - `from_metadata` feature: Fixed creating a cyclic dependency graph under [certain conditions](https://github.com/rustsec/rustsec/issues/1043). ## [0.6.0] - 2023-04-27 ### Changed - `toml` feature: upgraded to `cargo-lock` crate v9.x ### Fixed - Fixed changelog formatting ## [0.5.2] - 2022-10-24 ### Changed - `toml` feature: Versions are no longer roundtripped through `&str`, resulting in faster conversion. - `toml` feature: `cargo_lock::Dependency.source` field is now populated when when converting into `cargo-lock` crate format. ### Added - This changelog file ## [0.5.1] - 2022-10-02 ### Added - JSON schema (thanks to @tofay) - A mention of the `auditable-info` crate in the crate documentation ## [0.5.0] - 2022-08-08 ### Changed - This is the first feature-complete release 0707010000001E000081A400000000000000000000000165D39F85000003C9000000000000000000000000000000000000003300000000cargo-auditable-0.6.2~0/auditable-serde/Cargo.toml[package] name = "auditable-serde" version = "0.6.1" authors = ["Sergey \"Shnatsel\" Davidoff <shnatsel@gmail.com>"] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-secure-code/cargo-auditable" description = "Serialize/deserialize data encoded by `cargo auditable`" categories = ["encoding"] edition = "2018" [package.metadata.docs.rs] all-features = true [features] default = [] from_metadata = ["cargo_metadata"] toml = ["cargo-lock"] schema = ["schemars"] [dependencies] serde = { version = "1", features = ["serde_derive"] } serde_json = "1.0.57" semver = { version = "1.0", features = ["serde"] } cargo_metadata = { version = "0.15", optional = true } cargo-lock = { version = "9", default-features = false, optional = true } topological-sort = "0.2.2" schemars = {version = "0.8.10", optional = true } [[example]] name = "json-to-toml" required-features = ["toml"] [[example]] name = "from-metadata" required-features = ["from_metadata"] 0707010000001F000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000003100000000cargo-auditable-0.6.2~0/auditable-serde/examples07070100000020000081A400000000000000000000000165D39F85000003BA000000000000000000000000000000000000004200000000cargo-auditable-0.6.2~0/auditable-serde/examples/from-metadata.rs/// Prints the audit data for the package in the current directory. /// Passes any command-line parameters it receives to `cargo metadata`, /// so you can specify `--filter-platform`, `--features` and other flags. use auditable_serde::VersionInfo; use cargo_metadata::{Metadata, MetadataCommand}; use std::{convert::TryFrom, error::Error}; fn get_metadata() -> Result<Metadata, cargo_metadata::Error> { let mut metadata_command = MetadataCommand::new(); let args: Vec<String> = std::env::args().skip(1).collect(); metadata_command.other_options(args); metadata_command.exec() } fn do_work() -> Result<(), Box<dyn Error>> { let stdout = std::io::stdout(); let stdout = stdout.lock(); let metadata = get_metadata()?; let version_info = VersionInfo::try_from(&metadata)?; serde_json::to_writer(stdout, &version_info)?; Ok(()) } fn main() { if let Err(error) = do_work() { println!("{error}"); } } 07070100000021000081A400000000000000000000000165D39F8500000257000000000000000000000000000000000000004100000000cargo-auditable-0.6.2~0/auditable-serde/examples/json-to-toml.rsuse auditable_serde::VersionInfo; use cargo_lock::Lockfile; use std::convert::TryInto; use std::str::FromStr; fn main() { let path = std::env::args().skip(1).next().expect("No file specified"); let file_contents = std::fs::read_to_string(path).unwrap(); let version_info = VersionInfo::from_str(&file_contents).unwrap(); let lockfile: Lockfile = (&version_info).try_into().unwrap(); let lockfile_toml = lockfile.to_string(); let stdout = std::io::stdout(); let mut stdout = stdout.lock(); std::io::Write::write_all(&mut stdout, lockfile_toml.as_bytes()).unwrap(); } 07070100000022000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002C00000000cargo-auditable-0.6.2~0/auditable-serde/src07070100000023000081A400000000000000000000000165D39F8500005CDF000000000000000000000000000000000000003300000000cargo-auditable-0.6.2~0/auditable-serde/src/lib.rs#![forbid(unsafe_code)] #![allow(clippy::redundant_field_names)] //! Parses and serializes the JSON dependency tree embedded in executables by the //! [`cargo auditable`](https://github.com/rust-secure-code/cargo-auditable). //! //! This crate defines the data structures that a serialized to/from JSON //! and implements the serialization/deserialization routines via `serde`. //! It also provides optional conversions from [`cargo metadata`](https://docs.rs/cargo_metadata/) //! and to [`Cargo.lock`](https://docs.rs/cargo-lock) formats. //! //! The [`VersionInfo`] struct is where all the magic happens, see the docs on it for more info. //! //! ## Basic usage //! //! **Note:** this is a low-level crate that only implements JSON parsing. It rarely should be used directly. //! You probably want the higher-level [`auditable-info`](https://docs.rs/auditable-info) crate instead. //! //! The following snippet demonstrates full extraction pipeline, including //! platform-specific executable handling via //! [`auditable-extract`](http://docs.rs/auditable-serde/) and decompression //! using the safe-Rust [`miniz_oxide`](http://docs.rs/miniz_oxide/): //! //! ```rust,ignore //! use std::io::{Read, BufReader}; //! use std::{error::Error, fs::File, str::FromStr}; //! //! fn main() -> Result<(), Box<dyn Error>> { //! // Read the input //! let f = File::open("target/release/hello-world")?; //! let mut f = BufReader::new(f); //! let mut input_binary = Vec::new(); //! f.read_to_end(&mut input_binary)?; //! // Extract the compressed audit data //! let compressed_audit_data = auditable_extract::raw_auditable_data(&input_binary)?; //! // Decompress it with your Zlib implementation of choice. We recommend miniz_oxide //! use miniz_oxide::inflate::decompress_to_vec_zlib; //! let decompressed_data = decompress_to_vec_zlib(&compressed_audit_data) //! .map_err(|_| "Failed to decompress audit data")?; //! let decompressed_data = String::from_utf8(decompressed_data)?; //! println!("{}", decompressed_data); //! // Parse the audit data to Rust data structures //! let dependency_tree = auditable_serde::VersionInfo::from_str(&decompressed_data); //! Ok(()) //! } //! ``` mod validation; use validation::RawVersionInfo; use serde::{Deserialize, Serialize}; #[cfg(feature = "toml")] use cargo_lock; #[cfg(any(feature = "from_metadata", feature = "toml"))] use std::convert::TryFrom; #[cfg(feature = "toml")] use std::convert::TryInto; use std::str::FromStr; #[cfg(feature = "from_metadata")] #[cfg(feature = "from_metadata")] use std::{cmp::min, cmp::Ordering::*, collections::HashMap, error::Error, fmt::Display}; /// Dependency tree embedded in the binary. /// /// Implements `Serialize` and `Deserialize` traits from `serde`, so you can use /// [all the usual methods from serde-json](https://docs.rs/serde_json/1.0.57/serde_json/#functions) /// to read and write it. /// /// `from_str()` that parses JSON is also implemented for your convenience: /// ```rust /// use auditable_serde::VersionInfo; /// use std::str::FromStr; /// let json_str = r#"{"packages":[{ /// "name":"adler", /// "version":"0.2.3", /// "source":"registry" /// }]}"#; /// let info = VersionInfo::from_str(json_str).unwrap(); /// assert_eq!(&info.packages[0].name, "adler"); /// ``` /// /// If deserialization succeeds, it is guaranteed that there is only one root package, /// and that are no cyclic dependencies. /// /// ## Optional features /// /// If the `from_metadata` feature is enabled, a conversion from /// [`cargo_metadata::Metadata`](https://docs.rs/cargo_metadata/0.11.1/cargo_metadata/struct.Metadata.html) /// is possible via the `TryFrom` trait. This is the preferred way to construct this structure. /// An example demonstrating that can be found /// [here](https://github.com/rust-secure-code/cargo-auditable/blob/master/auditable-serde/examples/from-metadata.rs). /// /// If the `toml` feature is enabled, a conversion into the [`cargo_lock::Lockfile`](https://docs.rs/cargo-lock/) /// struct is possible via the `TryFrom` trait. This can be useful if you need to interoperate with tooling /// that consumes the `Cargo.lock` file format. An example demonstrating it can be found /// [here](https://github.com/rust-secure-code/cargo-auditable/blob/master/auditable-serde/examples/json-to-toml.rs). #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] #[serde(try_from = "RawVersionInfo")] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct VersionInfo { pub packages: Vec<Package>, } /// A single package in the dependency tree #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct Package { /// Crate name specified in the `name` field in Cargo.toml file. Examples: "libc", "rand" pub name: String, /// The package's version in the [semantic version](https://semver.org) format. #[cfg_attr(feature = "schema", schemars(with = "String"))] pub version: semver::Version, /// Currently "git", "local", "crates.io" or "registry". Designed to be extensible with other revision control systems, etc. pub source: Source, /// "build" or "runtime". May be omitted if set to "runtime". /// If it's both a build and a runtime dependency, "runtime" is recorded. #[serde(default)] #[serde(skip_serializing_if = "is_default")] pub kind: DependencyKind, /// Packages are stored in an ordered array both in the `VersionInfo` struct and in JSON. /// Here we refer to each package by its index in the array. /// May be omitted if the list is empty. #[serde(default)] #[serde(skip_serializing_if = "is_default")] pub dependencies: Vec<usize>, /// Whether this is the root package in the dependency tree. /// There should only be one root package. /// May be omitted if set to `false`. #[serde(default)] #[serde(skip_serializing_if = "is_default")] pub root: bool, } /// Serializes to "git", "local", "crates.io" or "registry". Designed to be extensible with other revision control systems, etc. #[non_exhaustive] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] #[serde(from = "&str")] #[serde(into = "String")] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum Source { CratesIo, Git, Local, Registry, Other(String), } impl From<&str> for Source { fn from(s: &str) -> Self { match s { "crates.io" => Self::CratesIo, "git" => Self::Git, "local" => Self::Local, "registry" => Self::Registry, other_str => Self::Other(other_str.to_string()), } } } impl From<Source> for String { fn from(s: Source) -> String { match s { Source::CratesIo => "crates.io".to_owned(), Source::Git => "git".to_owned(), Source::Local => "local".to_owned(), Source::Registry => "registry".to_owned(), Source::Other(string) => string, } } } #[cfg(feature = "from_metadata")] impl From<&cargo_metadata::Source> for Source { fn from(meta_source: &cargo_metadata::Source) -> Self { match meta_source.repr.as_str() { "registry+https://github.com/rust-lang/crates.io-index" => Source::CratesIo, source => Source::from( source .split('+') .next() .expect("Encoding of source strings in `cargo metadata` has changed!"), ), } } } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Default)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum DependencyKind { // The values are ordered from weakest to strongest so that casting to integer would make sense #[serde(rename = "build")] Build, #[default] #[serde(rename = "runtime")] Runtime, } /// The values are ordered from weakest to strongest so that casting to integer would make sense #[cfg(feature = "from_metadata")] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] enum PrivateDepKind { Development, Build, Runtime, } #[cfg(feature = "from_metadata")] impl From<PrivateDepKind> for DependencyKind { fn from(priv_kind: PrivateDepKind) -> Self { match priv_kind { PrivateDepKind::Development => { panic!("Cannot convert development dependency to serializable format") } PrivateDepKind::Build => DependencyKind::Build, PrivateDepKind::Runtime => DependencyKind::Runtime, } } } fn is_default<T: Default + PartialEq>(value: &T) -> bool { let default_value = T::default(); value == &default_value } impl FromStr for VersionInfo { type Err = serde_json::Error; fn from_str(s: &str) -> Result<Self, Self::Err> { serde_json::from_str(s) } } #[cfg(feature = "from_metadata")] impl From<&cargo_metadata::DependencyKind> for PrivateDepKind { fn from(kind: &cargo_metadata::DependencyKind) -> Self { match kind { cargo_metadata::DependencyKind::Normal => PrivateDepKind::Runtime, cargo_metadata::DependencyKind::Development => PrivateDepKind::Development, cargo_metadata::DependencyKind::Build => PrivateDepKind::Build, _ => panic!("Unknown dependency kind"), } } } /// Error returned by the conversion from /// [`cargo_metadata::Metadata`](https://docs.rs/cargo_metadata/0.11.1/cargo_metadata/struct.Metadata.html) #[cfg(feature = "from_metadata")] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum InsufficientMetadata { NoDeps, VirtualWorkspace, } #[cfg(feature = "from_metadata")] impl Display for InsufficientMetadata { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { InsufficientMetadata::NoDeps => { write!(f, "Missing dependency information! Please call 'cargo metadata' without '--no-deps' flag.") } InsufficientMetadata::VirtualWorkspace => { write!(f, "Missing root crate! Please call this from a package directory, not workspace root.") } } } } #[cfg(feature = "from_metadata")] impl Error for InsufficientMetadata {} #[cfg(feature = "from_metadata")] impl TryFrom<&cargo_metadata::Metadata> for VersionInfo { type Error = InsufficientMetadata; fn try_from(metadata: &cargo_metadata::Metadata) -> Result<Self, Self::Error> { let toplevel_crate_id = metadata .resolve .as_ref() .ok_or(InsufficientMetadata::NoDeps)? .root .as_ref() .ok_or(InsufficientMetadata::VirtualWorkspace)? .repr .as_str(); // Walk the dependency tree and resolve dependency kinds for each package. // We need this because there may be several different paths to the same package // and we need to aggregate dependency types across all of them. // Moreover, `cargo metadata` doesn't propagate dependency information: // A runtime dependency of a build dependency of your package should be recorded // as *build* dependency, but Cargo flags it as a runtime dependency. // Hoo boy, here I go hand-rolling BFS again! let nodes = &metadata.resolve.as_ref().unwrap().nodes; let id_to_node: HashMap<&str, &cargo_metadata::Node> = nodes.iter().map(|n| (n.id.repr.as_str(), n)).collect(); let mut id_to_dep_kind: HashMap<&str, PrivateDepKind> = HashMap::new(); id_to_dep_kind.insert(toplevel_crate_id, PrivateDepKind::Runtime); let mut current_queue: Vec<&cargo_metadata::Node> = vec![id_to_node[toplevel_crate_id]]; let mut next_step_queue: Vec<&cargo_metadata::Node> = Vec::new(); while !current_queue.is_empty() { for parent in current_queue.drain(..) { let parent_dep_kind = id_to_dep_kind[parent.id.repr.as_str()]; for child in &parent.deps { let child_id = child.pkg.repr.as_str(); let dep_kind = strongest_dep_kind(child.dep_kinds.as_slice()); let dep_kind = min(dep_kind, parent_dep_kind); let dep_kind_on_previous_visit = id_to_dep_kind.get(child_id); if dep_kind_on_previous_visit.is_none() || &dep_kind > dep_kind_on_previous_visit.unwrap() { // if we haven't visited this node in dependency graph yet // or if we've visited it with a weaker dependency type, // records its new dependency type and add it to the queue to visit its dependencies id_to_dep_kind.insert(child_id, dep_kind); next_step_queue.push(id_to_node[child_id]); } } } std::mem::swap(&mut next_step_queue, &mut current_queue); } let metadata_package_dep_kind = |p: &cargo_metadata::Package| { let package_id = p.id.repr.as_str(); id_to_dep_kind.get(package_id) }; // Remove dev-only dependencies from the package list and collect them to Vec let mut packages: Vec<&cargo_metadata::Package> = metadata .packages .iter() .filter(|p| { let dep_kind = metadata_package_dep_kind(p); // Dependencies that are present in the workspace but not used by the current root crate // will not be in the map we've built by traversing the root crate's dependencies. // In this case they will not be in the map at all. We skip them, along with dev-dependencies. dep_kind.is_some() && dep_kind.unwrap() != &PrivateDepKind::Development }) .collect(); // This function is the simplest place to introduce sorting, since // it contains enough data to distinguish between equal-looking packages // and provide a stable sorting that might not be possible // using the data from VersionInfo struct alone. // // We use sort_unstable here because there is no point in // not reordering equal elements, since they're supplied by // in arbitrary order by cargo-metadata anyway // and the order even varies between executions. packages.sort_unstable_by(|a, b| { // This is a workaround for Package not implementing Ord. // Deriving it in cargo_metadata might be more reliable? let names_order = a.name.cmp(&b.name); if names_order != Equal { return names_order; } let versions_order = a.name.cmp(&b.name); if versions_order != Equal { return versions_order; } // IDs are unique so comparing them should be sufficient a.id.repr.cmp(&b.id.repr) }); // Build a mapping from package ID to the index of that package in the Vec // because serializable representation doesn't store IDs let mut id_to_index = HashMap::new(); for (index, package) in packages.iter().enumerate() { id_to_index.insert(package.id.repr.as_str(), index); } // Convert packages from cargo-metadata representation to our representation let mut packages: Vec<Package> = packages .into_iter() .map(|p| Package { name: p.name.to_owned(), version: p.version.clone(), source: p.source.as_ref().map_or(Source::Local, Source::from), kind: (*metadata_package_dep_kind(p).unwrap()).into(), dependencies: Vec::new(), root: p.id.repr == toplevel_crate_id, }) .collect(); // Fill in dependency info from resolved dependency graph for node in metadata.resolve.as_ref().unwrap().nodes.iter() { let package_id = node.id.repr.as_str(); if id_to_index.contains_key(package_id) { // dev-dependencies are not included let package: &mut Package = &mut packages[id_to_index[package_id]]; // Dependencies for dep in node.deps.iter() { // Omit the graph edge if this is a development dependency // to fix https://github.com/rustsec/rustsec/issues/1043 // It is possible that something that we depend on normally // is also a dev-dependency for something, // and dev-dependencies are allowed to have cycles, // so we may end up encoding cyclic graph if we don't handle that. let dep_id = dep.pkg.repr.as_str(); if strongest_dep_kind(&dep.dep_kinds) != PrivateDepKind::Development { package.dependencies.push(id_to_index[dep_id]); } } // .sort_unstable() is fine because they're all integers package.dependencies.sort_unstable(); } } Ok(VersionInfo { packages }) } } #[cfg(feature = "from_metadata")] fn strongest_dep_kind(deps: &[cargo_metadata::DepKindInfo]) -> PrivateDepKind { deps.iter() .map(|d| PrivateDepKind::from(&d.kind)) .max() .unwrap_or(PrivateDepKind::Runtime) // for compatibility with Rust earlier than 1.41 } #[cfg(feature = "toml")] impl TryFrom<&Package> for cargo_lock::Dependency { type Error = cargo_lock::Error; fn try_from(input: &Package) -> Result<Self, Self::Error> { Ok(cargo_lock::Dependency { name: cargo_lock::package::Name::from_str(&input.name)?, version: input.version.clone(), source: (&input.source).into(), }) } } #[cfg(feature = "toml")] impl From<&Source> for Option<cargo_lock::SourceId> { fn from(source: &Source) -> Self { match source { Source::CratesIo => Some( cargo_lock::package::SourceId::from_url( "registry+https://github.com/rust-lang/crates.io-index", ) .unwrap(), ), _ => None, // we don't store enough info about other sources to reconstruct the URL } } } #[cfg(feature = "toml")] impl TryFrom<&VersionInfo> for cargo_lock::Lockfile { type Error = cargo_lock::Error; fn try_from(input: &VersionInfo) -> Result<Self, Self::Error> { let mut root_package: Option<cargo_lock::Package> = None; let mut packages: Vec<cargo_lock::Package> = Vec::new(); for pkg in input.packages.iter() { let lock_pkg = cargo_lock::package::Package { name: cargo_lock::package::Name::from_str(&pkg.name)?, version: pkg.version.clone(), checksum: Option::None, dependencies: { let result: Result<Vec<_>, _> = pkg.dependencies .iter() .map(|i| { input.packages.get(*i).ok_or(cargo_lock::Error::Parse( format!("There is no dependency with index {} in the input JSON", i)) )?.try_into() }) .collect(); result? }, replace: None, source: (&pkg.source).into(), }; if pkg.root { if root_package.is_some() { return Err(cargo_lock::Error::Parse( "More than one root package specified in JSON!".to_string(), )); } root_package = Some(lock_pkg.clone()); } packages.push(lock_pkg); } Ok(cargo_lock::Lockfile { version: cargo_lock::ResolveVersion::V2, packages: packages, root: root_package, metadata: std::collections::BTreeMap::new(), patch: cargo_lock::Patch { unused: Vec::new() }, }) } } #[cfg(test)] mod tests { #![allow(unused_imports)] // otherwise conditional compilation emits warnings use super::*; use std::fs; use std::{ convert::TryInto, path::{Path, PathBuf}, }; #[cfg(feature = "from_metadata")] fn load_metadata(cargo_toml_path: &Path) -> cargo_metadata::Metadata { let mut cmd = cargo_metadata::MetadataCommand::new(); cmd.manifest_path(cargo_toml_path); cmd.exec().unwrap() } #[test] #[cfg(feature = "from_metadata")] fn dependency_cycle() { let cargo_toml_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) .join("tests/fixtures/cargo-audit-dep-cycle/Cargo.toml"); let metadata = load_metadata(&cargo_toml_path); let version_info_struct: VersionInfo = (&metadata).try_into().unwrap(); let json = serde_json::to_string(&version_info_struct).unwrap(); VersionInfo::from_str(&json).unwrap(); // <- the part we care about succeeding } #[test] #[cfg(feature = "toml")] #[cfg(feature = "from_metadata")] fn to_toml() { let cargo_toml_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("Cargo.toml"); let metadata = load_metadata(&cargo_toml_path); let version_info_struct: VersionInfo = (&metadata).try_into().unwrap(); let _lockfile_struct: cargo_lock::Lockfile = (&version_info_struct).try_into().unwrap(); } #[cfg(feature = "schema")] /// Generate a JsonSchema for VersionInfo fn generate_schema() -> schemars::schema::RootSchema { let mut schema = schemars::schema_for!(VersionInfo); let mut metadata = *schema.schema.metadata.clone().unwrap(); let title = "cargo-auditable schema".to_string(); metadata.title = Some(title); metadata.id = Some("https://rustsec.org/schemas/cargo-auditable.json".to_string()); metadata.examples = [].to_vec(); metadata.description = Some( "Describes the `VersionInfo` JSON data structure that cargo-auditable embeds into Rust binaries." .to_string(), ); schema.schema.metadata = Some(Box::new(metadata)); schema } #[test] #[cfg(feature = "schema")] fn verify_schema() { use schemars::schema::RootSchema; let expected = generate_schema(); // Printing here makes it easier to update the schema when required println!( "expected schema:\n{}", serde_json::to_string_pretty(&expected).unwrap() ); let contents = fs::read_to_string( // `CARGO_MANIFEST_DIR` env is path to dir containing auditable-serde's Cargo.toml PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() .join("cargo-auditable.schema.json"), ) .expect("error reading existing schema"); let actual: RootSchema = serde_json::from_str(&contents).expect("error deserializing existing schema"); assert_eq!(expected, actual); } } 07070100000024000081A400000000000000000000000165D39F8500000D3D000000000000000000000000000000000000003A00000000cargo-auditable-0.6.2~0/auditable-serde/src/validation.rsuse crate::{Package, VersionInfo}; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt::Display}; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct RawVersionInfo { pub packages: Vec<Package>, } pub enum ValidationError { MultipleRoots, CyclicDependency, } impl Display for ValidationError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ValidationError::MultipleRoots => { write!(f, "Multiple root packages specified in the input JSON") } ValidationError::CyclicDependency => { write!(f, "The input JSON specifies a cyclic dependency graph") } } } } impl TryFrom<RawVersionInfo> for VersionInfo { type Error = ValidationError; fn try_from(v: RawVersionInfo) -> Result<Self, Self::Error> { if has_multiple_root_packages(&v) { Err(ValidationError::MultipleRoots) } else if has_cylic_dependencies(&v) { Err(ValidationError::CyclicDependency) } else { Ok(VersionInfo { packages: v.packages, }) } } } fn has_multiple_root_packages(v: &RawVersionInfo) -> bool { let mut seen_a_root = false; for package in &v.packages { if package.root { if seen_a_root { return true; } else { seen_a_root = true; } } } false } fn has_cylic_dependencies(v: &RawVersionInfo) -> bool { // I've reviewed the `topological_sort` crate and it appears to be high-quality, // so I'm not concerned about having it exposed to untrusted input. // It's better than my hand-rolled version would have been. // populate the topological sorting map let mut ts = topological_sort::TopologicalSort::<usize>::new(); for (index, package) in v.packages.iter().enumerate() { for dep in &package.dependencies { ts.add_dependency(*dep, index); } } // drain all elements that are not part of a cycle while ts.pop().is_some() {} // if the set isn't empty, the graph has cycles !ts.is_empty() } #[cfg(test)] mod tests { use std::str::FromStr; use super::*; use crate::*; fn dummy_package(pkg_counter: u32, root: bool, deps: Vec<usize>) -> Package { Package { name: format!("test_{pkg_counter}"), version: semver::Version::from_str("0.0.0").unwrap(), source: Source::Local, kind: DependencyKind::Build, dependencies: deps, root: root, } } // these tests are very basic because `topological_sort` crate is already tested extensively #[test] fn cyclic_dependencies() { let pkg0 = dummy_package(0, true, vec![1]); let pkg1 = dummy_package(1, false, vec![0]); let raw = RawVersionInfo { packages: vec![pkg0, pkg1], }; assert!(VersionInfo::try_from(raw).is_err()); } #[test] fn no_cyclic_dependencies() { let pkg0 = dummy_package(0, true, vec![1]); let pkg1 = dummy_package(1, false, vec![]); let raw = RawVersionInfo { packages: vec![pkg0, pkg1], }; assert!(VersionInfo::try_from(raw).is_ok()); } } 07070100000025000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002E00000000cargo-auditable-0.6.2~0/auditable-serde/tests07070100000026000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000003700000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures07070100000027000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004D00000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle07070100000028000081A400000000000000000000000165D39F8500000008000000000000000000000000000000000000005800000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/.gitignore/target 07070100000029000081A400000000000000000000000165D39F8500000144000000000000000000000000000000000000005800000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/Cargo.lock# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "a" version = "0.1.0" dependencies = [ "b", ] [[package]] name = "b" version = "0.1.0" dependencies = [ "a", ] [[package]] name = "cargo-audit-dep-cycle" version = "0.1.0" dependencies = [ "a", ] 0707010000002A000081A400000000000000000000000165D39F85000000F3000000000000000000000000000000000000005800000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/Cargo.toml[workspace] members = ["a", "b"] [package] name = "cargo-audit-dep-cycle" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] a = { path = "a" } 0707010000002B000081A400000000000000000000000165D39F85000001C7000000000000000000000000000000000000005700000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/README.md# Minimal example for cyclic dependency graph in audit data When building this project with `cargo auditable build`, and then running `cargo audit` on it, this error is printed: ``` error: parse error: Failed to deserialize audit data from JSON: The input JSON specifies a cyclic dependency graph ``` This repository serves as a minimal example for reproducing the issue. The issue was reported [here](https://github.com/rustsec/rustsec/issues/1043). 0707010000002C000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004F00000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/a0707010000002D000081A400000000000000000000000165D39F85000000BF000000000000000000000000000000000000005A00000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/a/Cargo.toml[package] name = "a" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] b = { path = "../b" }0707010000002E000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005300000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/a/src0707010000002F000081A400000000000000000000000165D39F85000000D8000000000000000000000000000000000000005A00000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/a/src/lib.rspub fn add(left: usize, right: usize) -> usize { left + right } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); } } 07070100000030000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004F00000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/b07070100000031000081A400000000000000000000000165D39F85000000D4000000000000000000000000000000000000005A00000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/b/Cargo.toml[package] name = "b" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] [dev-dependencies] a = { path = "../a" } 07070100000032000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005300000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/b/src07070100000033000081A400000000000000000000000165D39F85000000D8000000000000000000000000000000000000005A00000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/b/src/lib.rspub fn add(left: usize, right: usize) -> usize { left + right } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); } } 07070100000034000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005100000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/src07070100000035000081A400000000000000000000000165D39F850000002D000000000000000000000000000000000000005900000000cargo-auditable-0.6.2~0/auditable-serde/tests/fixtures/cargo-audit-dep-cycle/src/main.rsfn main() { println!("Hello, world!"); } 07070100000036000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002800000000cargo-auditable-0.6.2~0/cargo-auditable07070100000037000081A400000000000000000000000165D39F8500000EC2000000000000000000000000000000000000003400000000cargo-auditable-0.6.2~0/cargo-auditable.schema.json{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://rustsec.org/schemas/cargo-auditable.json", "title": "cargo-auditable schema", "description": "Describes the `VersionInfo` JSON data structure that cargo-auditable embeds into Rust binaries.", "type": "object", "required": [ "packages" ], "properties": { "packages": { "type": "array", "items": { "$ref": "#/definitions/Package" } } }, "definitions": { "DependencyKind": { "type": "string", "enum": [ "build", "runtime" ] }, "Package": { "description": "A single package in the dependency tree", "type": "object", "required": [ "name", "source", "version" ], "properties": { "dependencies": { "description": "Packages are stored in an ordered array both in the `VersionInfo` struct and in JSON. Here we refer to each package by its index in the array. May be omitted if the list is empty.", "type": "array", "items": { "type": "integer", "format": "uint", "minimum": 0.0 } }, "kind": { "description": "\"build\" or \"runtime\". May be omitted if set to \"runtime\". If it's both a build and a runtime dependency, \"runtime\" is recorded.", "allOf": [ { "$ref": "#/definitions/DependencyKind" } ] }, "name": { "description": "Crate name specified in the `name` field in Cargo.toml file. Examples: \"libc\", \"rand\"", "type": "string" }, "root": { "description": "Whether this is the root package in the dependency tree. There should only be one root package. May be omitted if set to `false`.", "type": "boolean" }, "source": { "description": "Currently \"git\", \"local\", \"crates.io\" or \"registry\". Designed to be extensible with other revision control systems, etc.", "allOf": [ { "$ref": "#/definitions/Source" } ] }, "version": { "description": "The package's version in the [semantic version](https://semver.org) format.", "type": "string" } } }, "Source": { "description": "Serializes to \"git\", \"local\", \"crates.io\" or \"registry\". Designed to be extensible with other revision control systems, etc.", "oneOf": [ { "type": "string", "enum": [ "CratesIo", "Git", "Local", "Registry" ] }, { "type": "object", "required": [ "Other" ], "properties": { "Other": { "type": "string" } }, "additionalProperties": false } ] } } } 07070100000038000081A400000000000000000000000165D39F850000096A000000000000000000000000000000000000003500000000cargo-auditable-0.6.2~0/cargo-auditable/CHANGELOG.md# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.6.2] - 2024-02-19 ### Fixed - Fixed `cargo auditable` encoding a cyclic dependency graph under [certain conditions](https://github.com/rustsec/rustsec/issues/1043) - Fixed an integration test failing intermittently on recent Rust versions ### Changed - No longer attempt to add audit info if `--print` arguments are passed to `rustc`, which disable code generation - Print a more meaningful error when invoking `rustc` fails ## [0.6.1] - 2023-03-06 ### Added - A Unix manpage - An explanation of how the project relates to supply chain attacks to the README - Keywords to the Cargo manifest to make discovering the project easier ### Changed - Updated to `object` crate version 0.30 to enable packaging for Debian - Synced to the latest object writing code from the Rust compiler. This should improve support for very obscure architectures. ## [0.6.0] - 2022-12-07 ### Changed - A build with `cargo auditable` no longer fails when targeting an unsupported architecture. Instead a warning is printed. - The `CARGO` environment variable is now read and honored; calls to Cargo will go through the binary specified in this variable instead of just `cargo`. ### Added - Added documentation on using `cargo auditable` as a drop-in replacement for `cargo`. ### Fixed - Fixed build failures when the `RUSTC` environment variable or the `build.rustc` configuration option is set. ## [0.5.5] - 2022-12-01 ### Fixed - Long builds with `sccache` now work as expected. They require additional quirks compared to regular Cargo builds, see [#87](https://github.com/rust-secure-code/cargo-auditable/issues/87). - Note that `sccache` v0.3.1 or later is required even with this fix - earlier versions have a [bug](https://github.com/mozilla/sccache/issues/1274) that prevents them from working with `cargo auditable`. ## [0.5.4] - 2022-11-12 ### Changed - Updated README.md ## [0.5.3] - 2022-11-12 ### Fixed - `--offline`, `--locked`, `--frozen` and `--config` flags now work as expected. Previously they were not forwarded to `cargo metadata`, so it could still access the network, etc. ### Added - Re-introduced CHANGELOG.md 07070100000039000081A400000000000000000000000165D39F85000003C8000000000000000000000000000000000000003300000000cargo-auditable-0.6.2~0/cargo-auditable/Cargo.toml[package] name = "cargo-auditable" version = "0.6.2" edition = "2021" authors = ["Sergey \"Shnatsel\" Davidoff <shnatsel@gmail.com>"] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-secure-code/cargo-auditable" description = "Make production Rust binaries auditable" categories = ["development-tools::cargo-plugins", "encoding"] keywords = ["security", "supply-chain", "sbom", "vulnerabilities"] readme = "../README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] object = {version = "0.30", default-features = false, features = ["write"]} auditable-serde = {version = "0.6.1", path = "../auditable-serde", features = ["from_metadata"]} miniz_oxide = {version = "0.6.0"} serde_json = "1.0.57" cargo_metadata = "0.15" pico-args = "0.5" serde = "1.0.147" [dev-dependencies] cargo_metadata = "0.15" auditable-info = {version = "0.7.0", path = "../auditable-info"} which = "4.3.0" 0707010000003A000081A400000000000000000000000165D39F850000150D000000000000000000000000000000000000003A00000000cargo-auditable-0.6.2~0/cargo-auditable/cargo-auditable.1.TH CARGO-AUDITABLE 1 .SH NAME cargo\-auditable \- Embed a JSON formatted dependency tree into a dedicated linker section of the compiled executable .SH SYNOPSIS \fBcargo\-auditable\fR .SH DESCRIPTION Know the exact crate versions used to build your Rust executable. Audit binaries for known bugs or security vulnerabilities in production, at scale, with zero bookkeeping. This works by embedding data about the dependency tree in JSON format into a dedicated linker section of the compiled executable. Linux, Windows and Mac OS are officially supported. All other ELF targets should work, but are not tested on CI. WASM is currently not supported, but patches are welcome. The end goal is to get Cargo itself to encode this information in binaries. There is an RFC for an implementation within Cargo, for which this project paves the way: https://github.com/rust\-lang/rfcs/pull/2801 .SH USAGE cargo auditable works with any Cargo command. All arguments are passed to cargo as\-is. .SH FAQ Doesn't this bloat my binary? In a word, no. The embedded dependency list uses under 4kB even on large dependency trees with 400+ entries. This typically translates to between 1/1000 and 1/10,000 of the size of the binary. Can I make cargo always build with cargo auditable? Yes! For example, on Linux/macOS/etc add this to your .bashrc: alias cargo="cargo auditable" If you're using a shell other than bash, or if using an alias is not an option, see https://github.com/rust\-secure\-code/cargo\-auditable/blob/HEAD/REPLACING_CARGO.md. Is there any tooling to consume this data? Vulnerability reporting cargo audit v0.17.3+ can detect this data in binaries and report on vulnerabilities. See here for details. trivy v0.31.0+ detects this data in binaries and reports on vulnerabilities. See the v0.31.0 release notes for an end\-to\-end example. Recovering the dependency list syft v0.53.0+ has experimental support for detecting this data in binaries. When used on images or directories, Rust audit support must be enabled by adding the \-\-catalogers all CLI option, e.g syft \-\-catalogers all <container image containing Rust auditable binary>. rust\-audit\-info recovers the dependency list from a binary and prints it in JSON. It is also interoperable with existing tooling that consumes Cargo.lock via the JSON\-to\-TOML convertor. However, we recommend supporting the format natively; the format is designed to be very easy to parse, even if your language does not have a library for that yet. Can I read this data using a tool written in a different language? Yes. The data format is designed for interoperability with alternative implementations. In fact, parsing it only takes 5 lines of Python. See https://github.com/rust\-secure\-code/cargo\-auditable/blob/HEAD/PARSING.md for documentation on parsing the data. What is the data format, exactly? The data format is described by the JSON schema https://github.com/rust\-secure\-code/cargo\-auditable/blob/HEAD/cargo\-auditable.schema.json. The JSON is Zlib\-compressed and placed in a linker section named .dep\-v0. You can find more info about parsing it here. What about embedded platforms? Embedded platforms where you cannot spare a byte should not add anything in the executable. Instead they should record the hash of every executable in a database and associate the hash with its Cargo.lock, compiler and LLVM version, build date, etc. This would make for an excellent Cargo wrapper or plugin. Since that can be done in a 5\-line shell script, writing that tool is left as an exercise to the reader. Does this impact reproducible builds? The data format is specifically designed not to disrupt reproducible builds. It contains no timestamps, and the generated JSON is sorted to make sure it is identical between compilations. If anything, this helps with reproducible builds, since you know all the versions for a given binary now. Does this disclose any sensitive information? No. All URLs and file paths are redacted, but the crate names and versions are recorded as\-is. At present panic messages already disclose all this info and more. Also, chances are that you're legally obligated have to disclose use of specific open\-source crates anyway, since MIT and many other licenses require it. What about recording the compiler version? The compiler itself will start embedding it soon. On older versions it's already there in the debug info. On Unix you can run strings your_executable | grep 'rustc version' to see it. What about keeping track of versions of statically linked C libraries? Good question. I don't think they are exposed in any reasonable way right now. Would be a great addition, but not required for the initial launch. We can add it later in a backwards\-compatible way. Adopting the \-src crate convention would make it happen naturally, and will have other benefits as well, so that's probably the best route. What is blocking uplifting this into Cargo? Cargo itself is currently in a feature freeze. .SH EXIT STATUS .TP \fB0\fR Successful program execution. .TP \fB1\fR Unsuccessful program execution. .TP \fB101\fR The program panicked. .SH EXAMPLES .TP Build your project with dependency lists embedded in the binaries \fB# cargo auditable build \-\-release\fR .SH AUTHOR .P .RS 2 .nf Sergey "Shnatsel" Davidoff <shnatsel@gmail.com> 0707010000003B000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000003400000000cargo-auditable-0.6.2~0/cargo-auditable/gen_manpage0707010000003C000081A400000000000000000000000165D39F8500000249000000000000000000000000000000000000003F00000000cargo-auditable-0.6.2~0/cargo-auditable/gen_manpage/Cargo.lock# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "gen_manpage" version = "0.1.0" dependencies = [ "man", ] [[package]] name = "man" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebf5fa795187a80147b1ac10aaedcf5ffd3bbeb1838bda61801a1c9ad700a1c9" dependencies = [ "roff", ] [[package]] name = "roff" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33e4fb37ba46888052c763e4ec2acfedd8f00f62897b630cadb6298b833675e" 0707010000003D000081A400000000000000000000000165D39F8500000114000000000000000000000000000000000000003F00000000cargo-auditable-0.6.2~0/cargo-auditable/gen_manpage/Cargo.toml[package] name = "gen_manpage" version = "0.1.0" edition = "2021" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] man = "0.3" # Exclude the manpage generator from the main workspace [workspace]0707010000003E000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000003800000000cargo-auditable-0.6.2~0/cargo-auditable/gen_manpage/src0707010000003F000081A400000000000000000000000165D39F85000017C3000000000000000000000000000000000000004000000000cargo-auditable-0.6.2~0/cargo-auditable/gen_manpage/src/main.rsuse std::io::prelude::*; fn generate_man_page() -> String { man::prelude::Manual::new("cargo-auditable") .about("Embed a JSON formatted dependency tree into a dedicated linker section of the compiled executable") .author(man::prelude::Author::new("Sergey \"Shnatsel\" Davidoff").email("shnatsel@gmail.com")) .description(" Know the exact crate versions used to build your Rust executable. Audit binaries for known bugs or security vulnerabilities in production, at scale, with zero bookkeeping. This works by embedding data about the dependency tree in JSON format into a dedicated linker section of the compiled executable. Linux, Windows and Mac OS are officially supported. All other ELF targets should work, but are not tested on CI. WASM is currently not supported, but patches are welcome. The end goal is to get Cargo itself to encode this information in binaries. There is an RFC for an implementation within Cargo, for which this project paves the way: https://github.com/rust-lang/rfcs/pull/2801 ") .example(man::Example::new() .prompt("#") .text("Build your project with dependency lists embedded in the binaries") .command("cargo auditable build --release")) .custom(man::prelude::Section::new("Usage") .paragraph("cargo auditable works with any Cargo command. All arguments are passed to cargo as-is.")) .custom(man::prelude::Section::new("FAQ") .paragraph("Doesn't this bloat my binary? In a word, no. The embedded dependency list uses under 4kB even on large dependency trees with 400+ entries. This typically translates to between 1/1000 and 1/10,000 of the size of the binary. ") .paragraph("Can I make cargo always build with cargo auditable? Yes! For example, on Linux/macOS/etc add this to your .bashrc: alias cargo=\"cargo auditable\" If you're using a shell other than bash, or if using an alias is not an option, see https://github.com/rust-secure-code/cargo-auditable/blob/HEAD/REPLACING_CARGO.md. ") .paragraph("Is there any tooling to consume this data? Vulnerability reporting cargo audit v0.17.3+ can detect this data in binaries and report on vulnerabilities. See here for details. trivy v0.31.0+ detects this data in binaries and reports on vulnerabilities. See the v0.31.0 release notes for an end-to-end example. Recovering the dependency list syft v0.53.0+ has experimental support for detecting this data in binaries. When used on images or directories, Rust audit support must be enabled by adding the --catalogers all CLI option, e.g syft --catalogers all <container image containing Rust auditable binary>. rust-audit-info recovers the dependency list from a binary and prints it in JSON. It is also interoperable with existing tooling that consumes Cargo.lock via the JSON-to-TOML convertor. However, we recommend supporting the format natively; the format is designed to be very easy to parse, even if your language does not have a library for that yet. ") .paragraph("Can I read this data using a tool written in a different language? Yes. The data format is designed for interoperability with alternative implementations. In fact, parsing it only takes 5 lines of Python. See https://github.com/rust-secure-code/cargo-auditable/blob/HEAD/PARSING.md for documentation on parsing the data. ") .paragraph("What is the data format, exactly? The data format is described by the JSON schema https://github.com/rust-secure-code/cargo-auditable/blob/HEAD/cargo-auditable.schema.json. The JSON is Zlib-compressed and placed in a linker section named .dep-v0. You can find more info about parsing it here. ") .paragraph("What about embedded platforms? Embedded platforms where you cannot spare a byte should not add anything in the executable. Instead they should record the hash of every executable in a database and associate the hash with its Cargo.lock, compiler and LLVM version, build date, etc. This would make for an excellent Cargo wrapper or plugin. Since that can be done in a 5-line shell script, writing that tool is left as an exercise to the reader. ") .paragraph("Does this impact reproducible builds? The data format is specifically designed not to disrupt reproducible builds. It contains no timestamps, and the generated JSON is sorted to make sure it is identical between compilations. If anything, this helps with reproducible builds, since you know all the versions for a given binary now. ") .paragraph("Does this disclose any sensitive information? No. All URLs and file paths are redacted, but the crate names and versions are recorded as-is. At present panic messages already disclose all this info and more. Also, chances are that you're legally obligated have to disclose use of specific open-source crates anyway, since MIT and many other licenses require it. ") .paragraph("What about recording the compiler version? The compiler itself will start embedding it soon. On older versions it's already there in the debug info. On Unix you can run strings your_executable | grep 'rustc version' to see it. ") .paragraph("What about keeping track of versions of statically linked C libraries? Good question. I don't think they are exposed in any reasonable way right now. Would be a great addition, but not required for the initial launch. We can add it later in a backwards-compatible way. Adopting the -src crate convention would make it happen naturally, and will have other benefits as well, so that's probably the best route. ") .paragraph("What is blocking uplifting this into Cargo? Cargo itself is currently in a feature freeze. ") ) .render() } fn generate_man_page_file() -> Result<(), Box<dyn std::error::Error>> { let dest_path = "cargo-auditable.1"; let mut file = std::fs::File::create(dest_path)?; file.write_all(generate_man_page().as_bytes())?; Ok(()) } fn main() { generate_man_page_file().unwrap(); } 07070100000040000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002C00000000cargo-auditable-0.6.2~0/cargo-auditable/src07070100000041000081A400000000000000000000000165D39F8500000DBB000000000000000000000000000000000000003F00000000cargo-auditable-0.6.2~0/cargo-auditable/src/cargo_arguments.rsuse std::ffi::OsString; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] /// Includes only the cargo arguments we care about pub struct CargoArgs { pub offline: bool, pub locked: bool, pub frozen: bool, pub config: Vec<String>, } impl CargoArgs { /// Extracts Cargo flags from the arguments to the current process pub fn from_args() -> CargoArgs { // we .skip(3) to get over `cargo auditable build` and to the start of the flags let raw_args: Vec<OsString> = std::env::args_os().skip(3).collect(); Self::from_args_vec(raw_args) } /// Split into its own function for unit testing fn from_args_vec(mut raw_args: Vec<OsString>) -> CargoArgs { // if there is a -- in the invocation somewhere, only parse up to it if let Some(position) = raw_args.iter().position(|s| s == "--") { raw_args.truncate(position); } let mut parser = pico_args::Arguments::from_vec(raw_args); CargoArgs { config: parser.values_from_str("--config").unwrap(), offline: parser.contains("--offline"), locked: parser.contains("--locked"), frozen: parser.contains("--frozen"), } } /// Recovers `SerializedCargoArgs` from an environment variable (if it was exported earlier) pub fn from_env() -> Result<Self, std::env::VarError> { let json_args = std::env::var("CARGO_AUDITABLE_ORIG_ARGS")?; // We unwrap here because we've serialized these args ourselves and they should roundtrip cleanly. // Deserialization would only fail if someone tampered with them in transit. Ok(serde_json::from_str(&json_args).unwrap()) } } #[cfg(test)] mod tests { use super::*; #[test] fn basic_parsing() { let input = [ "cargo", "auditable", "build", "--locked", "--config", "net.git-fetch-with-cli=true", "--offline", ]; let raw_args = input.iter().map(OsString::from).collect(); let args = CargoArgs::from_args_vec(raw_args); assert!(args.locked); assert!(args.offline); assert!(!args.frozen); assert_eq!(args.config, vec!["net.git-fetch-with-cli=true"]); } #[test] fn with_unrelated_flags() { let input = [ "cargo", "auditable", "build", "--locked", "--target", "x86_64-unknown-linux-gnu", "--release", "--config", "net.git-fetch-with-cli=true", "--offline", "--ignore-rust-version", ]; let raw_args = input.iter().map(OsString::from).collect(); let args = CargoArgs::from_args_vec(raw_args); assert!(args.locked); assert!(args.offline); assert!(!args.frozen); assert_eq!(args.config, vec!["net.git-fetch-with-cli=true"]); } #[test] fn double_dash_to_ignore_args() { let input = [ "cargo", "auditable", "run", "--release", "--config", "net.git-fetch-with-cli=true", "--", "--offline", ]; let raw_args = input.iter().map(OsString::from).collect(); let args = CargoArgs::from_args_vec(raw_args); assert!(!args.offline); assert_eq!(args.config, vec!["net.git-fetch-with-cli=true"]); } } 07070100000042000081A400000000000000000000000165D39F8500000ACD000000000000000000000000000000000000003F00000000cargo-auditable-0.6.2~0/cargo-auditable/src/cargo_auditable.rsuse crate::cargo_arguments::CargoArgs; use std::{env, process::Command}; pub fn main() { // set the RUSTFLAGS environment variable to inject our object and call Cargo with all the Cargo args // Cargo sets the path to itself in the `CARGO` environment variable: // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-3rd-party-subcommands // This is also useful for using `cargo auditable` as a drop-in replacement for Cargo. let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into()); let mut command = Command::new(cargo); // Pass along all our arguments; we don't currently have any args specific to `cargo auditable` // We skip argv[0] which is the path to this binary and the first argument which is 'auditable' passed by Cargo command.args(env::args_os().skip(2)); // Set the environment variable to use this binary as a rustc wrapper, that's when we do the real work // It's important that we set RUSTC_WORKSPACE_WRAPPER and not RUSTC_WRAPPER because only the former invalidates cache. // If we use RUSTC_WRAPPER, running `cargo auditable` will not trigger a rebuild. // The WORKSPACE part is a bit of a misnomer: it will be run for a local crate even if there's just one, not a workspace. // Security note: // `std::env::current_exe()` is not supposed to be relied on for security - the binary may be moved, etc. // But should not a code execution vulnerability since whoever sets this could set RUSTC_WORKSPACE_WRAPPER themselves // This would matter if the binary was made setuid, but it isn't, so this should be fine. let path_to_this_binary = std::env::current_exe().unwrap(); command.env("RUSTC_WORKSPACE_WRAPPER", path_to_this_binary); // Pass on the arguments we received so that they can be inspected later. // We're interested in flags like `--offline` and `--config` which have to be passed to `cargo metadata` later. // The shell has already split them for us and we don't want to mangle them, but we need to round-trip them // through a string. Since we already depend on `serde-json` and it does the job, use JSON. // This doesn't support non-UTF8 arguments, but `cargo_metadata` crate doesn't support them either, // so this is not an issue right now. // If it ever becomes one, we could use the `serde-bytes-repr` crate for a clean round-trip. let args = CargoArgs::from_args(); let args_in_json = serde_json::to_string(&args).unwrap(); command.env("CARGO_AUDITABLE_ORIG_ARGS", args_in_json); let results = command .status() .expect("Failed to invoke cargo! Make sure it's in your $PATH"); std::process::exit(results.code().unwrap()); } 07070100000043000081A400000000000000000000000165D39F850000106D000000000000000000000000000000000000004200000000cargo-auditable-0.6.2~0/cargo-auditable/src/collect_audit_data.rsuse auditable_serde::VersionInfo; use cargo_metadata::{Metadata, MetadataCommand}; use miniz_oxide::deflate::compress_to_vec_zlib; use std::{convert::TryFrom, str::from_utf8}; use crate::{cargo_arguments::CargoArgs, rustc_arguments::RustcArgs}; /// Calls `cargo metadata` to obtain the dependency tree, serializes it to JSON and compresses it pub fn compressed_dependency_list(rustc_args: &RustcArgs, target_triple: &str) -> Vec<u8> { let metadata = get_metadata(rustc_args, target_triple); let version_info = VersionInfo::try_from(&metadata).unwrap(); let json = serde_json::to_string(&version_info).unwrap(); // compression level 7 makes this complete in a few milliseconds, so no need to drop to a lower level in debug mode let compressed_json = compress_to_vec_zlib(json.as_bytes(), 7); compressed_json } fn get_metadata(args: &RustcArgs, target_triple: &str) -> Metadata { let mut metadata_command = MetadataCommand::new(); // Cargo sets the path to itself in the `CARGO` environment variable: // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-3rd-party-subcommands // This is also useful for using `cargo auditable` as a drop-in replacement for Cargo. if let Some(path) = std::env::var_os("CARGO") { metadata_command.cargo_path(path); } // Point cargo-metadata to the correct Cargo.toml in a workspace. // CARGO_MANIFEST_DIR env var will be set by Cargo when it calls our rustc wrapper let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR").unwrap(); metadata_command.current_dir(manifest_dir); // Pass the features that are actually enabled for this crate to cargo-metadata let mut features = args.enabled_features(); if let Some(index) = features.iter().position(|x| x == &"default") { features.remove(index); } else { metadata_command.features(cargo_metadata::CargoOpt::NoDefaultFeatures); } let owned_features: Vec<String> = features.iter().map(|s| s.to_string()).collect(); metadata_command.features(cargo_metadata::CargoOpt::SomeFeatures(owned_features)); // Restrict the dependency resolution to just the platform the binary is being compiled for. // By default `cargo metadata` resolves the dependency tree for all platforms. let mut other_args = vec!["--filter-platform".to_owned(), target_triple.to_owned()]; // Pass arguments such as `--config`, `--offline` and `--locked` // from the original CLI invocation of `cargo auditable` let orig_args = CargoArgs::from_env() .expect("Env var 'CARGO_AUDITABLE_ORIG_ARGS' set by 'cargo-auditable' is unset!"); if orig_args.offline { other_args.push("--offline".to_owned()); } if orig_args.frozen { other_args.push("--frozen".to_owned()); } if orig_args.locked { other_args.push("--locked".to_owned()); } for arg in orig_args.config { other_args.push("--config".to_owned()); other_args.push(arg); } // This can only be done once, multiple calls will replace previously set options. metadata_command.other_options(other_args); // Get the underlying std::process::Command and re-implement MetadataCommand::exec, // to clear RUSTC_WORKSPACE_WRAPPER in the child process to avoid recursion. // The alternative would be modifying the environment of our own process, // which is sketchy and discouraged on POSIX because it's not thread-safe: // https://doc.rust-lang.org/stable/std/env/fn.remove_var.html let mut metadata_command = metadata_command.cargo_command(); metadata_command.env_remove("RUSTC_WORKSPACE_WRAPPER"); let output = metadata_command.output().unwrap(); if !output.status.success() { panic!( "cargo metadata failure: {}", String::from_utf8_lossy(&output.stderr) ); } let stdout = from_utf8(&output.stdout) .expect("cargo metadata output not utf8") .lines() .find(|line| line.starts_with('{')) .expect("cargo metadata output not json"); MetadataCommand::parse(stdout).expect("failed to parse cargo metadata output") } 07070100000044000081A400000000000000000000000165D39F8500000606000000000000000000000000000000000000003400000000cargo-auditable-0.6.2~0/cargo-auditable/src/main.rs#![forbid(unsafe_code)] mod cargo_arguments; mod cargo_auditable; mod collect_audit_data; mod object_file; mod rustc_arguments; mod rustc_wrapper; mod target_info; use std::process::exit; /// Dispatches the call to either `cargo auditable` when invoked through cargo, /// or to `rustc_wrapper` when Cargo internals invoke it fn main() { let first_arg = std::env::args_os().nth(1); if let Some(arg) = first_arg { if arg == "auditable" { cargo_auditable::main() } // When this binary is called as a rustc wrapper, the first argument is the path to rustc: // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads // It's important to read it because it can be overridden via env vars or config files. // In order to distinguish that from someone running the binary directly by mistake, // we check if the env var we set earlier is still present. // The "rustc" special-case is purely to accommodate the weird things `sccache` does: // https://github.com/rust-secure-code/cargo-auditable/issues/87 // We should push back and make it sccache's problem if this ever causes issues. else if arg == "rustc" || std::env::var_os("CARGO_AUDITABLE_ORIG_ARGS").is_some() { rustc_wrapper::main(&arg) } else { shoo(); } } else { shoo(); } } fn shoo() -> ! { eprintln!("'cargo auditable' should be invoked through Cargo"); exit(1); } 07070100000045000081A400000000000000000000000165D39F8500002CE6000000000000000000000000000000000000003B00000000cargo-auditable-0.6.2~0/cargo-auditable/src/object_file.rs//! Shamelessly copied from rustc codebase: //! https://github.com/rust-lang/rust/blob/dcca6a375bd4eddb3deea7038ebf29d02af53b48/compiler/rustc_codegen_ssa/src/back/metadata.rs#L97-L206 //! and butchered ever so slightly use object::write::{self, StandardSegment, Symbol, SymbolSection}; use object::{ elf, Architecture, BinaryFormat, Endianness, FileFlags, SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope, }; use crate::target_info::RustcTargetInfo; /// Returns None if the architecture is not supported pub fn create_metadata_file( // formerly `create_compressed_metadata_file` in the rustc codebase target_info: &RustcTargetInfo, target_triple: &str, contents: &[u8], symbol_name: &str, ) -> Option<Vec<u8>> { let mut file = create_object_file(target_info, target_triple)?; let section = file.add_section( file.segment_name(StandardSegment::Data).to_vec(), b".dep-v0".to_vec(), SectionKind::ReadOnlyData, ); if let BinaryFormat::Elf = file.format() { // Explicitly set no flags to avoid SHF_ALLOC default for data section. file.section_mut(section).flags = SectionFlags::Elf { sh_flags: 0 }; }; let offset = file.append_section_data(section, contents, 1); // For MachO and probably PE this is necessary to prevent the linker from throwing away the // .rustc section. For ELF this isn't necessary, but it also doesn't harm. file.add_symbol(Symbol { name: symbol_name.as_bytes().to_vec(), value: offset, size: contents.len() as u64, kind: SymbolKind::Data, scope: SymbolScope::Dynamic, weak: false, section: SymbolSection::Section(section), flags: SymbolFlags::None, }); Some(file.write().unwrap()) } fn create_object_file( info: &RustcTargetInfo, target_triple: &str, ) -> Option<write::Object<'static>> { // This conversion evolves over time, and has some subtle logic for MIPS and RISC-V later on, that also evolves. // If/when uplifiting this into Cargo, we will need to extract this code from rustc and put it in the `object` crate // so that it could be shared between rustc and Cargo. let endianness = match info["target_endian"].as_str() { "little" => Endianness::Little, "big" => Endianness::Big, _ => unreachable!(), }; let architecture = match info["target_arch"].as_str() { "arm" => Architecture::Arm, "aarch64" => { if info["target_pointer_width"].as_str() == "32" { Architecture::Aarch64_Ilp32 } else { Architecture::Aarch64 } } "x86" => Architecture::I386, "s390x" => Architecture::S390x, "mips" => Architecture::Mips, "mips64" => Architecture::Mips64, "x86_64" => { if info["target_pointer_width"].as_str() == "32" { Architecture::X86_64_X32 } else { Architecture::X86_64 } } "powerpc" => Architecture::PowerPc, "powerpc64" => Architecture::PowerPc64, "riscv32" => Architecture::Riscv32, "riscv64" => Architecture::Riscv64, "sparc64" => Architecture::Sparc64, // Unsupported architecture. _ => return None, }; let binary_format = if target_triple.contains("-apple-") { BinaryFormat::MachO } else if target_triple.contains("-windows-") { BinaryFormat::Coff } else { BinaryFormat::Elf }; let mut file = write::Object::new(binary_format, architecture, endianness); let e_flags = match architecture { Architecture::Mips => { // the original code matches on info we don't have to support pre-1999 MIPS variants: // https://github.com/rust-lang/rust/blob/dcca6a375bd4eddb3deea7038ebf29d02af53b48/compiler/rustc_codegen_ssa/src/back/metadata.rs#L144C3-L153 // We can't support them, so this part was was modified significantly. let arch = if target_triple.contains("r6") { elf::EF_MIPS_ARCH_32R6 } else { elf::EF_MIPS_ARCH_32R2 }; // end of modified part // The only ABI LLVM supports for 32-bit MIPS CPUs is o32. let mut e_flags = elf::EF_MIPS_CPIC | elf::EF_MIPS_ABI_O32 | arch; // commented out: insufficient info to support this outside rustc // if sess.target.options.relocation_model != RelocModel::Static { // e_flags |= elf::EF_MIPS_PIC; // } if target_triple.contains("r6") { e_flags |= elf::EF_MIPS_NAN2008; } e_flags } Architecture::Mips64 => { // copied from `mips64el-linux-gnuabi64-gcc foo.c -c` #[allow(clippy::let_and_return)] // for staying as close to upstream as possible let e_flags = elf::EF_MIPS_CPIC | elf::EF_MIPS_PIC | if target_triple.contains("r6") { elf::EF_MIPS_ARCH_64R6 | elf::EF_MIPS_NAN2008 } else { elf::EF_MIPS_ARCH_64R2 }; e_flags } Architecture::Riscv32 | Architecture::Riscv64 => { // Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc let mut e_flags: u32 = 0x0; let features = riscv_features(target_triple); // Check if compressed is enabled if features.contains('c') { e_flags |= elf::EF_RISCV_RVC; } // Select the appropriate floating-point ABI if features.contains('d') { e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE; } else if features.contains('f') { e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE; } else { e_flags |= elf::EF_RISCV_FLOAT_ABI_SOFT; } e_flags } _ => 0, }; // adapted from LLVM's `MCELFObjectTargetWriter::getOSABI` let os_abi = match info["target_os"].as_str() { "hermit" => elf::ELFOSABI_STANDALONE, "freebsd" => elf::ELFOSABI_FREEBSD, "solaris" => elf::ELFOSABI_SOLARIS, _ => elf::ELFOSABI_NONE, }; let abi_version = 0; file.flags = FileFlags::Elf { os_abi, abi_version, e_flags, }; Some(file) } // This function was not present in the original rustc code, which simply used // `sess.target.options.features` // We do not have access to compiler internals, so we have to reimplement this function. fn riscv_features(target_triple: &str) -> String { let arch = target_triple.split('-').next().unwrap(); assert_eq!(&arch[..5], "riscv"); let mut extensions = arch[7..].to_owned(); if extensions.contains('g') { extensions.push_str("imadf"); } extensions } #[cfg(test)] mod tests { use super::*; use crate::target_info::parse_rustc_target_info; #[test] fn test_riscv_abi_detection() { // real-world target with double floats let features = riscv_features("riscv64gc-unknown-linux-gnu"); assert!(features.contains('c')); assert!(features.contains('d')); assert!(features.contains('f')); // real-world target without floats let features = riscv_features("riscv32imac-unknown-none-elf"); assert!(features.contains('c')); assert!(!features.contains('d')); assert!(!features.contains('f')); // real-world target without floats or compression let features = riscv_features("riscv32i-unknown-none-elf"); assert!(!features.contains('c')); assert!(!features.contains('d')); assert!(!features.contains('f')); // made-up target without compression and with single floats let features = riscv_features("riscv32if-unknown-none-elf"); assert!(!features.contains('c')); assert!(!features.contains('d')); assert!(features.contains('f')); } #[test] fn test_create_object_file_linux() { let rustc_output = br#"debug_assertions target_arch="x86_64" target_endian="little" target_env="gnu" target_family="unix" target_feature="fxsr" target_feature="sse" target_feature="sse2" target_os="linux" target_pointer_width="64" target_vendor="unknown" unix "#; let target_triple = "x86_64-unknown-linux-gnu"; let target_info = parse_rustc_target_info(rustc_output); let result = create_object_file(&target_info, target_triple).unwrap(); assert_eq!(result.format(), BinaryFormat::Elf); assert_eq!(result.architecture(), Architecture::X86_64); } #[test] fn test_create_object_file_windows_msvc() { let rustc_output = br#"debug_assertions target_arch="x86_64" target_endian="little" target_env="msvc" target_family="windows" target_feature="fxsr" target_feature="sse" target_feature="sse2" target_os="windows" target_pointer_width="64" target_vendor="pc" windows "#; let target_triple = "x86_64-pc-windows-msvc"; let target_info = parse_rustc_target_info(rustc_output); let result = create_object_file(&target_info, target_triple).unwrap(); assert_eq!(result.format(), BinaryFormat::Coff); assert_eq!(result.architecture(), Architecture::X86_64); } #[test] fn test_create_object_file_windows_gnu() { let rustc_output = br#"debug_assertions target_arch="x86_64" target_endian="little" target_env="gnu" target_family="windows" target_feature="fxsr" target_feature="sse" target_feature="sse2" target_os="windows" target_pointer_width="64" target_vendor="pc" windows "#; let target_triple = "x86_64-pc-windows-gnu"; let target_info = crate::target_info::parse_rustc_target_info(rustc_output); let result = create_object_file(&target_info, target_triple).unwrap(); assert_eq!(result.format(), BinaryFormat::Coff); assert_eq!(result.architecture(), Architecture::X86_64); } #[test] fn test_create_object_file_macos() { let rustc_output = br#"debug_assertions target_arch="x86_64" target_endian="little" target_env="" target_family="unix" target_feature="fxsr" target_feature="sse" target_feature="sse2" target_feature="sse3" target_feature="ssse3" target_os="macos" target_pointer_width="64" target_vendor="apple" unix "#; let target_triple = "x86_64-apple-darwin"; let target_info = crate::target_info::parse_rustc_target_info(rustc_output); let result = create_object_file(&target_info, target_triple).unwrap(); assert_eq!(result.format(), BinaryFormat::MachO); assert_eq!(result.architecture(), Architecture::X86_64); } #[test] fn test_create_object_file_linux_arm() { let rustc_output = br#"debug_assertions target_arch="aarch64" target_endian="little" target_env="gnu" target_family="unix" target_os="linux" target_pointer_width="64" target_vendor="unknown" unix "#; let target_triple = "aarch64-unknown-linux-gnu"; let target_info = parse_rustc_target_info(rustc_output); let result = create_object_file(&target_info, target_triple).unwrap(); assert_eq!(result.format(), BinaryFormat::Elf); assert_eq!(result.architecture(), Architecture::Aarch64); } } 07070100000046000081A400000000000000000000000165D39F85000007CD000000000000000000000000000000000000003F00000000cargo-auditable-0.6.2~0/cargo-auditable/src/rustc_arguments.rs//! Parses rustc arguments to extract the info not provided via environment variables. use std::{ffi::OsString, path::PathBuf}; // We use pico-args because we only need to extract a few specific arguments out of a larger set, // and other parsers (rustc's `getopts`, cargo's `clap`) make that difficult. // // We also intentionally do very little validation, to avoid rejecting new configurations // that may be added to rustc in the future. // // For reference, the rustc argument parsing code is at // https://github.com/rust-lang/rust/blob/26ecd44160f54395b3bd5558cc5352f49cb0a0ba/compiler/rustc_session/src/config.rs /// Includes only the rustc arguments we care about pub struct RustcArgs { pub crate_name: String, pub crate_types: Vec<String>, pub cfg: Vec<String>, pub out_dir: PathBuf, pub target: Option<String>, pub print: Vec<String>, } impl RustcArgs { pub fn enabled_features(&self) -> Vec<&str> { let mut result = Vec::new(); for item in &self.cfg { if item.starts_with("feature=\"") { // feature names cannot contain quotes according to the documentation: // https://doc.rust-lang.org/cargo/reference/features.html#the-features-section result.push(item.split('"').nth(1).unwrap()); } } result } } pub fn parse_args() -> Result<RustcArgs, pico_args::Error> { let raw_args: Vec<OsString> = std::env::args_os().skip(2).collect(); let mut parser = pico_args::Arguments::from_vec(raw_args); Ok(RustcArgs { crate_name: parser.value_from_str("--crate-name")?, crate_types: parser.values_from_str("--crate-type")?, cfg: parser.values_from_str("--cfg")?, out_dir: parser.value_from_os_str::<&str, PathBuf, pico_args::Error>("--out-dir", |s| { Ok(PathBuf::from(s)) })?, target: parser.opt_value_from_str("--target")?, print: parser.values_from_str("--print")?, }) } 07070100000047000081A400000000000000000000000165D39F85000016B7000000000000000000000000000000000000003D00000000cargo-auditable-0.6.2~0/cargo-auditable/src/rustc_wrapper.rsuse std::{ env, ffi::{OsStr, OsString}, process::Command, }; use crate::{collect_audit_data, object_file, rustc_arguments, target_info}; use std::io::BufRead; pub fn main(rustc_path: &OsStr) { let mut command = rustc_command(rustc_path); // Binaries and C dynamic libraries are not built as non-primary packages, // so this should not cause issues with Cargo caches. if env::var_os("CARGO_PRIMARY_PACKAGE").is_some() { let arg_parsing_result = rustc_arguments::parse_args(); if let Ok(args) = rustc_arguments::parse_args() { // Only inject audit data into crate types 'bin' and 'cdylib', // and only if --print is not specified (which disables compilation) if args.print.is_empty() && (args.crate_types.contains(&"bin".to_owned()) || args.crate_types.contains(&"cdylib".to_owned())) { // Get the audit data to embed let target_triple = args .target .clone() .unwrap_or_else(|| rustc_host_target_triple(rustc_path)); let contents: Vec<u8> = collect_audit_data::compressed_dependency_list(&args, &target_triple); // write the audit info to an object file let target_info = target_info::rustc_target_info(rustc_path, &target_triple); let binfile = object_file::create_metadata_file( &target_info, &target_triple, &contents, "AUDITABLE_VERSION_INFO", ); if let Some(file) = binfile { // Place the audit data in the output dir. // We can place it anywhere really, the only concern is clutter and name collisions, // and the target dir is locked so we're probably good let filename = format!("{}_audit_data.o", args.crate_name); let path = args.out_dir.join(filename); std::fs::write(&path, file).expect("Unable to write output file"); // Modify the rustc command to link the object file with audit data let mut linker_command = OsString::from("-Clink-arg="); linker_command.push(&path); command.arg(linker_command); // Prevent the symbol from being removed as unused by the linker if target_triple.contains("-apple-") { command.arg("-Clink-arg=-Wl,-u,_AUDITABLE_VERSION_INFO"); } else { command.arg("-Clink-arg=-Wl,--undefined=AUDITABLE_VERSION_INFO"); } } else { // create_metadata_file() returned None, indicating an unsupported architecture eprintln!("WARNING: target '{target_triple}' is not supported by 'cargo auditable'!\n\ The build will continue, but no audit data will be injected into the binary."); } } } else { // Failed to parse rustc arguments. // This may be due to a `rustc -vV` call, or similar non-compilation command. // This never happens with Cargo - it does call `rustc -vV`, // but either bypasses the wrapper or doesn't set CARGO_PRIMARY_PACKAGE=true. // However it does happen with `sccache`: // https://github.com/rust-secure-code/cargo-auditable/issues/87 // This is probably a bug in `sccache`, but it's easier to fix here. // There are many non-compilation flags (and they can be compound), // so parsing them properly adds a lot of complexity. // So we just check if `--crate-name` is passed and if not, // assume that it's a non-compilation command. if env::args_os() .skip(2) .any(|arg| arg == OsStr::new("--crate-name")) { // this was a compilation command, bail arg_parsing_result.unwrap(); } // for commands like `rustc --version` we just pass on the arguments without changes } } // Invoke rustc let results = command.status().unwrap_or_else(|err| { let mut command_with_args: Vec<&OsStr> = vec![command.get_program()]; command_with_args.extend(command.get_args()); eprintln!( "Failed to invoke rustc! Make sure it's in your $PATH\n\ The error was: {}\n\ The attempted call was: {:?}", err, command_with_args, ); std::process::exit(1); }); std::process::exit(results.code().unwrap()); } /// Creates a rustc command line and populates arguments from arguments passed to us. fn rustc_command(rustc_path: &OsStr) -> Command { let mut command = Command::new(rustc_path); // Pass along all the arguments that Cargo meant to pass to rustc // We skip the path to our binary as well as the first argument passed by Cargo, // which is the path to rustc to use (or just "rustc") command.args(env::args_os().skip(2)); command } /// Returns the default target triple for the rustc we're running fn rustc_host_target_triple(rustc_path: &OsStr) -> String { Command::new(rustc_path) .arg("-vV") .output() .expect("Failed to invoke rustc! Is it in your $PATH?") .stdout .lines() .map(|l| l.unwrap()) .find(|l| l.starts_with("host: ")) .map(|l| l[6..].to_string()) .expect("Failed to parse rustc output to determine the current platform. Please report this bug!") } 07070100000048000081A400000000000000000000000165D39F850000094B000000000000000000000000000000000000003B00000000cargo-auditable-0.6.2~0/cargo-auditable/src/target_info.rsuse std::{ffi::OsStr, io::BufRead}; pub type RustcTargetInfo = std::collections::HashMap<String, String>; pub fn rustc_target_info(rustc_path: &OsStr, target_triple: &str) -> RustcTargetInfo { // this is hand-rolled because the relevant piece of Cargo is hideously complex for some reason parse_rustc_target_info(&std::process::Command::new(rustc_path) .arg("--print=cfg") .arg(format!("--target={target_triple}")) //not being parsed by the shell, so not a vulnerability .output() .unwrap_or_else(|_| panic!("Failed to invoke rustc; make sure it's in $PATH and that '{target_triple}' is a valid target triple")) .stdout) } pub(crate) fn parse_rustc_target_info(rustc_output: &[u8]) -> RustcTargetInfo { // Decoupled from `rustc_target_info` to allow unit testing // `pub(crate)` so that unit tests in other modules could use it rustc_output .lines() .filter_map(|line| { let line = line.unwrap(); // rustc outputs some free-standing values as well as key-value pairs // we're only interested in the pairs, which are separated by '=' and the value is quoted if line.contains('=') { let key = line.split('=').next().unwrap(); let mut value: String = line.split('=').skip(1).collect(); // strip first and last chars of the quoted value. Verify that they're quotes assert!(value.pop().unwrap() == '"'); assert!(value.remove(0) == '"'); Some((key.to_owned(), value)) } else { None } }) .collect() } #[cfg(test)] mod tests { use super::*; #[test] fn test_rustc_parser_linux() { let rustc_output = br#"debug_assertions target_arch="x86_64" target_endian="little" target_env="gnu" target_family="unix" target_feature="fxsr" target_feature="sse" target_feature="sse2" target_os="linux" target_pointer_width="64" target_vendor="unknown" unix "#; let result = parse_rustc_target_info(rustc_output); assert_eq!(result.get("target_arch").unwrap(), "x86_64"); assert_eq!(result.get("target_endian").unwrap(), "little"); assert_eq!(result.get("target_pointer_width").unwrap(), "64"); assert_eq!(result.get("target_vendor").unwrap(), "unknown"); } } 07070100000049000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002E00000000cargo-auditable-0.6.2~0/cargo-auditable/tests0707010000004A000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000003700000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures0707010000004B000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004E00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep0707010000004C000081A400000000000000000000000165D39F8500000063000000000000000000000000000000000000005900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/Cargo.toml[workspace] members = [ "top_level_crate", "build_dep", "runtime_dep_of_build_dep", ] 0707010000004D000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005800000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/build_dep0707010000004E000081A400000000000000000000000165D39F85000000F4000000000000000000000000000000000000006300000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/build_dep/Cargo.toml[package] name = "build_dep" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] runtime_dep_of_build_dep = {path = "../runtime_dep_of_build_dep"} 0707010000004F000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005C00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/build_dep/src07070100000050000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000006300000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/build_dep/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 07070100000051000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006700000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep07070100000052000081A400000000000000000000000165D39F85000000C1000000000000000000000000000000000000007200000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/Cargo.toml[package] name = "runtime_dep_of_build_dep" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 07070100000053000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006B00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/src07070100000054000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000007200000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 07070100000055000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005E00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/top_level_crate07070100000056000081A400000000000000000000000165D39F85000000E2000000000000000000000000000000000000006900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/top_level_crate/Cargo.toml[package] name = "top_level_crate" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] build_dep = {path = "../build_dep"} 07070100000057000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006200000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/top_level_crate/src07070100000058000081A400000000000000000000000165D39F850000002D000000000000000000000000000000000000006A00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/top_level_crate/src/main.rsfn main() { println!("Hello, world!"); } 07070100000059000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004F00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/crate_with_build_script0707010000005A000081A400000000000000000000000165D39F85000000CD000000000000000000000000000000000000005A00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/crate_with_build_script/Cargo.toml[package] name = "crate_with_build_script" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] [dependencies] 0707010000005B000081A400000000000000000000000165D39F850000002B000000000000000000000000000000000000005800000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/crate_with_build_script/build.rsfn main() { println!("Hello there!"); }0707010000005C000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005300000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/crate_with_build_script/src0707010000005D000081A400000000000000000000000165D39F850000002D000000000000000000000000000000000000005B00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/crate_with_build_script/src/main.rsfn main() { println!("Hello, world!"); } 0707010000005E000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path0707010000005F000081A400000000000000000000000165D39F8500000065000000000000000000000000000000000000005400000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/Cargo.toml[workspace] members = [ "top_level_crate", "runtime_dep", "build_dep_of_runtime_dep", ] 07070100000060000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006200000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/build_dep_of_runtime_dep07070100000061000081A400000000000000000000000165D39F85000000C1000000000000000000000000000000000000006D00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/build_dep_of_runtime_dep/Cargo.toml[package] name = "build_dep_of_runtime_dep" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 07070100000062000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006600000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/build_dep_of_runtime_dep/src07070100000063000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000006D00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/build_dep_of_runtime_dep/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 07070100000064000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005500000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/runtime_dep07070100000065000081A400000000000000000000000165D39F85000000FC000000000000000000000000000000000000006000000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/runtime_dep/Cargo.toml[package] name = "runtime_dep" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] build_dep_of_runtime_dep = {path = "../build_dep_of_runtime_dep"} 07070100000066000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/runtime_dep/src07070100000067000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000006000000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/runtime_dep/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 07070100000068000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/top_level_crate07070100000069000081A400000000000000000000000165D39F85000000E0000000000000000000000000000000000000006400000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/top_level_crate/Cargo.toml[package] name = "top_level_crate" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] runtime_dep = {path = "../runtime_dep"} 0707010000006A000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005D00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/top_level_crate/src0707010000006B000081A400000000000000000000000165D39F850000002D000000000000000000000000000000000000006500000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/custom_rustc_path/top_level_crate/src/main.rsfn main() { println!("Hello, world!"); } 0707010000006C000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lib_and_bin_crate0707010000006D000081A400000000000000000000000165D39F85000000C7000000000000000000000000000000000000005400000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lib_and_bin_crate/Cargo.toml[package] name = "lib_and_bin_crate" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] [dependencies] 0707010000006E000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004D00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lib_and_bin_crate/src0707010000006F000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005100000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lib_and_bin_crate/src/bin07070100000070000081A400000000000000000000000165D39F850000005A000000000000000000000000000000000000006000000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lib_and_bin_crate/src/bin/some_binary.rsuse lib_and_bin_crate::some_library_function; fn main() { some_library_function(); } 07070100000071000081A400000000000000000000000165D39F85000000B7000000000000000000000000000000000000005400000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lib_and_bin_crate/src/lib.rspub fn some_library_function() { panic!("oh noes"); } #[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 07070100000072000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004800000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lto_binary_crate07070100000073000081A400000000000000000000000165D39F85000000E2000000000000000000000000000000000000005300000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lto_binary_crate/Cargo.toml[package] name = "lto_binary_crate" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] [workspace] [profile.release] lto=true 07070100000074000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004C00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lto_binary_crate/src07070100000075000081A400000000000000000000000165D39F850000002D000000000000000000000000000000000000005400000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/lto_binary_crate/src/main.rsfn main() { println!("Hello, world!"); } 07070100000076000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004E00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps07070100000077000081A400000000000000000000000165D39F8500000052000000000000000000000000000000000000005900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps/Cargo.toml[workspace] members = [ "with_platform_dep", "should_not_be_included", ] 07070100000078000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006500000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps/should_not_be_included07070100000079000081A400000000000000000000000165D39F85000000BF000000000000000000000000000000000000007000000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps/should_not_be_included/Cargo.toml[package] name = "should_not_be_included" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 0707010000007A000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps/should_not_be_included/src0707010000007B000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000007000000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps/should_not_be_included/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 0707010000007C000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006000000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps/with_platform_dep0707010000007D000081A400000000000000000000000165D39F850000012B000000000000000000000000000000000000006B00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps/with_platform_dep/Cargo.toml[package] name = "with_platform_dep" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] [target.'cfg(target_arch = "m68k")'.dependencies] should_not_be_included = {path = "../should_not_be_included"} 0707010000007E000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006400000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps/with_platform_dep/src0707010000007F000081A400000000000000000000000165D39F850000002D000000000000000000000000000000000000006C00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/platform_specific_deps/with_platform_dep/src/main.rsfn main() { println!("Hello, world!"); } 07070100000080000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004E00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep07070100000081000081A400000000000000000000000165D39F8500000065000000000000000000000000000000000000005900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/Cargo.toml[workspace] members = [ "top_level_crate", "runtime_dep", "build_dep_of_runtime_dep", ] 07070100000082000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006700000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/build_dep_of_runtime_dep07070100000083000081A400000000000000000000000165D39F85000000C1000000000000000000000000000000000000007200000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/build_dep_of_runtime_dep/Cargo.toml[package] name = "build_dep_of_runtime_dep" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 07070100000084000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006B00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/build_dep_of_runtime_dep/src07070100000085000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000007200000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/build_dep_of_runtime_dep/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 07070100000086000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005A00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/runtime_dep07070100000087000081A400000000000000000000000165D39F85000000FC000000000000000000000000000000000000006500000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/runtime_dep/Cargo.toml[package] name = "runtime_dep" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] build_dep_of_runtime_dep = {path = "../build_dep_of_runtime_dep"} 07070100000088000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005E00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/runtime_dep/src07070100000089000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000006500000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/runtime_dep/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 0707010000008A000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005E00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/top_level_crate0707010000008B000081A400000000000000000000000165D39F85000000E0000000000000000000000000000000000000006900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/top_level_crate/Cargo.toml[package] name = "top_level_crate" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] runtime_dep = {path = "../runtime_dep"} 0707010000008C000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000006200000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/top_level_crate/src0707010000008D000081A400000000000000000000000165D39F850000002D000000000000000000000000000000000000006A00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/top_level_crate/src/main.rsfn main() { println!("Hello, world!"); } 0707010000008E000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004100000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace0707010000008F000081A400000000000000000000000165D39F8500000147000000000000000000000000000000000000004C00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/Cargo.toml[workspace] members = [ # lib crate, no cdylib or bin target "library_crate", # dependes on library_crate, has bin target "binary_and_cdylib_crate", # depends on library_crate and binary_and_cdylib_crate optionally, just library_crate by default # produces bin and cdylib "crate_with_features", ] 07070100000090000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/binary_and_cdylib_crate07070100000091000081A400000000000000000000000165D39F8500000122000000000000000000000000000000000000006400000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/binary_and_cdylib_crate/Cargo.toml[package] name = "binary_and_cdylib_crate" version = "0.1.0" edition = "2021" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate_type = ["cdylib", "lib"] [dependencies] library_crate = {path = "../library_crate"} 07070100000092000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005D00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/binary_and_cdylib_crate/src07070100000093000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000006400000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/binary_and_cdylib_crate/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 07070100000094000081A400000000000000000000000165D39F850000002D000000000000000000000000000000000000006500000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/binary_and_cdylib_crate/src/main.rsfn main() { println!("Hello, world!"); } 07070100000095000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005500000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/crate_with_features07070100000096000081A400000000000000000000000165D39F85000001EB000000000000000000000000000000000000006000000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/crate_with_features/Cargo.toml[package] name = "crate_with_features" version = "0.1.0" edition = "2021" publish = false [[bin]] # avoid pdb name collision with lib target name = "crate_with_features_bin" path = "src/main.rs" [dependencies] library_crate = {path = "../library_crate", optional = true} binary_and_cdylib_crate = {path = "../binary_and_cdylib_crate", optional = true} [features] default = ["library_crate"] library_crate = ["dep:library_crate"] binary_and_cdylib_crate = ["dep:binary_and_cdylib_crate"] 07070100000097000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005900000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/crate_with_features/src07070100000098000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000006000000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/crate_with_features/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 07070100000099000081A400000000000000000000000165D39F850000002D000000000000000000000000000000000000006100000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/crate_with_features/src/main.rsfn main() { println!("Hello, world!"); } 0707010000009A000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000004F00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/library_crate0707010000009B000081A400000000000000000000000165D39F85000000C6000000000000000000000000000000000000005A00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/library_crate/Cargo.toml[package] name = "library_crate" version = "0.1.0" edition = "2021" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] 0707010000009C000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000005300000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/library_crate/src0707010000009D000081A400000000000000000000000165D39F850000007C000000000000000000000000000000000000005A00000000cargo-auditable-0.6.2~0/cargo-auditable/tests/fixtures/workspace/library_crate/src/lib.rs#[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } } 0707010000009E000081A400000000000000000000000165D39F8500004051000000000000000000000000000000000000003400000000cargo-auditable-0.6.2~0/cargo-auditable/tests/it.rs//! Integration Tests for cargo auditable use std::{ collections::HashMap, ffi::OsStr, io::Write, path::PathBuf, process::{Command, Output, Stdio}, }; use auditable_serde::{DependencyKind, VersionInfo}; use cargo_metadata::{ camino::{Utf8Path, Utf8PathBuf}, Artifact, }; // Path to cargo-auditable binary under test const EXE: &str = env!("CARGO_BIN_EXE_cargo-auditable"); // Path to Cargo itself const CARGO: &str = env!("CARGO"); /// Run cargo auditable with --manifest-path <cargo_toml_path arg> and extra args, /// returning of map of workspace member names -> produced binaries (bin and cdylib) /// Reads the AUDITABLE_TEST_TARGET environment variable to determine the target to compile for fn run_cargo_auditable<P>( cargo_toml_path: P, args: &[&str], env: &[(&str, &OsStr)], ) -> HashMap<String, Vec<Utf8PathBuf>> where P: AsRef<OsStr>, { // run `cargo clean` before performing the build, // otherwise already built binaries will be used // and we won't actually test the *current* version of `cargo auditable` let status = Command::new(CARGO) .arg("clean") .arg("--manifest-path") .arg(&cargo_toml_path) .status() .unwrap(); assert!(status.success(), "Failed to invoke `cargo clean`!"); let mut command = Command::new(EXE); command .arg("auditable") .arg("build") .arg("--manifest-path") .arg(&cargo_toml_path) // We'll parse these to get binary paths .arg("--message-format=json") .args(args); if let Ok(target) = std::env::var("AUDITABLE_TEST_TARGET") { command.arg(format!("--target={target}")); } for (name, value) in env { command.env(name, value); } let output = command // We don't need to read stderr, so inherit for easier test debugging .stderr(Stdio::inherit()) .stdout(Stdio::piped()) .output() .unwrap(); ensure_build_succeeded(&output); let mut bins = HashMap::new(); std::str::from_utf8(&output.stdout) .unwrap() .lines() .flat_map(|line: &str| { let mut binaries = vec![]; if let Ok(artifact) = serde_json::from_str::<Artifact>(line) { // workspace member name is first word in package ID let member = artifact .package_id .to_string() .split(' ') .next() .unwrap() .to_string(); // bin targets are straightforward - use executable if let Some(executable) = artifact.executable { binaries.push((member, executable)); // cdylibs less so } else if artifact .target .kind .iter() .any(|kind| kind.as_str() == "cdylib") { // Detect files with .so (Linux), .dylib (Mac) and .dll (Windows) extensions artifact .filenames .into_iter() .filter(|f| { f.extension() == Some("dylib") || f.extension() == Some("so") || f.extension() == Some("dll") }) .for_each(|f| { binaries.push((member.clone(), f)); }); } } binaries }) .for_each(|(package, binary)| { bins.entry(package).or_insert(Vec::new()).push(binary); }); bins } fn ensure_build_succeeded(output: &Output) { if !output.status.success() { let stderr = std::io::stderr(); let mut handle = stderr.lock(); handle.write_all(&output.stdout).unwrap(); handle.write_all(&output.stderr).unwrap(); handle.flush().unwrap(); panic!("Build with `cargo auditable` failed"); } } fn get_dependency_info(binary: &Utf8Path) -> VersionInfo { auditable_info::audit_info_from_file(binary.as_std_path(), Default::default()).unwrap() } #[test] fn test_cargo_auditable_workspaces() { // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/workspace/Cargo.toml"); // Run in workspace root with default features let bins = run_cargo_auditable(&workspace_cargo_toml, &[], &[]); eprintln!("Test fixture binary map: {bins:?}"); // No binaries for library_crate assert!(bins.get("library_crate").is_none()); // binary_and_cdylib_crate let binary_and_cdylib_crate_bins = bins.get("binary_and_cdylib_crate").unwrap(); match std::env::var("AUDITABLE_TEST_TARGET") { // musl targets do not produce cdylibs by default: https://github.com/rust-lang/cargo/issues/8607 // So when targeting musl, we only check that the binary has been built, not the cdylib. Ok(target) if target.contains("musl") => assert!(!binary_and_cdylib_crate_bins.is_empty()), // everything else should build both the binary and cdylib _ => assert_eq!(binary_and_cdylib_crate_bins.len(), 2), } for binary in binary_and_cdylib_crate_bins { let dep_info = get_dependency_info(binary); eprintln!("{binary} dependency info: {dep_info:?}"); // binary_and_cdylib_crate should have two dependencies, library_crate and itself assert!(dep_info.packages.len() == 2); assert!(dep_info.packages.iter().any(|p| p.name == "library_crate")); assert!(dep_info .packages .iter() .any(|p| p.name == "binary_and_cdylib_crate")); } // crate_with_features should create a binary with two dependencies, library_crate and itself let crate_with_features_bin = &bins.get("crate_with_features").unwrap()[0]; let dep_info = get_dependency_info(crate_with_features_bin); eprintln!("{crate_with_features_bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 2); assert!(dep_info.packages.iter().any(|p| p.name == "library_crate")); assert!(dep_info .packages .iter() .any(|p| p.name == "crate_with_features")); // Run enabling binary_and_cdylib_crate feature let bins = run_cargo_auditable( &workspace_cargo_toml, &["--features", "binary_and_cdylib_crate"], &[], ); // crate_with_features should now have three dependencies, library_crate binary_and_cdylib_crate and crate_with_features, let crate_with_features_bin = &bins.get("crate_with_features").unwrap()[0]; let dep_info = get_dependency_info(crate_with_features_bin); eprintln!("{crate_with_features_bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 3); assert!(dep_info.packages.iter().any(|p| p.name == "library_crate")); assert!(dep_info .packages .iter() .any(|p| p.name == "crate_with_features")); assert!(dep_info .packages .iter() .any(|p| p.name == "binary_and_cdylib_crate")); // Run without default features let bins = run_cargo_auditable(&workspace_cargo_toml, &["--no-default-features"], &[]); // crate_with_features should now only depend on itself let crate_with_features_bin = &bins.get("crate_with_features").unwrap()[0]; let dep_info = get_dependency_info(crate_with_features_bin); eprintln!("{crate_with_features_bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 1); assert!(dep_info .packages .iter() .any(|p| p.name == "crate_with_features")); } /// This exercises a small real-world project #[test] fn test_self_hosting() { // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../rust-audit-info/Cargo.toml"); // Run in workspace root with default features let bins = run_cargo_auditable(workspace_cargo_toml, &[], &[]); eprintln!("Self-hosting binary map: {bins:?}"); // verify that the dependency info is present at all let bin = &bins.get("rust-audit-info").unwrap()[0]; let dep_info = get_dependency_info(bin); eprintln!("{bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() > 1); assert!(dep_info .packages .iter() .any(|p| p.name == "rust-audit-info")); } #[test] fn test_lto() { // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/lto_binary_crate/Cargo.toml"); // Run in workspace root with default features let bins = run_cargo_auditable(workspace_cargo_toml, &["--release"], &[]); eprintln!("LTO binary map: {bins:?}"); // lto_binary_crate should only depend on itself let lto_binary_crate_bin = &bins.get("lto_binary_crate").unwrap()[0]; let dep_info = get_dependency_info(lto_binary_crate_bin); eprintln!("{lto_binary_crate_bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 1); assert!(dep_info .packages .iter() .any(|p| p.name == "lto_binary_crate")); } #[test] fn test_bin_and_lib_in_one_crate() { // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/lib_and_bin_crate/Cargo.toml"); let bins = run_cargo_auditable(workspace_cargo_toml, &["--bin=some_binary"], &[]); eprintln!("Test fixture binary map: {bins:?}"); // lib_and_bin_crate should only depend on itself let lib_and_bin_crate_bin = &bins.get("lib_and_bin_crate").unwrap()[0]; let dep_info = get_dependency_info(lib_and_bin_crate_bin); eprintln!("{lib_and_bin_crate_bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 1); assert!(dep_info .packages .iter() .any(|p| p.name == "lib_and_bin_crate")); } /// A previous approach had trouble with build scripts and proc macros. /// Verify that those still work. #[test] fn test_build_script() { // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/crate_with_build_script/Cargo.toml"); let bins = run_cargo_auditable(workspace_cargo_toml, &[], &[]); eprintln!("Test fixture binary map: {bins:?}"); // crate_with_build_script should only depend on itself let crate_with_build_script_bin = &bins.get("crate_with_build_script").unwrap()[0]; let dep_info = get_dependency_info(crate_with_build_script_bin); eprintln!("{crate_with_build_script_bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 1); assert!(dep_info .packages .iter() .any(|p| p.name == "crate_with_build_script")); } #[test] fn test_platform_specific_deps() { // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/platform_specific_deps/Cargo.toml"); // Run in workspace root with default features let bins = run_cargo_auditable(workspace_cargo_toml, &[], &[]); eprintln!("Test fixture binary map: {bins:?}"); let test_target = std::env::var("AUDITABLE_TEST_TARGET"); if test_target.is_err() || !test_target.unwrap().starts_with("m68k") { // 'with_platform_dep' should only depend on 'should_not_be_included' on m68k processors // and we're not building for those, so it should be omitted let bin = &bins.get("with_platform_dep").unwrap()[0]; let dep_info = get_dependency_info(bin); eprintln!("{bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 1); assert!(!dep_info .packages .iter() .any(|p| p.name == "should_not_be_included")); } } #[test] fn test_build_then_runtime_dep() { // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/build_then_runtime_dep/Cargo.toml"); // Run in workspace root with default features let bins = run_cargo_auditable(workspace_cargo_toml, &[], &[]); eprintln!("Test fixture binary map: {bins:?}"); // check that the build types are propagated correctly let toplevel_crate_bin = &bins.get("top_level_crate").unwrap()[0]; let dep_info = get_dependency_info(toplevel_crate_bin); eprintln!("{toplevel_crate_bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 3); assert!(dep_info .packages .iter() .any(|p| p.name == "build_dep" && p.kind == DependencyKind::Build)); assert!(dep_info .packages .iter() .any(|p| p.name == "runtime_dep_of_build_dep" && p.kind == DependencyKind::Build)); } #[test] fn test_runtime_then_build_dep() { // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/runtime_then_build_dep/Cargo.toml"); // Run in workspace root with default features let bins = run_cargo_auditable(workspace_cargo_toml, &[], &[]); eprintln!("Test fixture binary map: {bins:?}"); // check that the build types are propagated correctly let toplevel_crate_bin = &bins.get("top_level_crate").unwrap()[0]; let dep_info = get_dependency_info(toplevel_crate_bin); eprintln!("{toplevel_crate_bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 3); assert!(dep_info .packages .iter() .any(|p| p.name == "runtime_dep" && p.kind == DependencyKind::Runtime)); assert!(dep_info .packages .iter() .any(|p| p.name == "build_dep_of_runtime_dep" && p.kind == DependencyKind::Build)); } #[test] fn test_custom_rustc_path() { // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/custom_rustc_path/Cargo.toml"); // locate rustc let rustc_path = which::which("rustc").unwrap(); // Run in workspace root with a custom path to rustc let bins = run_cargo_auditable(workspace_cargo_toml, &[], &[("RUSTC", rustc_path.as_ref())]); eprintln!("Test fixture binary map: {bins:?}"); // check that the build types are propagated correctly let toplevel_crate_bin = &bins.get("top_level_crate").unwrap()[0]; let dep_info = get_dependency_info(toplevel_crate_bin); eprintln!("{toplevel_crate_bin} dependency info: {dep_info:?}"); assert!(dep_info.packages.len() == 3); assert!(dep_info .packages .iter() .any(|p| p.name == "runtime_dep" && p.kind == DependencyKind::Runtime)); assert!(dep_info .packages .iter() .any(|p| p.name == "build_dep_of_runtime_dep" && p.kind == DependencyKind::Build)); } #[test] fn test_workspace_member_version_info() { // Test that `/path/to/cargo-auditable rustc -vV works when compiling a workspace member // // Never happens with Cargo - it does call `rustc -vV`, // but either bypasses the wrapper or doesn't set CARGO_PRIMARY_PACKAGE=true. // However it does happen with `sccache`: // https://github.com/rust-secure-code/cargo-auditable/issues/87 let mut command = Command::new(EXE); command.env("CARGO_PRIMARY_PACKAGE", "true"); command.args(["rustc", "-vV"]); let status = command.status().unwrap(); assert!(status.success()); } 0707010000009F000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002800000000cargo-auditable-0.6.2~0/rust-audit-info070701000000A0000081A400000000000000000000000165D39F8500000008000000000000000000000000000000000000003300000000cargo-auditable-0.6.2~0/rust-audit-info/.gitignore/target 070701000000A1000081A400000000000000000000000165D39F85000003EC000000000000000000000000000000000000003300000000cargo-auditable-0.6.2~0/rust-audit-info/Cargo.lock# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "auditable-extract" version = "0.3.2" dependencies = [ "binfarce", ] [[package]] name = "auditable-info" version = "0.7.0" dependencies = [ "auditable-extract", "miniz_oxide", ] [[package]] name = "binfarce" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18464ccbb85e5dede30d70cc7676dc9950a0fb7dbf595a43d765be9123c616a2" [[package]] name = "miniz_oxide" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "rust-audit-info" version = "0.5.2" dependencies = [ "auditable-info", ] 070701000000A2000081A400000000000000000000000165D39F8500000262000000000000000000000000000000000000003300000000cargo-auditable-0.6.2~0/rust-audit-info/Cargo.toml[package] name = "rust-audit-info" version = "0.5.2" authors = ["Sergey \"Shnatsel\" Davidoff <shnatsel@gmail.com>"] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-secure-code/cargo-auditable" description = "Command-line tool to extract the dependency trees embedded in binaries by `cargo auditable`." categories = ["command-line-utilities", "encoding"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] auditable-info = {version = "0.7.0", default-features = false, path = "../auditable-info"} [workspace] 070701000000A3000081A400000000000000000000000165D39F8500000AE9000000000000000000000000000000000000003200000000cargo-auditable-0.6.2~0/rust-audit-info/README.md## rust-audit-info Command-line tool to extract the dependency trees embedded in binaries by [`cargo auditable`](https://crates.io/crates/cargo-auditable). It takes care of parsing the platform-specific formats ([ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format), [PE](https://en.wikipedia.org/wiki/Portable_Executable), [Mach-O](https://en.wikipedia.org/wiki/Mach-O)) and outputs the decompressed JSON. This tool is intentionally minimal and does not implement vulnerability scanning on its own. However, it is useful for building your own vulnerability scanner. If you're looking for a Rust library instead of a command-line tool, see [`auditable-info`](https://docs.rs/auditable-info/). ### Features - Parses binaries from any supported platform, not just the platform it's running on. - Compiles down to a ~400Kb self-contained executable with no external dependencies. - Binary parsing designed from the ground up for resilience to malicious inputs. - 100% memory-safe Rust, including all dependencies. No memory-unsafe code anywhere in the dependency tree. - Cross-platform, portable, easy to cross-compile. Runs on [any Rust target with `std`](https://doc.rust-lang.org/stable/rustc/platform-support.html). - Supports setting size limits for both input and output, to protect against [OOMs](https://en.wikipedia.org/wiki/Out_of_memory) and [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). ### Usage ```bash Usage: rust-audit-info FILE [INPUT_SIZE_LIMIT] [OUTPUT_SIZE_LIMIT] The limits are specified in bytes. The default values are: INPUT_SIZE_LIMIT: 1073741824 (1 GiB) OUTPUT_SIZE_LIMIT: 67108864 (64 MiB) ``` The highest possible RAM usage is `INPUT_SIZE_LIMIT + OUTPUT_SIZE_LIMIT`, plus up to 1MB of overhead. If you need to read from the standard input, pass `/dev/stdin` as the `FILE`. ### Dependencies ``` $ cargo geiger Metric output format: x/y x = unsafe code used by the build y = total unsafe code found in the crate Symbols: 🔒 = No `unsafe` usage found, declares #![forbid(unsafe_code)] ❓ = No `unsafe` usage found, missing #![forbid(unsafe_code)] ☢️ = `unsafe` usage found Functions Expressions Impls Traits Methods Dependency 0/0 0/0 0/0 0/0 0/0 🔒 rust-audit-info 0.5.2 0/0 0/0 0/0 0/0 0/0 🔒 └── auditable-info 0.6.2 0/0 0/0 0/0 0/0 0/0 🔒 ├── auditable-extract 0.3.2 0/0 0/0 0/0 0/0 0/0 🔒 │ └── binfarce 0.2.1 0/0 0/0 0/0 0/0 0/0 🔒 └── miniz_oxide 0.6.2 0/0 0/0 0/0 0/0 0/0 🔒 └── adler 1.0.2 0/0 0/0 0/0 0/0 0/0 ``` 070701000000A4000041ED00000000000000000000000265D39F8500000000000000000000000000000000000000000000002C00000000cargo-auditable-0.6.2~0/rust-audit-info/src070701000000A5000081A400000000000000000000000165D39F85000005AF000000000000000000000000000000000000003400000000cargo-auditable-0.6.2~0/rust-audit-info/src/main.rs#![forbid(unsafe_code)] use auditable_info::{json_from_file, Limits}; use std::env::args_os; use std::error::Error; use std::io::Write; use std::path::PathBuf; const USAGE: &'static str = "\ Usage: rust-audit-info FILE [INPUT_SIZE_LIMIT] [OUTPUT_SIZE_LIMIT] The limits are specified in bytes. The default values are: INPUT_SIZE_LIMIT: 1073741824 (1 GiB) OUTPUT_SIZE_LIMIT: 8388608 (8 MiB) "; fn main() { if let Err(e) = actual_main() { eprintln!("{}", e); std::process::exit(1); } } fn actual_main() -> Result<(), Box<dyn Error>> { let (input, limits) = parse_args()?; let decompressed_data: String = json_from_file(&input, limits)?; let stdout = std::io::stdout(); let mut stdout = stdout.lock(); stdout.write_all(&decompressed_data.as_bytes())?; Ok(()) } fn parse_args() -> Result<(PathBuf, Limits), Box<dyn Error>> { let input = args_os().nth(1).ok_or(USAGE)?; let mut limits: Limits = Default::default(); if let Some(s) = args_os().nth(2) { let utf8_s = s .to_str() .ok_or("Invalid UTF-8 in input size limit argument")?; limits.input_file_size = utf8_s.parse::<usize>()? } if let Some(s) = args_os().nth(3) { let utf8_s = s .to_str() .ok_or("Invalid UTF-8 in output size limit argument")?; limits.decompressed_json_size = utf8_s.parse::<usize>()? } Ok((input.into(), limits)) } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!414 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor