DEV Community

Rich
Rich

Posted on

firmware-auditor: Zero-Emulation Static Security Analysis for Firmware Rootfs in CI/CD

Security assessments for IoT devices and embedded Linux systems have long relied on two main approaches: manual static inspection or full dynamic emulation (QEMU, Unicorn, Firmadyne, FirmAE).

Dynamic analysis is invaluable for verifying vulnerabilities, but plugging emulated environments into automated CI/CD pipelines often leads to:

  1. High execution overhead and slow feedback loops.
  2. Architecture incompatibilities: custom NVRAM, vendor peripherals and proprietary SoC hardware are exactly where generic emulators tend to break.
  3. Flaky automation that stalls pull requests.

To bridge this gap I built firmware-auditor: a fast, zero-emulation static analysis framework for unpacked root filesystem images.


The Core Idea: Reproducible Static Posture

When you have an unpacked SquashFS, ext4 or UBIFS rootfs, many security posture indicators can be checked entirely offline, without executing a single target binary:

  • ELF binary hardening: are binaries built with modern mitigations, or exposed to classic exploitation techniques?
  • Filesystem hygiene and secret exposure: did the build bake private SSH keys, a readable /etc/shadow or world-writable scripts into production?
  • Actionable DevSecOps gates: can the pipeline fail (exit 1) on critical issues and emit standardized, machine-readable results?

Here is what a run looks like. This is a test rootfs with intentionally planted flaws, not a real firmware image:

rootfs     : test_rootfs
duration   : 0.007s (workers=1)
severity   : >= info
analyzers  : binary_hardening 1.0.0 ok (0 findings, 0.004s)
             filesystem_hygiene 1.0.0 ok (5 findings, 0.002s)

SUMMARY BY SEVERITY
CRITICAL   5 ####################
HIGH       0
MEDIUM     0
LOW        0
TOTAL      5

FINDINGS BY RULE (4 rule(s), 5 finding(s))
SEVERITY RULE         COUNT LOCATION SAMPLE     TITLE
CRITICAL FWK-PERM-001     1 etc                 World-writable directory
CRITICAL FWK-KEY-001      1 etc/id_rsa          Private key material in the firmware image
CRITICAL FWK-PERM-002     2 etc/id_rsa; shadow  World-writable file in a sensitive tree
CRITICAL FWK-PERM-004     1 etc/shadow          Password hash database readable by any user
Enter fullscreen mode Exit fullscreen mode

Architecture

The framework is written in Python 3.10+ with a modular design built around isolation and reproducibility:

firmware_auditor/
├── analyzers/
│   ├── base.py                 # Base analyzer & Finding contract
│   ├── binary_hardening.py     # ELF checks (NX, PIE, Canary, RELRO)
│   └── filesystem_hygiene.py   # Secrets, permissions, credentials
├── core/
│   ├── pipeline.py             # Concurrent runner, fault isolation
│   └── reporter.py             # Console, JSON and SARIF v2.1.0
└── tests/                      # pytest suite (Ubuntu & Windows)
Enter fullscreen mode Exit fullscreen mode

Each analyzer runs behind a fault-isolation boundary with a timeout guard, so one broken analyzer or a malformed binary doesn't take down the whole scan.


1. ELF Hardening Inspections

Using pyelftools, the engine parses ELF headers directly:

  • NX / DEP: checks the PT_GNU_STACK flags for an executable stack. A missing PT_GNU_STACK header is also treated as an executable stack, since that is the default on several architectures common in firmware (ARM, MIPS).
  • PIE / ASLR: ET_DYN alone is not enough, because shared libraries are ET_DYN too. The engine separates PIE executables from .so files by checking for PT_INTERP and the DF_1_PIE flag in DT_FLAGS_1. ET_EXEC means a fixed load address.
  • Stack canaries: looks for __stack_chk_fail in .dynsym and .symtab.
  • RELRO: checks PT_GNU_RELRO for Partial RELRO, and for Full RELRO also requires immediate binding (DT_BIND_NOW, DF_BIND_NOW in DT_FLAGS, or DF_1_NOW in DT_FLAGS_1).

2. Filesystem Hygiene

Instead of running arbitrary regex over the entire filesystem, the module targets specific security attributes:

  • Checks permission masks (st_mode) on sensitive Unix paths (/etc, /bin, /sbin).
  • Detects unshadowed or empty password hashes in authentication files.
  • Identifies raw private key material without leaking the secret into CI logs or reports.

Note: permission checks need an extraction that preserves Unix modes (unsquashfs, tar, WSL or Linux). A rootfs copied onto NTFS loses this information, and permission findings will not be reliable.

3. Native SARIF Support for GitHub Code Scanning

firmware-auditor outputs SARIF v2.1.0 instead of a proprietary report format, so findings can show up directly under Security > Code scanning on GitHub:

firmware-auditor /path/to/extracted/rootfs \
  --format sarif \
  --output report.sarif \
  --fail-on high
Enter fullscreen mode Exit fullscreen mode

GitHub Actions example

name: firmware-audit
on: [pull_request]

permissions:
  contents: read
  security-events: write

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install firmware-auditor
        run: |
          git clone https://github.com/RichJJ98/firmware-auditor
          pip install ./firmware-auditor

      - name: Extract rootfs (preserving permissions)
        run: |
          sudo apt-get update && sudo apt-get install -y squashfs-tools
          sudo unsquashfs -f -d rootfs firmware.squashfs

      - name: Run audit
        run: firmware-auditor rootfs --format sarif --output report.sarif --fail-on high

      - name: Upload SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: report.sarif
Enter fullscreen mode Exit fullscreen mode

The if: always() on the upload step matters: with --fail-on high the audit step exits non-zero, and without it the findings would never reach the Code scanning tab.


Limitations (What It Does Not Cover)

Static analysis has blind spots, and it is better to say so upfront:

  • Not a replacement for dynamic analysis. It won't confirm exploitability or find runtime and logic vulnerabilities.
  • No CVE matching of bundled library versions (yet).
  • Possible false negatives on canaries: stripped or statically linked binaries may not expose __stack_chk_fail.
  • Needs an already unpacked rootfs. Extraction (binwalk, unsquashfs, etc.) is a separate step, and encrypted or proprietary containers are out of scope.
  • Permission checks depend on extraction fidelity (see the note above).

Open Source & Next Steps

firmware-auditor is released under the MIT License, and the test suite runs in CI on both Linux and Windows.

If you work with embedded devices, IoT security or DevSecOps pipelines, clone it, try it on your own firmware images and contribute new rule modules. Feedback, issues and pull requests are very welcome.

Top comments (0)