Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Security Assurance Case

This document provides a structured argument that libmagic-rs meets its security requirements. It follows the assurance case model described in NIST IR 7608.

1. Security Requirements

libmagic-rs is a file type detection library and CLI tool. Its security requirements are:

  1. SR-1: Must not crash, panic, or exhibit undefined behavior when processing any input file
  2. SR-2: Must not crash, panic, or exhibit undefined behavior when parsing any magic file
  3. SR-3: Must not read beyond allocated buffer boundaries
  4. SR-4: Must not allow path traversal via CLI arguments
  5. SR-5: Must not execute arbitrary code based on file contents or magic rule definitions
  6. SR-6: Must not consume unbounded resources (memory, CPU) during evaluation
  7. SR-7: Must not leak sensitive information from one file evaluation to another

2. Threat Model

2.1 Assets

  • Host system: The machine running libmagic-rs
  • File contents: Data being inspected (may be sensitive)
  • Magic rules: Definitions that drive file type detection

2.2 Threat Actors

ActorMotivationCapability
Malicious file authorExploit the detection tool to gain code execution or cause DoSCan craft arbitrary file contents
Malicious magic file authorInject rules that cause crashes, resource exhaustion, or incorrect resultsCan craft arbitrary magic rule syntax
Supply chain attackerCompromise a dependency to inject malicious codeCan publish malicious crate versions

2.3 Attack Vectors

IDVectorTarget SR
AV-1Crafted file triggers buffer over-readSR-1, SR-3
AV-2Crafted file triggers integer overflow in offset calculationSR-1, SR-3
AV-3Deeply nested magic rules cause stack overflowSR-1, SR-6
AV-4Extremely large file causes memory exhaustionSR-6
AV-5Malformed magic file causes parser crashSR-2
AV-6CLI argument with path traversal reads unintended filesSR-4
AV-7Compromised dependency introduces unsafe codeSR-5

3. Trust Boundaries

flowchart TD
    subgraph Untrusted["Untrusted Zone"]
        direction LR
        IF["Input Files<br/>(any content)"]
        MF["Magic Files<br/>(user or system)"]
        CA["CLI Arguments<br/>(user paths)"]
    end

    subgraph libmagic-rs["libmagic-rs (Trusted Zone)"]
        IO["I/O Layer<br/>mmap files, size limits"]
        CLI["CLI<br/>clap args, validates paths"]
        P["Parser<br/>validates magic syntax"]
        E["Evaluator<br/>bounds-checks all access"]
        O["Output<br/>formats results"]
    end

    IF -- "file bytes" --> IO
    MF -- "magic syntax" --> P
    CA -- "user paths" --> CLI
    IO -- "mapped buffer" --> E
    CLI -- "validated paths" --> IO
    P -- "validated AST" --> E
    E -- "match results" --> O

    style Untrusted fill:#4a1a1a,stroke:#ef5350,color:#e0e0e0,stroke-width:2px
    style libmagic-rs fill:#1b3d1b,stroke:#66bb6a,color:#e0e0e0,stroke-width:2px

All data crossing the trust boundary (file contents, magic file syntax, CLI arguments) is treated as untrusted and validated before use.

4. Secure Design Principles (Saltzer and Schroeder)

PrincipleHow Applied
Economy of mechanismPure Rust with minimal dependencies. Simple parser-evaluator pipeline. No plugin system, no scripting, no network I/O.
Fail-safe defaultsWorkspace lint unsafe_code = "forbid" enforced project-wide via Cargo.toml. Buffer access defaults to bounds-checked .get() returning None rather than panicking. Invalid magic rules are skipped, not executed.
Complete mediationEvery buffer access is bounds-checked. Every magic file is validated during parsing. Every CLI argument is validated by clap.
Open designFully open source (Apache-2.0). Security does not depend on obscurity. All security mechanisms are publicly documented.
Separation of privilegeParser and evaluator are separate modules with distinct responsibilities. Parse errors cannot bypass evaluation safety checks.
Least privilegeThe tool only reads files; it never writes, executes, or modifies them. No network access. No elevated permissions required.
Least common mechanismNo shared mutable state between file evaluations. Each evaluation operates on its own data. No global caches that could leak information.
Psychological acceptabilityCLI follows GNU file conventions. Error messages are descriptive and actionable. Default behavior is safe (built-in rules, no network).

5. Common Weakness Countermeasures

5.1 CWE/SANS Top 25

CWEWeaknessCountermeasureStatus
CWE-787Out-of-bounds writeRust ownership prevents writes to unowned memory. Workspace-level lints in Cargo.toml forbid unsafe code and eliminate raw pointer writes.Mitigated
CWE-79XSSNot applicable (no web output).N/A
CWE-89SQL injectionNot applicable (no database).N/A
CWE-416Use after freeRust ownership/borrowing system prevents use-after-free at compile time.Mitigated
CWE-78OS command injectionNo shell invocation or command execution. CLI arguments parsed by clap, not passed to shell.Mitigated
CWE-20Improper input validationAll inputs validated: magic syntax validated by parser, file buffers bounds-checked, CLI args validated by clap.Mitigated
CWE-125Out-of-bounds readAll buffer access uses .get() with bounds checking. Memory-mapped files have known size limits.Mitigated
CWE-22Path traversalCLI accepts file paths as arguments but only performs read-only access. No path construction from file contents.Mitigated
CWE-352CSRFNot applicable (no web interface).N/A
CWE-434Unrestricted uploadNot applicable (no file upload).N/A
CWE-476NULL pointer dereferenceRust’s Option type eliminates null pointer dereferences at compile time.Mitigated
CWE-190Integer overflowRust panics on integer overflow in debug builds. Offset calculations use checked arithmetic.Mitigated
CWE-502Deserialization of untrusted dataMagic files are parsed with a strict grammar, not deserialized from arbitrary formats.Mitigated
CWE-400Resource exhaustionEvaluation timeouts prevent unbounded CPU use. Memory-mapped I/O avoids loading entire files into memory.Mitigated

5.2 OWASP Top 10 (where applicable)

Most OWASP Top 10 categories target web applications and are not applicable to a file detection library. The applicable items are:

CategoryApplicabilityCountermeasure
A03: InjectionPartial – magic file parsingStrict grammar-based parser rejects invalid syntax
A04: Insecure DesignApplicableSecure design principles applied throughout (see Section 4)
A06: Vulnerable ComponentsApplicablecargo audit daily, cargo deny, Dependabot, cargo-auditable
A09: Security LoggingPartialEvaluation errors logged; security events reported via GitHub Advisories

6. Supply Chain Security

MeasureImplementation
Dependency auditingcargo audit and cargo deny run daily in CI
Dependency updatesDependabot configured for automated PRs
Pinned toolchainRust stable via rust-toolchain.toml
Reproducible buildsCargo.lock and mise.lock committed
Build provenanceSigstore attestations via actions/attest-build-provenance (wrapper around actions/attest)
SBOM generationcargo-cyclonedx produces CycloneDX SBOM per release
Binary auditingcargo-auditable embeds dependency metadata in binaries
CI integrityAll GitHub Actions pinned to SHA hashes
Code reviewRequired on all PRs; automated by CodeRabbit with security-focused checks

7. Known Limitations and Residual Risk

7.1 Default Configuration Has No Timeout

EvaluationConfig::default() (and EvaluationConfig::new()) sets timeout_ms: None, meaning evaluation runs without a wall-clock limit. The other validated bounds (recursion depth, string length, resource combination) prevent stack overflow and unbounded memory growth, but they do not bound total CPU time. A maliciously crafted file or magic rule that stays within those bounds could still drive evaluation into a long-running state, resulting in a denial-of-service condition for callers that process untrusted input with the default configuration.

Mitigation for callers: When processing untrusted input, use EvaluationConfig::performance() (which sets a 1-second timeout) or set timeout_ms explicitly. The CLI exposes this as --timeout-ms. See Configuration: Security Considerations for details.

This behavior is documented in the development gotchas (GOTCHAS.md section 13.1, “EvaluationConfig::default() Has No Timeout”) and is intentional: changing the default would silently break callers that legitimately need long-running evaluation on trusted input.

7.2 TOCTOU Window in evaluate_file

MagicDatabase::evaluate_file has a time-of-check/time-of-use (TOCTOU) window between path validation and memory mapping (CWE-367). The method calls std::fs::metadata(path) to handle the empty-file case, then opens and memory-maps the file via the I/O layer, which itself re-validates file metadata (regular file, size bounds) before calling the underlying mmap. Between these validation steps and the final mapping, the path may be swapped – for example via a symlink replacement or rename – by another process. The bytes that ultimately get mapped may therefore belong to a different file than the one that passed validation.

The I/O layer mitigates the common shapes of this attack by canonicalizing the path and rejecting non-regular file types (directories, FIFOs, sockets, block/character devices). The mapping is always read-only, so a successful race cannot corrupt the target file or the caller’s process state. The residual risk is incorrect classification: evaluate_file may return a file-type description for a file other than the one the caller named.

Mitigation for callers: When processing untrusted paths in an adversarial environment, do not use evaluate_file. Instead:

  1. Open the file yourself using a TOCTOU-aware I/O strategy appropriate to your platform – e.g., openat with O_NOFOLLOW, or holding a single open file descriptor across validation and read.
  2. Read the bytes into memory (bounded by your own size limit).
  3. Pass the resulting &[u8] to MagicDatabase::evaluate_buffer, which has no filesystem interaction and therefore no TOCTOU window.

The evaluate_file rustdoc (# Security section) cross-references this subsection.

8. Ongoing Assurance

This assurance case is maintained as a living document. It is updated when:

  • New features introduce new attack surfaces
  • New threat vectors are identified
  • Dependencies change significantly
  • Security incidents occur

The project maintains continuous assurance through automated CI checks (clippy, CodeQL, cargo audit, cargo deny) that run on every commit.