Skip to content

feat: New crate dokono-cve: determine whether a vulnerable function in a dependency is reachable from a bin #18

Description

@enomoto11

Overview

Add a new sibling crate dokono-cve. While dokono-rs solves "changed lines → affected bins", dokono-cve solves "vulnerable function in a dependency crate → bins that reach it". It reuses dokono-core's upward BFS as-is.

In one sentence

When a vulnerability is found in one of your repository's dependencies, determine whether your bins actually have a call path that can reach the vulnerable function, and if so, display the call chain from main down to that function.

Triggers (when to use)

Manual invocation by an auditor:

  • cargo audit reported a CVE in a dependency crate
  • A GitHub Security Advisory notified you that "a crate you use has a vulnerability"
  • Someone internally said "isn't this function dangerous?" (a 0-day not yet in any DB)

→ When you want to decide: "OK, does our code actually hit it or not?"

Inputs

Three patterns:

  1. Output of cargo audit --json (evaluate all advisories at once)
  2. A single advisory ID (e.g. RUSTSEC-2023-XXXX)
  3. A vulnerable symbol given directly as a string (e.g. openssl::ssl::SslContext::new, for 0-days)

Plus, always required: the workspace path.

Output sketch

```
CVE-2023-XXXX openssl::ssl::SslContext::new

✗ services/src/bin/api.rs [REACHABLE]
main
└─ build_router services/src/http/mod.rs:45
└─ TlsConfig::load services/src/tls/mod.rs:18
└─ SslContext::new (vulnerable)

✓ services/src/bin/migrate.rs [NOT REACHABLE]
```

Differentiation from existing tools

What cargo audit answers is only "does the dependency tree contain a vulnerable crate?" (crate granularity). In reality:

  • You might not be calling the vulnerable function at all
  • Even if you are, the migrate bin might not reach it
  • It might only be called from under cfg(test) and never ship to production

cargo audit cannot distinguish any of these. dokono-cve looks at real reachability at symbol × bin granularity, enabling differentiated decisions like "patch api.rs immediately, leave the others on watch".

Out of scope (boundaries)

  • Applying patches (cargo update is a separate tool)
  • Computing CVSS scores (we just display what's in the advisory)
  • Calls through an intermediate crate (A → third-party crate → vulnerable crate) are not traced (known limitation)
  • Dynamic behavior (reflection etc.) is not handled (static analysis only)

Design direction (conclusions from up-front investigation)

1. How to find the first call site on the workspace side

Adopted: synthesized use-site + goto-def → run textDocument/references at the resulting dep-side location.

  • rust-analyzer's Definition::search_scope walks reverse_dependencies for pub items, so invoking references at the declaration site in the dependency crate returns hits on the workspace side (crates/ide-db/src/search.rs)
  • However, there is no LSP-level way to go from the string openssl::ssl::SslContext::new to the declaration site in the dep crate (workspace/symbol is fuzzy and the ranking is unstable rust-analyzer#16491)
  • → Write use openssl::ssl::SslContext; into a synthesized file and goto-def from there. This lets rust-analyzer's name resolution handle re-exports / aliases for us
  • Resolving manually via AST matching would mean re-implementing use as and type-alias chains, so we don't go that route

2. Extracting vulnerable symbols from cargo audit JSON / advisories

Adopted: depend on the rustsec crate and read affected.functions. MVP limited to Tier A.

  • cargo audit --json is Serialize-wired straight from rustsec::Report. vulnerabilities.list[].advisory.affected.functions is included as-is (rustsec/cargo-audit#41)
  • Format: BTreeMap<FunctionPath, Vec<VersionReq>>, where FunctionPath is a normalized Rust path
  • Coverage is realistically a minority of advisories (openssl is rich, tokio/hyper are nearly empty)
  • Tier A (affected.functions present): convert directly into seeds
  • Tier B (absent): return reachable: unknown (no affected.functions in DB) and stop. We explicitly degrade to the same granularity as cargo audit
  • Extending Tier B to "seed every usage site of the crate in question" is not in the first version (it would kill the value prop)

3. Handling of #[cfg(test)]

Adopted: during BFS, inspect each reference's enclosing function and its parent module chain for #[cfg(test)], surface test paths under a separate label, and exclude them from the headline result.

  • rust-analyzer defaults to cargo.allTargets=true and fully indexes #[cfg(test)] mod tests
  • textDocument/references responses do not distinguish prod vs test
  • The references.excludeTests setting only targets #[test] function bodies; helpers under #[cfg(test)] mod tests slip through (rust-analyzer#18573)
  • → We have to classify on the client side. Output is one of three labels: [REACHABLE] / [REACHABLE FROM TESTS ONLY] / [NOT REACHABLE]

4. Exit codes for CI

  • 0: analysis succeeded, zero reachable vulnerabilities
  • 1: analysis succeeded, vulnerabilities reachable from a production path exist
  • 2: tool error
  • Test-only reachability is exit 0 by default; --strict promotes it to 1

5. Crate name

dokono-cve (to keep parity with dokono-rs / dokono-core).

Impact on dokono-core

No changes are required to dokono-core as it stands today. dokono-cve only needs to implement the bfs::LspBackend trait to reuse everything:

  • Seed construction is fully decoupled from symbols::pick_at_lines
  • bfs::run is backend-agnostic
  • entrypoints::load_bin_entrypoints is reused
  • lsp::client::Client / lsp::backend::Backend are reused

The #[cfg(test)] detection logic is implemented locally inside dokono-cve (so the behavior of dokono-rs proper is unchanged).

Known limitations (inherited from dokono-rs)

  • Missed references in spots where macro expansion is incomplete
  • Missed references in code that depends on generics / type inference
  • We do not expand downward through trait impls
  • Vulnerable calls through an intermediate crate are not traced (specific to dokono-cve)

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions