Why I built this
I've worked in embedded development and in security tooling. There was a gap where those two worlds met.
Binary hardening checks — RELRO, PIE, stack protection — are things embedded developers naturally think about. But there's no tool to automate them in CI (at least none I could find). SCA tools check your source code and licenses, but they don't look at the hardening state of the binary you actually ship.
I thought there was demand for something that filled that gap, so I built it.
What are hardening flags?
Hardening flags are security features applied by the compiler or linker at build time. The five main ones for ELF binaries:
- RELRO (Relocation Read-Only): Makes the GOT read-only. Full RELRO is ideal.
- Stack Canary: A canary value placed on the stack to detect buffer overflows. If it's been tampered with before the function returns, the process terminates.
- NX (No eXecute): Marks the stack and heap as non-executable. Basic defense against shellcode injection.
- PIE (Position Independent Executable): Required for ASLR (address space layout randomization) to work.
-
Fortify Source: When built with
_FORTIFY_SOURCE, dangerous functions likestrcpyare replaced with safer versions that detect buffer overflows at runtime. bincheck detects this by inspecting fortified symbols in the binary.
For PE binaries (Windows EXE/DLL), the equivalent concepts are DEP, CFG, SafeSEH, and ASLR.
Missing any of these doesn't automatically mean a vulnerability, but it lowers the bar for attackers. IoT devices and firmware tend to have long lifespans with infrequent updates — the cost of shipping without hardening is higher than in typical software.
What bincheck does
bincheck is a CLI tool that checks hardening flags in ELF, PE, and Mach-O binaries. Written in Rust, using the goblin crate for binary parsing.
1. SARIF output for GitHub Code Scanning
bincheck firmware.elf --format sarif > results.sarif
Upload the SARIF output to GitHub Code Scanning and you get hardening results in the Security tab. You can track changes per PR.
2. --strict as a CI gate
bincheck firmware.elf --strict
echo $? # returns 1 if any hardening flag is missing
With --strict, bincheck exits with code 1 if any flag is absent. Drop it into your CI pipeline as a gate.
Demo
# Install
cargo install bincheck
# Check an ELF binary (table output)
$ bincheck ./firmware.elf
Binary: ./firmware.elf (ELF, x86_64)
┌─────────────────┬──────────┬───────────────────────────────────────┐
│ Check │ Status │ Detail │
├─────────────────┼──────────┼───────────────────────────────────────┤
│ RELRO │ FULL │ │
│ Stack Canary │ ENABLED │ found __stack_chk_fail in .symtab │
│ NX │ ENABLED │ │
│ PIE │ ENABLED │ │
│ Fortify Source │ DISABLED │ no fortified symbols found │
│ RPATH │ NONE │ │
│ RUNPATH │ NONE │ │
└─────────────────┴──────────┴───────────────────────────────────────┘
# JSON output
$ bincheck ./firmware.elf --format json
# SARIF output (for GitHub Code Scanning)
$ bincheck ./firmware.elf --format sarif > results.sarif
# CI gate (exit 1 on missing hardening)
$ bincheck ./firmware.elf --strict && echo "OK" || echo "FAILED"
GitHub Actions
- name: Check binary hardening
uses: kazu11max17/bincheck@v0.2.0
with:
files: ./build/firmware.elf
strict: true
format: sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: bincheck-results.sarif
Note: the upload-sarif step requires permissions: security-events: write in your workflow.
Closing
bincheck is available here:
- crates.io: https://crates.io/crates/bincheck
- GitHub: https://github.com/kazu11max17/bincheck
If you're using it on embedded Linux, firmware, or anything cross-compiled — feedback and PRs are welcome.
Top comments (0)