Skip to content

Commit 1d638b6

Browse files
authored
Merge pull request #52 from auths-dev/dev-mirDepthCheck
feat: add --deep MIR-based analysis via capsec-driver
2 parents 5f0bddd + 4f8c56d commit 1d638b6

16 files changed

Lines changed: 920 additions & 1 deletion

File tree

.capsec.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@ crate = "capsec-std"
66

77
[[allow]]
88
crate = "capsec-tokio"
9+
10+
# cargo-capsec is the CLI tool — it necessarily performs I/O to scan,
11+
# cache, and invoke the deep analysis driver.
12+
[[allow]]
13+
crate = "cargo-capsec"

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ members = [
1212
]
1313
exclude = [
1414
"crates/capsec-example-db",
15+
"crates/capsec-deep",
1516
]
1617

1718
[workspace.package]

crates/capsec-deep/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/target
2+
*.o
3+
*.d
4+
# Binaries produced when testing the driver on fixture files
5+
/clean
6+
/simple_fs
7+
/macro_ffi
8+
/capsec_test

crates/capsec-deep/Cargo.lock

Lines changed: 107 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/capsec-deep/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "capsec-deep"
3+
version = "0.1.0"
4+
edition = "2024"
5+
license = "Apache-2.0"
6+
description = "MIR-based deep analysis driver for capsec — requires nightly"
7+
publish = false
8+
9+
[[bin]]
10+
name = "capsec-driver"
11+
path = "src/main.rs"
12+
13+
[dependencies]
14+
serde = { version = "1", features = ["derive"] }
15+
serde_json = "1"
16+
17+
[package.metadata.rust-analyzer]
18+
rustc_private = true

crates/capsec-deep/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# capsec-deep
2+
3+
MIR-based deep analysis driver for capsec. Uses `rustc`'s Mid-level IR to detect ambient authority usage that syntactic analysis misses — macro-expanded FFI calls, trait dispatch, and generic instantiation.
4+
5+
## Requirements
6+
7+
- Nightly Rust toolchain (pinned in `rust-toolchain.toml`)
8+
- `rustc-dev` and `llvm-tools` components
9+
10+
## Install
11+
12+
```bash
13+
cd crates/capsec-deep
14+
cargo install --path .
15+
```
16+
17+
This installs the `capsec-driver` binary, which `cargo capsec audit --deep` invokes automatically.
18+
19+
## How it works
20+
21+
`capsec-driver` is a custom Rust compiler driver. When invoked via `RUSTC_WRAPPER`, it intercepts every crate compilation, runs the normal compiler pipeline through type checking, then walks the MIR of every function looking for:
22+
23+
- **Authority calls**`std::fs::*`, `std::net::*`, `std::env::*`, `std::process::*` resolved through the full type system (including macro expansion)
24+
- **FFI calls** — any call to a `DefKind::ForeignFn` item (catches `-sys` crate wrappers like `libgit2-sys`, `sqlite3-sys`)
25+
26+
Findings are written as JSONL to a temp file, which the main `cargo-capsec` CLI reads, merges with syntactic findings, and feeds into the cross-crate export map system for transitive propagation.
27+
28+
## Architecture
29+
30+
```mermaid
31+
flowchart TD
32+
A["cargo capsec audit --deep"] --> B["cargo check\n(RUSTC_WRAPPER=capsec-driver)"]
33+
B --> C["capsec-driver replaces rustc\nfor each crate"]
34+
C --> D["after_analysis callback"]
35+
D --> E["Walk MIR BasicBlocks\nTerminatorKind::Call"]
36+
E --> F["Extract callee DefId\ntcx.def_path_str()"]
37+
F --> G{Classify call}
38+
G -->|"std::fs, std::net,\nstd::env, std::process"| H["Authority finding\n(FS/NET/ENV/PROC)"]
39+
G -->|"tcx.is_foreign_item()"| I["FFI finding"]
40+
G -->|"No match"| J["Skip"]
41+
H --> K["Write JSONL to\n$CAPSEC_DEEP_OUTPUT"]
42+
I --> K
43+
K --> L["cargo-capsec reads JSONL\nbuilds export maps"]
44+
L --> M["Phase 2: workspace scan\nwith MIR export maps injected"]
45+
M --> N["Unified cross-crate\ntransitive findings"]
46+
```
47+
48+
## Standalone testing
49+
50+
```bash
51+
# Test on a single file
52+
CAPSEC_DEEP_DEBUG=1 cargo run -- --edition 2024 tests/fixtures/simple_fs.rs
53+
54+
# Test FFI detection through macros
55+
CAPSEC_DEEP_DEBUG=1 cargo run -- --edition 2024 tests/fixtures/macro_ffi.rs
56+
```
57+
58+
## Excluded from workspace
59+
60+
This crate requires nightly and is listed in the workspace `exclude` list. It builds independently and does not affect `cargo test --workspace` or `cargo check --workspace` on stable.

crates/capsec-deep/build.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// Embeds the sysroot library path so the binary can find librustc_driver at runtime.
2+
fn main() {
3+
let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
4+
let output = std::process::Command::new(&rustc)
5+
.arg("--print=sysroot")
6+
.output()
7+
.expect("Failed to run rustc --print=sysroot");
8+
let sysroot = String::from_utf8(output.stdout)
9+
.expect("Invalid UTF-8 from rustc --print=sysroot");
10+
let sysroot = sysroot.trim();
11+
12+
// Link against the sysroot lib directory so librustc_driver.dylib/.so is found
13+
println!("cargo:rustc-link-arg=-Wl,-rpath,{sysroot}/lib");
14+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[toolchain]
2+
channel = "nightly-2026-02-17"
3+
components = ["rustc-dev", "llvm-tools", "rust-src"]
4+
profile = "minimal"

0 commit comments

Comments
 (0)