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:

Relationship to SBOM and VEX

Binary diff attestations complement SBOM and VEX documents:

ArtifactPurposeGranularity
SBOMInventory of componentsPackage/library level
VEXExploitability statusVulnerability level
Binary Diff AttestationChange evidenceSection/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

+-------------------+   +--------------------+   +--------------------+   +----------------------+
| OCI Registry      |-->| Layer Extraction   |-->| ELF Detection       |-->| Section Hash Extract |
+-------------------+   +--------------------+   +--------------------+   +----------------------+
        | base + target images
        v
+-------------------+   +--------------------+   +------------------+   +------------------+
| Diff Computation  |-->| Predicate Builder  |-->| DSSE Signer       |-->| Output Files     |
+-------------------+   +--------------------+   +------------------+   +------------------+

Key Components

ComponentLocationResponsibility
ElfSectionHashExtractorScanner.Analyzers.NativeExtract per-section SHA-256 hashes from ELF binaries
BinaryDiffServiceCli.Commands.ScanOrchestrate diff computation between two images
BinaryDiffPredicateBuilderAttestor.StandardPredicatesConstruct BinaryDiffV1 predicate payloads
BinaryDiffDsseSignerAttestor.StandardPredicatesSign predicates with DSSE envelopes

AddBinaryDiffPredicates() registers the serializer, signer, and verifier as stateless singletons. The fluent IBinaryDiffPredicateBuilder is transient because it owns mutable subjects, findings, inputs, and metadata; sharing one instance would contaminate concurrent or sequential predicate constructions. The current CLI BinaryDiffService constructs a builder with its own options and TimeProvider, while scan diff --emit-dsse resolves the registered signer.

Data Flow

  1. Image Resolution: Resolve base and target image references to manifest digests
  2. Layer Extraction: Download and extract layers from both images
  3. Binary Identification: Identify ELF binaries in both filesystems
  4. Section Hash Computation: Compute SHA-256 for each target section in each binary
  5. Diff Computation: Compare section hashes between base and target
  6. Verdict Classification: Basic classification of unchanged vs modified binaries
  7. Predicate Construction: Build BinaryDiffV1 predicate with findings
  8. DSSE Signing: Sign predicate; optional transparency log submission is handled by attestor tooling

ELF Section Hashing

Target Sections

The following ELF sections are analyzed for hash computation:

SectionPurposeBackport Relevance
.textExecutable codeHigh - Patched functions modify this section
.rodataRead-only data (strings, constants)Medium - String constants may change with patches
.dataInitialized global/static variablesLow - Rarely changes for security patches
.symtabSymbol table (function names, addresses)High - Function signature changes
.dynsymDynamic symbols (exports)High - Exported API changes

Hash Algorithm

Primary: SHA-256

Optional: BLAKE3-256

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:

AspectGuarantee
Section orderingSorted lexicographically by name
Hash formatLowercase hexadecimal, no prefix
TimestampsFrom injected TimeProvider
JSON serializationRFC 8785 canonical JSON

The CLI produces local payload and DSSE files only. It does not submit to Rekor or attach the attestation automatically, and the current executable analysis path is ELF/section-hash based. PE/Mach-O and automatic CVE-specific patch verdicts remain outside this verified boundary.

The BinaryDiff verifier requires the signed payload itself to be the production RFC 8785 serialization, not merely schema-equivalent JSON with a valid signature. This gives each semantic predicate one accepted byte representation. With a fixed Ed25519 key, repeated signing of that payload produces identical signature and envelope bytes. ECDSA envelopes remain supported and verifiable, but repeated ECDSA signature bytes are not part of the deterministic-output guarantee.

BinaryDiffV1 Predicate

Schema Overview

The BinaryDiffV1 predicate payload uses the following structure:

{
  "predicateType": "stellaops.binarydiff.v1",
  "subjects": [
    {
      "name": "docker://repo/app@sha256:target...",
      "digest": { "sha256": "target..." },
      "platform": { "os": "linux", "architecture": "amd64" }
    }
  ],
  "inputs": {
    "base": { "digest": "sha256:base..." },
    "target": { "digest": "sha256:target..." }
  },
  "findings": [...],
  "metadata": { ... }
}

Predicate Fields

FieldTypeDescription
subjectsarrayTarget image references with digests
inputs.baseobjectBase image reference
inputs.targetobjectTarget image reference
findingsarrayPer-binary diff findings
metadataobjectTool 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": "added" }
  ],
  "confidence": 0.50,
  "verdict": "unknown"
}

Verdicts

Current CLI output uses vanilla for unchanged binaries and unknown for modified binaries. Advanced verdict classification (patched/vanilla) is planned for follow-up work.

VerdictMeaningConfidence Threshold
patchedBinary shows evidence of security patch>= 0.90
vanillaBinary matches upstream/unmodified>= 0.95
unknownCannot determine patch status< 0.90
incompatibleCannot 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

Rekor Submission

When Rekor is enabled in attestor tooling:

  1. DSSE envelope is submitted to Rekor transparency log
  2. Inclusion proof is retrieved
  3. Rekor metadata is stored in result
{
  "rekorLogIndex": 12345678,
  "rekorEntryId": "abc123...",
  "integratedTime": "2026-01-13T12:00:00Z"
}

Note: stella scan diff does not submit to Rekor; it only emits local DSSE outputs.

Verification

Binary diff attestations can be verified with:

# Attach the DSSE envelope to the image
stella attest attach \
  --image docker://repo/app:1.0.1 \
  --attestation ./binarydiff.dsse.json

# Verify with cosign (key-based)
cosign verify-attestation \
  --type stellaops.binarydiff.v1 \
  --key ./keys/binarydiff.pub \
  docker://repo/app:1.0.1

# Verify with stella CLI
stella attest verify \
  --image docker://repo/app:1.0.1 \
  --predicate-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

  1. ELF only: PE and Mach-O support planned for M2
  2. Single platform: Multi-platform diff requires multiple invocations
  3. No function-level analysis: Section-level granularity only
  4. Confidence scoring: Placeholder scoring only; verdict classifier is minimal

Roadmap

MilestoneCapability
M2PE section analysis for Windows containers
M2Mach-O section analysis for macOS binaries
M3Vendor backport corpus with curated test fixtures
M3Function-level diff using DWARF debug info
M4ML-based verdict classification

References