File cargo-auditable-0.6.0~0.obscpio of Package cargo-auditable
07070100000000000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002000000000cargo-auditable-0.6.0~0/.github07070100000001000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002A00000000cargo-auditable-0.6.0~0/.github/workflows07070100000002000081A400000000000000000000000163910A4300000165000000000000000000000000000000000000003400000000cargo-auditable-0.6.0~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
- uses: actions-rs/cargo@v1
with:
command: check07070100000003000081A400000000000000000000000163910A43000006F4000000000000000000000000000000000000003400000000cargo-auditable-0.6.0~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
- name: "Test cross-compiling to x86_64-unknown-linux-musl"
env:
AUDITABLE_TEST_TARGET: "x86_64-unknown-linux-musl"
run: cargo test --all-features
- 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
07070100000004000081A400000000000000000000000163910A43000002FA000000000000000000000000000000000000003200000000cargo-auditable-0.6.0~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
07070100000005000081A400000000000000000000000163910A4300000414000000000000000000000000000000000000003600000000cargo-auditable-0.6.0~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
- 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
07070100000006000081A400000000000000000000000163910A4300000007000000000000000000000000000000000000002300000000cargo-auditable-0.6.0~0/.gitignoretarget
07070100000007000081A400000000000000000000000163910A4300001572000000000000000000000000000000000000002B00000000cargo-auditable-0.6.0~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
07070100000008000081A400000000000000000000000163910A43000003E5000000000000000000000000000000000000002800000000cargo-auditable-0.6.0~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.
07070100000009000081A400000000000000000000000163910A4300002D72000000000000000000000000000000000000002300000000cargo-auditable-0.6.0~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.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "auditable-extract"
version = "0.3.2"
dependencies = [
"binfarce",
]
[[package]]
name = "auditable-info"
version = "0.6.2"
dependencies = [
"auditable-extract",
"auditable-serde",
"miniz_oxide 0.6.2",
"serde_json",
]
[[package]]
name = "auditable-serde"
version = "0.5.2"
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.0"
dependencies = [
"auditable-info",
"auditable-serde",
"cargo_metadata",
"miniz_oxide 0.5.4",
"object",
"pico-args",
"serde",
"serde_json",
"which",
]
[[package]]
name = "cargo-lock"
version = "8.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c4c54d47a4532db3494ef7332c257ab57b02750daae3250d49e01ee55201ce8"
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 = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[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.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
dependencies = [
"adler",
]
[[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.28.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
dependencies = [
"crc32fast",
"hashbrown 0.11.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 = "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.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[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 = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "which"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
dependencies = [
"either",
"libc",
"once_cell",
]
0707010000000A000081A400000000000000000000000163910A4300000078000000000000000000000000000000000000002300000000cargo-auditable-0.6.0~0/Cargo.toml[workspace]
members = [
"auditable-info",
"auditable-extract",
"auditable-serde",
"cargo-auditable",
]
0707010000000B000081A400000000000000000000000163910A4300002A5F000000000000000000000000000000000000002700000000cargo-auditable-0.6.0~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.
0707010000000C000081A400000000000000000000000163910A430000042E000000000000000000000000000000000000002400000000cargo-auditable-0.6.0~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.
0707010000000D000081A400000000000000000000000163910A4300000EE8000000000000000000000000000000000000002300000000cargo-auditable-0.6.0~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".0707010000000E000081A400000000000000000000000163910A43000015E3000000000000000000000000000000000000002200000000cargo-auditable-0.6.0~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.
### 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).
0707010000000F000081A400000000000000000000000163910A4300000570000000000000000000000000000000000000002B00000000cargo-auditable-0.6.0~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 "$@"
```07070100000010000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002A00000000cargo-auditable-0.6.0~0/auditable-extract07070100000011000081A400000000000000000000000163910A4300000008000000000000000000000000000000000000003500000000cargo-auditable-0.6.0~0/auditable-extract/.gitignore/target
07070100000012000081A400000000000000000000000163910A43000001DD000000000000000000000000000000000000003500000000cargo-auditable-0.6.0~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"
07070100000013000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002E00000000cargo-auditable-0.6.0~0/auditable-extract/src07070100000014000081A400000000000000000000000163910A430000146C000000000000000000000000000000000000003500000000cargo-auditable-0.6.0~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,
}
}
}
07070100000015000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002700000000cargo-auditable-0.6.0~0/auditable-info07070100000016000081A400000000000000000000000163910A4300000330000000000000000000000000000000000000003200000000cargo-auditable-0.6.0~0/auditable-info/Cargo.toml[package]
name = "auditable-info"
version = "0.6.2"
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.5.0", path = "../auditable-serde", optional = true}
serde_json = { version = "1.0.57", optional = true }
[features]
serde = ["serde_json", "auditable-serde"]
default = ["serde"]
07070100000017000081A400000000000000000000000163910A430000060F000000000000000000000000000000000000003100000000cargo-auditable-0.6.0~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, ignore
// Uses the default limits: 1GiB input file size, 8MiB audit data size
let info = audit_info_from_file("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.07070100000018000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002B00000000cargo-auditable-0.6.0~0/auditable-info/src07070100000019000081A400000000000000000000000163910A4300000B00000000000000000000000000000000000000003400000000cargo-auditable-0.6.0~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)
}
}
0707010000001A000081A400000000000000000000000163910A430000205B000000000000000000000000000000000000003200000000cargo-auditable-0.6.0~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("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;
#[cfg(feature = "serde")]
use serde_json;
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("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"));
}
}
0707010000001B000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002800000000cargo-auditable-0.6.0~0/auditable-serde0707010000001C000081A400000000000000000000000163910A4300000339000000000000000000000000000000000000003500000000cargo-auditable-0.6.0~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).
## [UNRELEASED]
### Changed
- 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 release0707010000001D000081A400000000000000000000000163910A4300000383000000000000000000000000000000000000003300000000cargo-auditable-0.6.0~0/auditable-serde/Cargo.toml[package]
name = "auditable-serde"
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 = "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 = "8.0.2", 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"]
0707010000001E000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000003100000000cargo-auditable-0.6.0~0/auditable-serde/examples0707010000001F000081A400000000000000000000000163910A43000003BC000000000000000000000000000000000000004200000000cargo-auditable-0.6.0~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);
}
}
07070100000020000081A400000000000000000000000163910A4300000257000000000000000000000000000000000000004100000000cargo-auditable-0.6.0~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();
}
07070100000021000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002C00000000cargo-auditable-0.6.0~0/auditable-serde/src07070100000022000081A400000000000000000000000163910A4300005945000000000000000000000000000000000000003300000000cargo-auditable-0.6.0~0/auditable-serde/src/lib.rs#![forbid(unsafe_code)]
//! 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)]
#[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,
#[serde(rename = "runtime")]
Runtime,
}
impl Default for DependencyKind {
fn default() -> Self {
DependencyKind::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 == 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, |s| Source::from(s)),
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.dependencies.iter() {
// omit package if it is a development-only dependency
let dep_id = dep.repr.as_str();
if id_to_dep_kind[dep_id] != 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::PathBuf};
#[cfg(feature = "toml")]
#[cfg(feature = "from_metadata")]
fn load_own_metadata() -> cargo_metadata::Metadata {
let mut cmd = cargo_metadata::MetadataCommand::new();
let cargo_toml_path =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("Cargo.toml");
cmd.manifest_path(cargo_toml_path);
cmd.exec().unwrap()
}
#[test]
#[cfg(feature = "toml")]
#[cfg(feature = "from_metadata")]
fn to_toml() {
let metadata = load_own_metadata();
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);
}
}
07070100000023000081A400000000000000000000000163910A4300000D3D000000000000000000000000000000000000003A00000000cargo-auditable-0.6.0~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());
}
}
07070100000024000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002800000000cargo-auditable-0.6.0~0/cargo-auditable07070100000025000081A400000000000000000000000163910A4300000EC2000000000000000000000000000000000000003400000000cargo-auditable-0.6.0~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
}
]
}
}
}
07070100000026000081A400000000000000000000000163910A430000060B000000000000000000000000000000000000003500000000cargo-auditable-0.6.0~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.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
07070100000027000081A400000000000000000000000163910A4300000386000000000000000000000000000000000000003300000000cargo-auditable-0.6.0~0/cargo-auditable/Cargo.toml[package]
name = "cargo-auditable"
version = "0.6.0"
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"]
readme = "../README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
object = {version = "0.28.1", default-features = false, features = ["write"]}
auditable-serde = {version = "0.5.0", path = "../auditable-serde", features = ["from_metadata"]}
miniz_oxide = {version = "0.5.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.6.0", path = "../auditable-info"}
which = "4.3.0"07070100000028000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002C00000000cargo-auditable-0.6.0~0/cargo-auditable/src07070100000029000081A400000000000000000000000163910A4300000E0F000000000000000000000000000000000000003F00000000cargo-auditable-0.6.0~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(|s| OsString::from(s)).collect();
let args = CargoArgs::from_args_vec(raw_args);
assert_eq!(args.locked, true);
assert_eq!(args.offline, true);
assert_eq!(args.frozen, false);
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(|s| OsString::from(s)).collect();
let args = CargoArgs::from_args_vec(raw_args);
assert_eq!(args.locked, true);
assert_eq!(args.offline, true);
assert_eq!(args.frozen, false);
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(|s| OsString::from(s)).collect();
let args = CargoArgs::from_args_vec(raw_args);
assert_eq!(args.offline, false);
assert_eq!(args.config, vec!["net.git-fetch-with-cli=true"]);
}
}
0707010000002A000081A400000000000000000000000163910A4300000ACD000000000000000000000000000000000000003F00000000cargo-auditable-0.6.0~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());
}
0707010000002B000081A400000000000000000000000163910A430000106D000000000000000000000000000000000000004200000000cargo-auditable-0.6.0~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")
}
0707010000002C000081A400000000000000000000000163910A4300000606000000000000000000000000000000000000003400000000cargo-auditable-0.6.0~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);
}
0707010000002D000081A400000000000000000000000163910A43000024BE000000000000000000000000000000000000003B00000000cargo-auditable-0.6.0~0/cargo-auditable/src/object_file.rs//! Shamelessly copied from rustc codebase:
//! https://github.com/rust-lang/rust/blob/3b186511f62b0ce20e72ede0e8e13f8787155f02/compiler/rustc_codegen_ssa/src/back/metadata.rs#L260-L298
//! 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,
);
match file.format() {
BinaryFormat::Elf => {
// 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" => 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);
match architecture {
Architecture::Mips => {
// copied from `mipsel-linux-gnu-gcc foo.c -c` and
// inspecting the resulting `e_flags` field.
let e_flags = elf::EF_MIPS_CPIC
| elf::EF_MIPS_PIC
| if target_triple.contains("r6") {
elf::EF_MIPS_ARCH_32R6 | elf::EF_MIPS_NAN2008
} else {
elf::EF_MIPS_ARCH_32R2
};
file.flags = FileFlags::Elf { e_flags };
}
Architecture::Mips64 => {
// copied from `mips64el-linux-gnuabi64-gcc foo.c -c`
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
};
file.flags = FileFlags::Elf { e_flags };
}
Architecture::Riscv64 if has_riscv_double_precision_float_abi(target_triple) => {
// copied from `riscv64-linux-gnu-gcc foo.c -c`, note though
// that the `+d` target feature represents whether the double
// float abi is enabled.
let e_flags = elf::EF_RISCV_RVC | elf::EF_RISCV_FLOAT_ABI_DOUBLE;
file.flags = FileFlags::Elf { e_flags };
}
_ => {}
};
Some(file)
}
// This function was not present in the original rustc code, which simply used
// `sess.target.options.features.contains("+d")`
// We do not have access to compiler internals, so we have to reimplement the check
// for double-precision floating-point ABI.
fn has_riscv_double_precision_float_abi(target_triple: &str) -> bool {
let arch = target_triple.split('-').next().unwrap();
assert_eq!(&arch[..5], "riscv");
let extensions = &arch[7..];
extensions.contains('g') || extensions.contains('d')
}
#[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
assert!(has_riscv_double_precision_float_abi(
"riscv64gc-unknown-linux-gnu"
));
// real-world target without double floats
assert!(!has_riscv_double_precision_float_abi(
"riscv32imac-unknown-none-elf"
));
// made-up target with double floats but without atomics
assert!(has_riscv_double_precision_float_abi(
"riscv64imd-unknown-none-elf"
));
}
#[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);
}
}
0707010000002E000081A400000000000000000000000163910A430000077E000000000000000000000000000000000000003F00000000cargo-auditable-0.6.0~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>,
}
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")?,
})
}
0707010000002F000081A400000000000000000000000163910A43000014EB000000000000000000000000000000000000003D00000000cargo-auditable-0.6.0~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'
if 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()
.expect("Failed to invoke rustc! Make sure it's in your $PATH");
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!")
}
07070100000030000081A400000000000000000000000163910A430000094F000000000000000000000000000000000000003B00000000cargo-auditable-0.6.0~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 '{}' is a valid target triple", 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");
}
}
07070100000031000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002E00000000cargo-auditable-0.6.0~0/cargo-auditable/tests07070100000032000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000003700000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures07070100000033000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004E00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/build_then_runtime_dep07070100000034000081A400000000000000000000000163910A4300000063000000000000000000000000000000000000005900000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/Cargo.toml[workspace]
members = [
"top_level_crate",
"build_dep",
"runtime_dep_of_build_dep",
]
07070100000035000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005800000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/build_dep07070100000036000081A400000000000000000000000163910A43000000F4000000000000000000000000000000000000006300000000cargo-auditable-0.6.0~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"}
07070100000037000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005C00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/build_dep/src07070100000038000081A400000000000000000000000163910A430000007C000000000000000000000000000000000000006300000000cargo-auditable-0.6.0~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);
}
}
07070100000039000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006700000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep0707010000003A000081A400000000000000000000000163910A43000000C1000000000000000000000000000000000000007200000000cargo-auditable-0.6.0~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]
0707010000003B000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006B00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/src0707010000003C000081A400000000000000000000000163910A430000007C000000000000000000000000000000000000007200000000cargo-auditable-0.6.0~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);
}
}
0707010000003D000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005E00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/top_level_crate0707010000003E000081A400000000000000000000000163910A43000000E2000000000000000000000000000000000000006900000000cargo-auditable-0.6.0~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"}
0707010000003F000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006200000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/top_level_crate/src07070100000040000081A400000000000000000000000163910A430000002D000000000000000000000000000000000000006A00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/build_then_runtime_dep/top_level_crate/src/main.rsfn main() {
println!("Hello, world!");
}
07070100000041000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004F00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/crate_with_build_script07070100000042000081A400000000000000000000000163910A43000000A7000000000000000000000000000000000000005A00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/crate_with_build_script/Cargo.lock# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "crate_with_build_script"
version = "0.1.0"
07070100000043000081A400000000000000000000000163910A43000000CD000000000000000000000000000000000000005A00000000cargo-auditable-0.6.0~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]
07070100000044000081A400000000000000000000000163910A430000002B000000000000000000000000000000000000005800000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/crate_with_build_script/build.rsfn main() {
println!("Hello there!");
}07070100000045000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005300000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/crate_with_build_script/src07070100000046000081A400000000000000000000000163910A430000002D000000000000000000000000000000000000005B00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/crate_with_build_script/src/main.rsfn main() {
println!("Hello, world!");
}
07070100000047000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004900000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/lib_and_bin_crate07070100000048000081A400000000000000000000000163910A43000000C7000000000000000000000000000000000000005400000000cargo-auditable-0.6.0~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]
07070100000049000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004D00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/lib_and_bin_crate/src0707010000004A000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005100000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/lib_and_bin_crate/src/bin0707010000004B000081A400000000000000000000000163910A430000005A000000000000000000000000000000000000006000000000cargo-auditable-0.6.0~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();
}
0707010000004C000081A400000000000000000000000163910A43000000B7000000000000000000000000000000000000005400000000cargo-auditable-0.6.0~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);
}
}
0707010000004D000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004800000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/lto_binary_crate0707010000004E000081A400000000000000000000000163910A43000000A0000000000000000000000000000000000000005300000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/lto_binary_crate/Cargo.lock# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "lto_binary_crate"
version = "0.1.0"
0707010000004F000081A400000000000000000000000163910A43000000E2000000000000000000000000000000000000005300000000cargo-auditable-0.6.0~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
07070100000050000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004C00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/lto_binary_crate/src07070100000051000081A400000000000000000000000163910A430000002D000000000000000000000000000000000000005400000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/lto_binary_crate/src/main.rsfn main() {
println!("Hello, world!");
}
07070100000052000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004E00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/platform_specific_deps07070100000053000081A400000000000000000000000163910A4300000052000000000000000000000000000000000000005900000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/platform_specific_deps/Cargo.toml[workspace]
members = [
"with_platform_dep",
"should_not_be_included",
]
07070100000054000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006500000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/platform_specific_deps/should_not_be_included07070100000055000081A400000000000000000000000163910A43000000BF000000000000000000000000000000000000007000000000cargo-auditable-0.6.0~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]
07070100000056000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006900000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/platform_specific_deps/should_not_be_included/src07070100000057000081A400000000000000000000000163910A430000007C000000000000000000000000000000000000007000000000cargo-auditable-0.6.0~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);
}
}
07070100000058000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006000000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/platform_specific_deps/with_platform_dep07070100000059000081A400000000000000000000000163910A430000012B000000000000000000000000000000000000006B00000000cargo-auditable-0.6.0~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"}
0707010000005A000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006400000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/platform_specific_deps/with_platform_dep/src0707010000005B000081A400000000000000000000000163910A430000002D000000000000000000000000000000000000006C00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/platform_specific_deps/with_platform_dep/src/main.rsfn main() {
println!("Hello, world!");
}
0707010000005C000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004E00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/runtime_then_build_dep0707010000005D000081A400000000000000000000000163910A4300000065000000000000000000000000000000000000005900000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/Cargo.toml[workspace]
members = [
"top_level_crate",
"runtime_dep",
"build_dep_of_runtime_dep",
]
0707010000005E000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006700000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/build_dep_of_runtime_dep0707010000005F000081A400000000000000000000000163910A43000000C1000000000000000000000000000000000000007200000000cargo-auditable-0.6.0~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]
07070100000060000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006B00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/build_dep_of_runtime_dep/src07070100000061000081A400000000000000000000000163910A430000007C000000000000000000000000000000000000007200000000cargo-auditable-0.6.0~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);
}
}
07070100000062000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005A00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/runtime_dep07070100000063000081A400000000000000000000000163910A43000000FC000000000000000000000000000000000000006500000000cargo-auditable-0.6.0~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"}
07070100000064000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005E00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/runtime_dep/src07070100000065000081A400000000000000000000000163910A430000007C000000000000000000000000000000000000006500000000cargo-auditable-0.6.0~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);
}
}
07070100000066000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005E00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/top_level_crate07070100000067000081A400000000000000000000000163910A43000000E0000000000000000000000000000000000000006900000000cargo-auditable-0.6.0~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"}
07070100000068000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000006200000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/top_level_crate/src07070100000069000081A400000000000000000000000163910A430000002D000000000000000000000000000000000000006A00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/runtime_then_build_dep/top_level_crate/src/main.rsfn main() {
println!("Hello, world!");
}
0707010000006A000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004100000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace0707010000006B000081A400000000000000000000000163910A430000017F000000000000000000000000000000000000004C00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace/Cargo.lock# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "binary_and_cdylib_crate"
version = "0.1.0"
dependencies = [
"library_crate",
]
[[package]]
name = "crate_with_features"
version = "0.1.0"
dependencies = [
"binary_and_cdylib_crate",
"library_crate",
]
[[package]]
name = "library_crate"
version = "0.1.0"
0707010000006C000081A400000000000000000000000163910A4300000147000000000000000000000000000000000000004C00000000cargo-auditable-0.6.0~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",
]
0707010000006D000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005900000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace/binary_and_cdylib_crate0707010000006E000081A400000000000000000000000163910A4300000122000000000000000000000000000000000000006400000000cargo-auditable-0.6.0~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"}
0707010000006F000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005D00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace/binary_and_cdylib_crate/src07070100000070000081A400000000000000000000000163910A430000007C000000000000000000000000000000000000006400000000cargo-auditable-0.6.0~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);
}
}
07070100000071000081A400000000000000000000000163910A430000002D000000000000000000000000000000000000006500000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace/binary_and_cdylib_crate/src/main.rsfn main() {
println!("Hello, world!");
}
07070100000072000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005500000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace/crate_with_features07070100000073000081A400000000000000000000000163910A43000001EB000000000000000000000000000000000000006000000000cargo-auditable-0.6.0~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"]
07070100000074000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005900000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace/crate_with_features/src07070100000075000081A400000000000000000000000163910A430000007C000000000000000000000000000000000000006000000000cargo-auditable-0.6.0~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);
}
}
07070100000076000081A400000000000000000000000163910A430000002D000000000000000000000000000000000000006100000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace/crate_with_features/src/main.rsfn main() {
println!("Hello, world!");
}
07070100000077000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000004F00000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace/library_crate07070100000078000081A400000000000000000000000163910A43000000C6000000000000000000000000000000000000005A00000000cargo-auditable-0.6.0~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]
07070100000079000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000005300000000cargo-auditable-0.6.0~0/cargo-auditable/tests/fixtures/workspace/library_crate/src0707010000007A000081A400000000000000000000000163910A430000007C000000000000000000000000000000000000005A00000000cargo-auditable-0.6.0~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);
}
}
0707010000007B000081A400000000000000000000000163910A4300003F48000000000000000000000000000000000000003400000000cargo-auditable-0.6.0~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");
/// 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>,
{
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.len() >= 1),
// 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!("{} dependency info: {:?}", binary, 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!(
"{} dependency info: {:?}",
crate_with_features_bin, 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!(
"{} dependency info: {:?}",
crate_with_features_bin, 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!(
"{} dependency info: {:?}",
crate_with_features_bin, 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!("{} dependency info: {:?}", bin, 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!("{} dependency info: {:?}", lto_binary_crate_bin, 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!("{} dependency info: {:?}", lib_and_bin_crate_bin, 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!(
"{} dependency info: {:?}",
crate_with_build_script_bin, 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!("{} dependency info: {:?}", bin, 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!("{} dependency info: {:?}", toplevel_crate_bin, 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!("{} dependency info: {:?}", toplevel_crate_bin, 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/runtime_then_build_dep/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".into(), 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!("{} dependency info: {:?}", toplevel_crate_bin, 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());
}
0707010000007C000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002800000000cargo-auditable-0.6.0~0/rust-audit-info0707010000007D000081A400000000000000000000000163910A4300000008000000000000000000000000000000000000003300000000cargo-auditable-0.6.0~0/rust-audit-info/.gitignore/target
0707010000007E000081A400000000000000000000000163910A43000003EC000000000000000000000000000000000000003300000000cargo-auditable-0.6.0~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.6.2"
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",
]
0707010000007F000081A400000000000000000000000163910A4300000262000000000000000000000000000000000000003300000000cargo-auditable-0.6.0~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.6.0", default-features = false, path = "../auditable-info"}
[workspace]
07070100000080000081A400000000000000000000000163910A4300000AE9000000000000000000000000000000000000003200000000cargo-auditable-0.6.0~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
```
07070100000081000041ED00000000000000000000000163910A4300000000000000000000000000000000000000000000002C00000000cargo-auditable-0.6.0~0/rust-audit-info/src07070100000082000081A400000000000000000000000163910A43000005AF000000000000000000000000000000000000003400000000cargo-auditable-0.6.0~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!!!360 blocks