Skip to content

Security: constant-time comparisons, bounded decompression and streaming export with resource limits - #11

Merged
dkruempe merged 5 commits into
masterfrom
feature/p0-constant-time-comparisons
Sep 19, 2026
Merged

dkruempe merged 5 commits into
masterfrom
feature/p0-constant-time-comparisons

Conversation

@dkruempe

@dkruempe dkruempe commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

Description

This PR contains two bundled security-forward workstreams tracked in ROADMAP.md:

  1. P0 — constant-time comparisons everywhere secrets are compared. All mock
    secret and integrity-critical comparisons now funnel through
    keepass::detail::constant_time_eq (backed by CRYPTO_memcmp,
    src/include/libkeepass/detail/constant_time.hh), covering the KDBX4 stored
    header HMAC, the KDBX3/4 header hash + content start byte + per-block
    verification (src/kdbx_import.cc, src/stream.cc), the KDB content
    hash/password check (src/kdb.cc), secure_string equality
    (src/secure.cc) and the derived-key zero check (src/key.cc). Unit-tested
    in test/constant_time.cc.

  2. P1 — bound decompressed payload size (zip-bomb protection). gzip/hashed/
    HMAC block streams enforce configurable caps on total decompressed bytes,
    block count and per-block size, checked before any allocation or MAC
    verification; violations surface as FormatError. Hostile fixtures in
    test/robustness.cc cover gzip bombs, HMAC block size/count and hashed
    framing.

All of the above was previously split verbatim from the monolith codec commit;
this PR sources the split output and the streaming export with explicit
resource limits (src/export.cc, cli/export.cc).

Related issue

Security coverage tracked in ROADMAP.md:

  • P0 «Constant-time comparisons everywhere secrets are compared» — [x] all
  • P1 «Bound decompressed payload size (zip-bomb protection)» — [x]
  • P1 «Parser/XML resource budgets» — [x] (hostile fixtures in test/robustness.cc)

Type of change

  • New feature (streaming export with resource limits)
  • Security/hardening (constant-time comparisons, decompression budgets)

Checklist

  • The whole test suite passes: ctest --test-dir build --output-on-failure
  • Formatting is clean: clang-format --dry-run --Werror $(git ls-files '*.cc' '*.hh')
  • New public API is documented with Doxygen comments in src/include/libkeepass/
  • User-facing changes are reflected in CHANGELOG.md (Keep a Changelog format)

Security information

This branch (feature/p0-constant-time-comparisons) hardens every point where a
secret is compared for equality, and bounds decompression/streaming resources
so hostile payloads cannot be forced to allocate (or keep alive) arbitrary
amounts of memory before any MAC/hash check runs:

  • Constant-time everywhere: constant_time_eq (src/include/libkeepass/detail/constant_time.hh)
    wraps CRYPTO_memcmp and is used for the KDBX4 header HMAC, the KDBX3/4
    header hash + per-block integrity, the KDB content-hash/password check, all
    secure_string equality comparisons, and the derived-key zero check. These
    are the only places secret material is compared; no plaintext == or
    memcmp remains on any secret path (verified by test/constant_time.cc and
    repo-wide grep).
  • Bounded decompression + streaming: gzip/hashed/HMAC block streams honor
    max_total_bytes, max_block_count and per-block size caps before any
    allocation or MAC verification; exceeding them raises FormatError (not
    swallowed), so export/import cannot be turned into a zip-bomb or an
    unbounded-memory parser.
  • Test evidence: ctest 18/18 passes; hostile fixtures (test/robustness.cc,
    test/property.cc) and constant-time tests (test/constant_time.cc) all pass;
    clang-format and clang-tidy 22.1.8 are clean on this HEAD.

Residual, intentionally NOT changed here (tracked in ROADMAP P2 «CLI
credential hygiene», P2 «Keyfile hardening»):
cli/kpx.cc still accepts the
database password on the command line (kpx --password / KEEPASS_PASSWORD),
which exposes it to argv//proc/the environment. Moving to
--password-file/getpass/stdin is a separate, already-scoped change; this PR
deliberately keeps that border unchanged so the constant-time work stays
reviewable and mergeable on its own.

@codecov-commenter

codecov-commenter commented Sep 19, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 93.29004% with 217 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cli/output.cc 86.75% 42 Missing ⚠️
src/kdbx_xml_entry.cc 82.09% 41 Missing ⚠️
src/keepass.cc 48.88% 23 Missing ⚠️
src/stream.cc 80.95% 20 Missing ⚠️
src/kdb_fields.cc 94.09% 14 Missing ⚠️
test/property.cc 96.27% 2 Missing and 12 partials ⚠️
src/kdb_import.cc 90.82% 10 Missing ⚠️
cli/args.cc 95.69% 9 Missing ⚠️
src/cipher_aes.cc 73.52% 9 Missing ⚠️
src/kdb_export.cc 92.40% 6 Missing ⚠️
... and 10 more

📢 Thoughts on this report? Let us know!

Comment thread cli/output.cc Fixed
Comment thread test/property.cc Fixed
Comment thread test/property.cc Fixed
Comment thread test/property.cc Fixed
…wallowing them

The iostream layer silently converts exceptions thrown from streambuf
underflow into short reads, so a stream exceeding its resource budget used
to end as a truncated/empty payload (or, in the KDBX4 HMAC path, a spurious
IoError). Record the violation on the streambuf (BudgetExceeded()) and have
the importers raise a FormatError once the read finishes.

Adds robustness tests for every budget: gzip bomb, HMAC block size/count,
hashed framing, XML depth, group/entry/history counts and binary/string
field sizes.
Fixes the failing Lint job: several files were flagged by clang-format
(column overflows, include ordering, member-init alignment).
Resolve all 9 warnings-as-errors that the CI lint workflow flags on this
branch while leaving the pinned clang-format 22 output byte-identical:

- make KdbxXml::ParseProtectedString const (readability-make-member-function-const)
- uppercase integer literal suffixes off-test/lqueue u->U and ull->ULL
- return convert-string by value instead of by const-ref (bugprone-return-const-ref-from-parameter)
- build prefix keys via insert() instead of literal concatenation
  (performance-inefficient-string-concatenation)
- make the argonn2 memory product's widening explicit
  (bugprone-implicit-widening-of-multiplication-result)
- construct the test ToBytes() payload into a name vector instead of
  restating the return type (modernize-return-braced-init-list)

Verified locally: the same nine clang-tidy 22 diagnostics no longer
reproduce, all 18 ctest binaries pass, and clang-format is drift-free.
Comment thread test/property.cc Fixed
@dkruempe dkruempe changed the title Split monolith codecs and add streaming export with resource limits Security: constant-time comparisons, bounded decompression and streaming export with resource limits Sep 19, 2026
- Drop unused Str overload and StrVal helper in property.cc
- Reword framing comment flagged as commented-out code
- Document the RunAudit function in output.cc
@dkruempe
dkruempe merged commit 55f982f into master Sep 19, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants