13 KiB
Binary Diff Attestation
Overview
Binary Diff Attestation enables verification of binary-level changes between container images, producing cryptographically signed evidence of what changed at the ELF/PE section level. This capability is essential for:
- Vendor backport detection: Identify when a vendor has patched a binary without changing version numbers
- Supply chain verification: Prove that expected changes (and no unexpected changes) occurred between releases
- VEX evidence generation: Provide concrete evidence for "not_affected" or "fixed" vulnerability status claims
- Audit trail: Maintain verifiable records of binary modifications across deployments
Relationship to SBOM and VEX
Binary diff attestations complement SBOM and VEX documents:
| Artifact | Purpose | Granularity |
|---|---|---|
| SBOM | Inventory of components | Package/library level |
| VEX | Exploitability status | Vulnerability level |
| Binary Diff Attestation | Change evidence | Section/function level |
The attestation provides the evidence that supports VEX claims. For example, a VEX statement claiming a CVE is "fixed" due to a vendor backport can reference the binary diff attestation showing the .text section hash changed.
Architecture
Component Diagram
┌──────────────────────────────────────────────────────────────────────────────┐
│ Binary Diff Attestation Flow │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ OCI │ │ Layer │ │ Binary │ │ Section │ │
│ │ Registry │───▶│ Extraction │───▶│ Detection │───▶│ Hash │ │
│ │ Client │ │ │ │ │ │ Extractor │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ Base Image ─────────────────────────────────────┐ │ │
│ Target Image ───────────────────────────────────┤ ▼ │
│ │ ┌─────────────┐ │
│ └─▶│ Diff │ │
│ │ Computation │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ DSSE │◀───│ Predicate │◀───│ Finding │◀───│ Verdict │ │
│ │ Signer │ │ Builder │ │ Aggregation │ │ Classifier │ │
│ └──────┬──────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Rekor │ │ File │ │
│ │ Submission │ │ Output │ │
│ └─────────────┘ └─────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
Key Components
| Component | Location | Responsibility |
|---|---|---|
ElfSectionHashExtractor |
Scanner.Analyzers.Native |
Extract per-section SHA-256 hashes from ELF binaries |
BinaryDiffService |
Cli.Services |
Orchestrate diff computation between two images |
BinaryDiffPredicateBuilder |
Attestor.StandardPredicates |
Construct BinaryDiffV1 in-toto predicates |
BinaryDiffDsseSigner |
Attestor.StandardPredicates |
Sign predicates with DSSE envelopes |
Data Flow
- Image Resolution: Resolve base and target image references to manifest digests
- Layer Extraction: Download and extract layers from both images
- Binary Identification: Identify ELF binaries in both filesystems
- Section Hash Computation: Compute SHA-256 for each target section in each binary
- Diff Computation: Compare section hashes between base and target
- Verdict Classification: Classify changes as patched/vanilla/unknown
- Predicate Construction: Build BinaryDiffV1 predicate with findings
- DSSE Signing: Sign predicate and optionally submit to Rekor
ELF Section Hashing
Target Sections
The following ELF sections are analyzed for hash computation:
| Section | Purpose | Backport Relevance |
|---|---|---|
.text |
Executable code | High - Patched functions modify this section |
.rodata |
Read-only data (strings, constants) | Medium - String constants may change with patches |
.data |
Initialized global/static variables | Low - Rarely changes for security patches |
.symtab |
Symbol table (function names, addresses) | High - Function signature changes |
.dynsym |
Dynamic symbols (exports) | High - Exported API changes |
Hash Algorithm
Primary: SHA-256
- Industry standard, widely supported
- Collision-resistant for security applications
Optional: BLAKE3-256
- Faster computation for large binaries
- Enabled via configuration
Hash Computation
For each ELF binary:
1. Parse ELF header
2. Locate section headers
3. For each target section:
a. Read section contents
b. Compute SHA-256(contents)
c. Store: {name, offset, size, sha256}
4. Sort sections by name (lexicographic)
5. Return ElfSectionHashSet
Determinism Guarantees
All operations produce deterministic output:
| Aspect | Guarantee |
|---|---|
| Section ordering | Sorted lexicographically by name |
| Hash format | Lowercase hexadecimal, no prefix |
| Timestamps | From injected TimeProvider |
| JSON serialization | RFC 8785 canonical JSON |
BinaryDiffV1 Predicate
Schema Overview
The BinaryDiffV1 predicate follows in-toto attestation format:
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "docker://repo/app@sha256:target...",
"digest": { "sha256": "target..." }
}
],
"predicateType": "stellaops.binarydiff.v1",
"predicate": {
"inputs": {
"base": { "digest": "sha256:base..." },
"target": { "digest": "sha256:target..." }
},
"findings": [...],
"metadata": {...}
}
}
Predicate Fields
| Field | Type | Description |
|---|---|---|
subjects |
array | Target image references with digests |
inputs.base |
object | Base image reference |
inputs.target |
object | Target image reference |
findings |
array | Per-binary diff findings |
metadata |
object | Tool version, timestamp, config |
Finding Structure
Each finding represents a binary comparison:
{
"path": "/usr/lib/libssl.so.3",
"changeType": "modified",
"binaryFormat": "elf",
"sectionDeltas": [
{ "section": ".text", "status": "modified" },
{ "section": ".rodata", "status": "identical" }
],
"confidence": 0.95,
"verdict": "patched"
}
Verdicts
| Verdict | Meaning | Confidence Threshold |
|---|---|---|
patched |
Binary shows evidence of security patch | >= 0.90 |
vanilla |
Binary matches upstream/unmodified | >= 0.95 |
unknown |
Cannot determine patch status | < 0.90 |
incompatible |
Cannot compare (different architecture, etc.) | N/A |
DSSE Attestation
Envelope Structure
{
"payloadType": "stellaops.binarydiff.v1",
"payload": "<base64-encoded predicate>",
"signatures": [
{
"keyid": "...",
"sig": "<base64-encoded signature>"
}
]
}
Signature Algorithm
- Default: Ed25519
- Alternative: ECDSA P-256, RSA-PSS (via
ICryptoProviderRegistry) - Keyless: Sigstore Fulcio certificate chain
Rekor Submission
When Rekor is enabled:
- DSSE envelope is submitted to Rekor transparency log
- Inclusion proof is retrieved
- Rekor metadata is stored in result
{
"rekorLogIndex": 12345678,
"rekorEntryId": "abc123...",
"integratedTime": "2026-01-13T12:00:00Z"
}
Verification
Binary diff attestations can be verified with:
# Using cosign
cosign verify-attestation \
--type stellaops.binarydiff.v1 \
--certificate-identity-regexp '.*' \
--certificate-oidc-issuer-regexp '.*' \
docker://repo/app:1.0.1
# Using stella CLI
stella verify attestation ./binarydiff.dsse.json \
--type stellaops.binarydiff.v1
Integration Points
VEX Mapping
Binary diff evidence can support VEX claims:
{
"vulnerability": "CVE-2024-1234",
"status": "fixed",
"justification": "vulnerable_code_not_present",
"detail": "Vendor backport applied; evidence in binary diff attestation",
"evidence": {
"attestationRef": "sha256:dsse-envelope-hash...",
"finding": {
"path": "/usr/lib/libssl.so.3",
"verdict": "patched",
"confidence": 0.95
}
}
}
Policy Engine
Policy rules can reference binary diff evidence:
# Accept high-confidence patch verdicts as mitigation
allow contains decision if {
input.binaryDiff.findings[_].verdict == "patched"
input.binaryDiff.findings[_].confidence >= 0.90
decision := {
"action": "accept",
"reason": "Binary diff shows patched code",
"evidence": input.binaryDiff.attestationRef
}
}
SBOM Properties
Section hashes appear in SBOM component properties:
{
"type": "library",
"name": "libssl.so.3",
"properties": [
{"name": "evidence:section:.text:sha256", "value": "abc123..."},
{"name": "evidence:section:.rodata:sha256", "value": "def456..."},
{"name": "evidence:extractor-version", "value": "1.0.0"}
]
}
Configuration
Scanner Options
scanner:
native:
sectionHashes:
enabled: true
algorithms:
- sha256
- blake3 # optional
sections:
- .text
- .rodata
- .data
- .symtab
- .dynsym
maxSectionSize: 104857600 # 100MB limit
CLI Options
See CLI Reference for full option documentation.
Limitations and Future Work
Current Limitations
- ELF only: PE and Mach-O support planned for M2
- Single platform: Multi-platform diff requires multiple invocations
- No function-level analysis: Section-level granularity only
- Confidence scoring: Based on section changes, not semantic analysis
Roadmap
| Milestone | Capability |
|---|---|
| M2 | PE section analysis for Windows containers |
| M2 | Mach-O section analysis for macOS binaries |
| M3 | Vendor backport corpus with curated test fixtures |
| M3 | Function-level diff using DWARF debug info |
| M4 | ML-based verdict classification |