secure boot measurement gap analyser + sbat/tcg parsing and pcr replay
  • C 97.8%
  • Shell 1.1%
  • Makefile 1.1%
Find a file
2026-05-01 17:05:00 +00:00
include/pegleg feat(cli): wire all commands and add the qemu/swtpm lab harness 2026-05-01 17:05:00 +00:00
lab feat(cli): wire all commands and add the qemu/swtpm lab harness 2026-05-01 17:05:00 +00:00
src feat(cli): wire all commands and add the qemu/swtpm lab harness 2026-05-01 17:05:00 +00:00
tests feat(policy): add json parsing and cve backed enforcement 2026-04-27 21:20:00 +00:00
.gitignore chore: bootstrap pegleg runtime, build system, and test harness 2026-04-12 09:00:00 +00:00
Makefile build(make): install the static library and public headers 2026-04-29 18:45:00 +00:00
README.txt feat(cli): wire all commands and add the qemu/swtpm lab harness 2026-05-01 17:05:00 +00:00

 ____  _____ ____ _     _____ ____
|  _ \| ____/ ___| |   | ____/ ___|
| |_) |  _|| |  _| |   |  _|| |  _
|  __/| |__| |_| | |___| |__| |_| |
|_|   |_____\____|_____|_____\____|

              measured boot lab for disclosed
                 secure boot bypasses
                    c4tchall - apr 2026


#### [abstract] ####
secure boot enforces what may execute; measured boot records what did execute
into the tpm platform configuration registers [1][4]. a chain loaded boot
loader such as grub sits between the two. firmware verifies the grub image and
grub then reads a plain text configuration file from the efi system partition
that firmware never verified and never measured

                            secure boot           measured boot
                            what may run          what did run
                           +------------+        +------------+
                           | signature  |        | pcr extend |
                           | check      |        | chain      |
                           +------------+        +------------+
                                 |                     |
                                 v                     v
      +-------+  +------+  +------+  +---------+  +---------+
      | ovmf  |->| shim |->| grub |->| vmlinuz |->| initrd  |
      +-------+  +------+  +------+  +---------+  +---------+
          |         |         |          |            |
       extend    extend    extend     extend       extend
       pcr0-7    pcr4      pcr4       pcr8         pcr9

                              grub.cfg
                                 |
                                 +--> parsed by grub
                                 +--> never signed by firmware
                                 +--> never measured into a pcr

that seam is the source of a family of disclosed vulnerabilities: crafted
grub.cfg input can corrupt boot loader memory (cve-2020-10713, boothole
[2][5]) or invoke commands missing from the secure boot denylist
(cve-2020-14372 acpi, cve-2020-27779 cutmem [2]). none of these paths alter a
pcr, so secrets sealed to those pcrs still unseal

pegleg models that failure and parses pe images and their sbat metadata
[3][6], parses tcg measured boot logs in both legacy sha-1 and crypto agile
layouts [4], replays pcr extension in software, matches images against a
database of disclosed grub vulnerabilities and proves that grub.cfg is absent
from the measurements and then emits the revocation, configuration, and policy
artifacts that close the seam. it contains no exploit for any referenced bug

#### [pcr model] ####
a pcr is extend only:

    pcr[i] = hash( pcr[i] || event_digest )

replaying a log reconstructs the registers from zero without a tpm:

    pcr[0] = 0
    for each event in log:
        if event.pcr == i:
            pcr[i] = H(pcr[i] || event.digest)

pegleg accepts both record layouts and selects between them from the first
record. crypto agile logs open with a spec id event that declares the digest
algorithms and widths for every subsequent record [4]:

    legacy (sha1)
    +--------+--------+------------+-----------+---------+
    | pcr u32| type   | digest[20] | size u32  | payload |
    +--------+--------+------------+-----------+---------+

    crypto agile
    +--------+--------+--------+--------------------------+-----------+---------+
    | pcr u32| type   | count  | { alg u16, digest[] } xN | size u32  | payload |
    +--------+--------+--------+--------------------------+-----------+---------+

#### [measurement gap] ####
grub.cfg selects the kernel, its parameters and the modules loaded but it is
read after firmware has finished measuring. it is data, not an image, and no
pcr commits to it:

                verified            verified          not verified
                measured            measured          not measured
                   |                   |                   |
                   v                   v                   v
              +---------+        +---------+        +-------------+
              |  grub   |------->| kernel  |------->| grub.cfg    |
              |  image  |        | initrd  |        | selection   |
              +---------+        +---------+        +-------------+
                   |                   |                   |
                   | extend            | extend            | nothing
                   v                   v                   v
              +-------------------------------------------------------+
              |  tpm pcrs 4, 8, 9                                     |
              +-------------------------------------------------------+

a policy that unseals on pcr 7 alone cannot distinguish a benign configuration
from a hostile one and the disclosed cves are what becomes reachable once that
unmeasured input is parsed

#### [cve(s)] ####
    +----------------+----------------------------------------+-----------+
    | CVE            | summary                                | component |
    +----------------+----------------------------------------+-----------+
    | CVE-2020-10713 | boothole, overflow in the cfg parser   | grub      |
    | CVE-2020-14308 | integer overflow in grub_malloc        | grub      |
    | CVE-2020-14309 | heap overflow                          | grub      |
    | CVE-2020-14310 | integer overflow                       | grub      |
    | CVE-2020-14311 | heap overflow                          | grub      |
    | CVE-2020-14372 | secure boot bypass via the acpi command| grub      |
    | CVE-2020-27779 | secure boot bypass via the cutmem command | grub   |
    +----------------+----------------------------------------+-----------+

the fixes converged on two mechanisms. sbat embeds a generation in the signed
image so old builds are revoked by generation rather than by hash [3]. a
measured boot policy that pins the loader inputs and expected pcr values makes
a configuration change a policy violation even when no measurement changes:

    sbat generation revocation           loader input pinning
    +----------------------+          +----------------------+
    | shim checks .sbat    |          | policy lists expected|
    | generation against   |          | pcr values and the   |
    | the dbx revocation   |          | components that must |
    | list before loading  |          | be present           |
    +----------------------+          +----------------------+
              |                                   |
              +---------------+-------------------+
                              v
                    a boot path that is both
                    signed and attested

#### [implementation] ####
a single c11 binary with no runtime dependencies beyond libc and every parser
bounds checks untrusted input and returns a diagnostic on malformed data

    +----------------+--------------------------------------------------+
    | module         | responsibility                                   |
    +----------------+--------------------------------------------------+
    | buffer, common | dynamic byte buffers and a checked reader         |
    | sha1, sha256   | hash primitives for pcr replay                    |
    | pe             | pe/coff parser for efi images                     |
    | sbat           | sbat section parser and generation model          |
    | tcg            | crypto agile and legacy event log parser          |
    | pcr            | pcr bank replay and formatting                    |
    | json           | a small recursive descent json parser             |
    | policy         | policy loading and enforcement                    |
    | cve            | the vulnerability database                        |
    | measure        | a readable model of the measured boot path        |
    | report         | text and json renderers for findings              |
    | harden, emulate| the two higher level workflows                    |
    +----------------+--------------------------------------------------+

pe images are walked from the dos stub to the pe signature, coff header,
optional header, and section table, with each section bounded against the file
[6]. sbat is stored in a section named .sbat as comma separated records of
component, generation and vendor metadata; revocation is by generation [3]

pcr extension is a single hash of the current register concatenated with the
event digest:

    int pl_pcr_extend(pl_pcr_bank_t *bank, uint32_t index,
                      const uint8_t *digest) {
        slot = bank->values + (size_t)index * bank->digest_size;
        memcpy(combined, slot, bank->digest_size);
        memcpy(combined + bank->digest_size, digest, bank->digest_size);
        pl_hash(bank->algorithm, combined, bank->digest_size * 2, result);
        memcpy(slot, result, bank->digest_size);
    }

efi variable events are decoded so the model reads as prose:

    pcr4  EV_EFI_BOOT_SERVICES_APPLICATION  sha256  6f2c...  boot loader
    pcr7  EV_EFI_VARIABLE_DRIVER_CONFIG     sha256  1a90...  SecureBoot
    pcr7  EV_EFI_VARIABLE_DRIVER_CONFIG     sha256  9be3...  PK
    pcr7  EV_EFI_VARIABLE_DRIVER_CONFIG     sha256  44c1...  KEK

the emulate workflow writes a baseline and a modified configuration that
selects the same kernel plus an unmeasured include; the replayed pcrs are
identical in both cases:

    +----------------------+        +----------------------+
    | measured boot log    |        | candidate grub.cfg   |
    | pcr0-7: firmware,    |        | timeout, menuentry,  |
    |        db, dbx, shim  |        | kernel selection     |
    | pcr4:   grub image    |        |                      |
    +----------------------+        +----------------------+
              |                                |
              |  replay                        |  no measurement
              v                                v
    +--------------------------------------------------------+
    |  identical pcr values for baseline and modified config  |
    +--------------------------------------------------------+

#### [demo] ####
the lab boots ovmf with a virtual tpm through swtpm and captures the real
event log. output from such a log:

    pegleg measured boot gap demonstration

    cve:                CVE-2020-10713 (BootHole)
    component:          grub
    fixed generation:   >= 2
    vulnerable:         yes
    grub.cfg measured:  no

    replayed PCR values
      PCR 4  sha256  ee4b0e933b56cdf12a42b1e3f3b9ed1aa70cf9f3cf37325693255c8bfbcb8ba8
      PCR 7  sha256  aa3fbb7913e12ae041ff4ac2b75384d7e97ab7a9cc3e405c2bbfc96c65590160

    conclusion:
      the event log measures the firmware, the security databases, and the
      chain loaded boot loader, but it does not contain any event whose
      payload matches grub.cfg.

#### [remediation] ####
revocation removes the ability to chain load a vulnerable generation and the
policy removes the ability to unseal beside a configuration that was never
attested. neither alone is sufficient

    before                                after
    +------------------+                  +------------------+
    | signed grub 1    |                  | signed grub 2    |
    | reads any cfg    |                  | cfg sig enforced |
    | pcrs do not cover|                  | policy pins both |
    | the cfg          |                  | image and inputs |
    +------------------+                  +------------------+
             |                                     |
             v                                     v
    secret unseals beside               secret unseals only when
    an unattested config                image and config both match

the harden workflow emits a dbx sbat revocation entry, a grub configuration
with signature verification enforced and then a measured boot policy that revokes
the vulnerable generation and pins expected pcr digests

#### [scope] ####
pegleg reads files and reports. it does not modify firmware, write to a tpm,
or exploit anything. its endpoints are the software pcr replay and the absence
of a grub.cfg measurement

    useful for                                not a replacement for
    +----------------------------------+      +----------------------------+
    | auditing a chain for a loader in |      | a real tpm                 |
    | the unmeasured gap               |      | an attestation service     |
    | checking exposure by generation  |      | a vendor response process  |
    | generating a hardened policy     |      |                            |
    +----------------------------------+      +----------------------------+

#### [references] ####
[1] trusted computing group, "pc client platform tpm profile (ptp)", 2023.
    https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/
[2] eclypsium, "there's a hole in the boot", 2020.
    https://eclypsium.com/2020/07/29/theres-a-hole-in-the-boot/
[3] rhboot, "secure boot advanced targeting (sbat)".
    https://github.com/rhboot/shim/blob/main/SBAT.md
[4] trusted computing group, "tcg efi platform specification", 2022.
    https://trustedcomputinggroup.org/resource/tcg-efi-platform-specification/
[5] mitre, "cve-2020-10713".
    https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10713
[6] microsoft, "pe format".
    https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
[7] tianocore, "edk2 reference implementation".
    https://github.com/tianocore/edk2
[8] gnu, "grub boot loader".
    https://www.gnu.org/software/grub/