Skip to content

Re-apply lost fix 4f33f5b: collection_unpack value_len underflow regression#3560

Open
g4mm4-VCF wants to merge 4 commits intoowasp-modsecurity:v2/masterfrom
g4mm4-VCF:fix/persist-dbm-value-len-regression
Open

Re-apply lost fix 4f33f5b: collection_unpack value_len underflow regression#3560
g4mm4-VCF wants to merge 4 commits intoowasp-modsecurity:v2/masterfrom
g4mm4-VCF:fix/persist-dbm-value-len-regression

Conversation

@g4mm4-VCF
Copy link
Copy Markdown

Summary

Re-applies commit 4f33f5b ("Fix possible segfault in collection_unpack",
@twouters, 2024-03-01) which was silently dropped during merge 649aea7
on 2024-04-04. The drop has been confirmed by:

  • git show 4f33f5b:apache2/persist_dbm.c | sed -n '60,75p' — has the guard
  • git show 649aea7:apache2/persist_dbm.c | sed -n '60,75p' — guard gone
  • git show v2.9.13:apache2/persist_dbm.c | sed -n '60,75p' — still gone

Affected releases: v2.9.10, v2.9.11, v2.9.12, v2.9.13.

What this PR does

  1. Re-introduces the var->name_len < 1 || and var->value_len < 1 ||
    guards in collection_unpack() (one-line change each).
  2. Adds a standalone regression test under
    tests/regression/persist_dbm/ that replicates the unpack
    arithmetic and asserts:
    • the buggy code-path produces copy_len = UINT_MAX for a
      value_len=0 blob (smoking gun)
    • the patched code-path returns NULL before the underflow

Test plan

  • cd tests/regression/persist_dbm && make check passes
  • manual: feed an 8-byte SDBM blob (header + name_len=1 + name="" +
    value_len=0) into a SecDataDir-backed collection; observe the
    previously-crashing worker now ignore the malformed entry.

References

Coordinated with @airween via private security channel.

@g4mm4-VCF g4mm4-VCF force-pushed the fix/persist-dbm-value-len-regression branch from cf1f9c4 to 17f3445 Compare May 10, 2026 09:15
@airween
Copy link
Copy Markdown
Member

airween commented May 10, 2026

Hi @g4mm4-VCF,

thanks for this patch.

Please take a look at the SonarCLoud report and fix those issues.

g4mm4-VCF added 3 commits May 10, 2026 20:21
The fix from owasp-modsecurity#3082 was lost in merge 649aea7 (2024-04-04) and has been
missing from every release v2.9.10 .. v2.9.13. This re-applies the
same one-line guard as the original commit by @twouters.

Refs: owasp-modsecurity#3082
Standalone harness that replicates the unpack arithmetic and asserts
that the patched bound check rejects a value_len=0 blob while the
buggy version would have produced a 4 GiB copy length.

Runs without APR / Apache build dependencies so it integrates into
`make check` as a self-contained TAP test.
Split combined declarations and remove redundant inner casts as
flagged by SonarCloud's static analyser. No functional change —
the regression test still produces buggy_copy = UINT_MAX on the
buggy path and patched_rc = 0 on the patched path.
@g4mm4-VCF g4mm4-VCF force-pushed the fix/persist-dbm-value-len-regression branch from b47c646 to d7759ab Compare May 10, 2026 13:22
@g4mm4-VCF
Copy link
Copy Markdown
Author

g4mm4-VCF commented May 10, 2026 via email

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Re-applies a previously lost defensive check in collection_unpack() to prevent value_len - 1 underflow (and resulting huge memcpy/apr_pstrmemdup), and adds a standalone regression test intended to demonstrate the buggy vs fixed arithmetic.

Changes:

  • Reintroduce name_len < 1 / value_len < 1 guards in apache2/persist_dbm.c before subtracting 1 from those lengths.
  • Add a new standalone C regression test for the value_len == 0 underflow scenario.
  • Add an Automake snippet intended to build/run the new regression test.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
apache2/persist_dbm.c Restores the missing < 1 guards to prevent *_len - 1 unsigned underflow leading to oversized copies.
tests/regression/persist_dbm/test_persist_dbm_value_len.c Adds a standalone test program modeling the buggy vs patched arithmetic around value_len - 1.
tests/regression/persist_dbm/Makefile.am Attempts to hook the new test into an Automake-based make check flow for that subdirectory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apache2/persist_dbm.c Outdated
Comment on lines +64 to +72
if (var->name_len < 1 || blob_offset + var->name_len > blob_size) return NULL;
var->name = apr_pstrmemdup(msr->mp, (const char *)blob + blob_offset, var->name_len - 1);
blob_offset += var->name_len;
var->name_len--;

var->value_len = (blob[blob_offset] << 8) + blob[blob_offset + 1];
blob_offset += 2;

if (blob_offset + var->value_len > blob_size) return NULL;
if (var->value_len < 1 || blob_offset + var->value_len > blob_size) return NULL;
Comment on lines +1 to +3
TESTS = test_persist_dbm_value_len
check_PROGRAMS = test_persist_dbm_value_len
test_persist_dbm_value_len_SOURCES = test_persist_dbm_value_len.c
Comment on lines +4 to +5
test_persist_dbm_value_len_CFLAGS = -fsanitize=address,undefined -O0 -g -Wall
test_persist_dbm_value_len_LDFLAGS = -fsanitize=address,undefined
Comment on lines +54 to +60
* Faithful replica of collection_unpack's deserialisation arithmetic.
*
* The two functions below differ ONLY in the `value_len < 1 ||` clause
* that 4f33f5b added and 649aea7 removed. The blob layout, the read of
* the 16-bit big-endian length, and the apr_pstrmemdup() argument
* computation are otherwise byte-for-byte identical.
*
*
* 0 = both before-fix and after-fix paths behaved as expected
* (test PASSED — confirms regression and confirms patch fixes it)
* 1 = test FAILED (either path returned an unexpected result)
@airween
Copy link
Copy Markdown
Member

airween commented May 10, 2026

Hi there, I have fixed all the issues, and everything is now working properly.

Thanks - now please take a look at Copilot suggestions. I think all of them make sense.

g4mm4-VCF added a commit to g4mm4-VCF/ModSecurity that referenced this pull request May 10, 2026
1. Add missing bound check before reading value_len (Copilot owasp-modsecurity#1 — real
   bug). After consuming the name field, blob_offset can advance to
   exactly blob_size; the subsequent 16-bit read of value_len from
   blob[blob_offset] / blob[blob_offset+1] would then OOB-read on a
   truncated blob. Fixed with the standard 2-byte check.

2. Drop tests/regression/persist_dbm/ (Copilot owasp-modsecurity#2-owasp-modsecurity#5). The directory
   was not wired into the Autotools build (no AC_CONFIG_FILES nor
   parent SUBDIRS entry), and the existing tests/regression/ is a
   Perl-based HTTP integration harness that doesn't fit a unit test
   of a static function. Wiring it into tests/Makefile.am where
   msc_test lives would require non-trivial restructuring; keeping
   the standalone harness outside the upstream tree (in the security
   advisory's PoC archive) is the cleaner path for now.

Refs: PR owasp-modsecurity#3560 review comments by github-actions[bot] / Copilot.
@g4mm4-VCF
Copy link
Copy Markdown
Author

Thanks for running Copilot. Pushed f239b02 to address all 5 suggestions. Summary:

#1 (apache2/persist_dbm.c:72) — fixed. Copilot is right, this is a real adjacent bug. After consuming the name field, blob_offset can land exactly at blob_size, and the 16-bit read of value_len from blob[blob_offset] / blob[blob_offset+1] would OOB-read on a truncated blob. Added if (blob_offset + 1 >= blob_size) return NULL; before the read. Same defensive pattern as the existing checks for name_len.

#2#5 (tests/regression/persist_dbm/) — dropped. Copilot was right that the directory wasn't wired into Autotools (no AC_CONFIG_FILES, no parent SUBDIRS), and the sanitizer flags weren't portable. Looking at the existing test layout (tests/regression/ is the Perl HTTP regression harness, tests/ is the msc_test binary that links all the apache2/ sources), a unit test of a static function inside persist_dbm.c doesn't slot cleanly into either: it's not an HTTP-level test for the Perl harness, and adding a separate check_PROGRAMS to tests/Makefile.am would require non-trivial restructuring of the existing pattern.

The cleanest move was to drop the in-tree test directory entirely. The standalone test harness is preserved outside the upstream tree (in the disclosure PoC archive) for anyone who wants to verify the fix offline:

// snippet from the standalone test
unsigned char blob[] = {
    0x49, 0x52, 0x01,   // SDBM header
    0x00, 0x01,         // name_len = 1
    0x00,               // name = ""
    0x00, 0x00,         // value_len = 0  ← BUG TRIGGER
};
// vulnerable build → apr_pstrmemdup(..., 0xFFFFFFFF) → SIGSEGV
// patched build    → second bound check returns NULL early

Happy to revisit the in-tree test if you'd prefer a Perl-level integration test that drops a malformed SDBM file into SecDataDir and asserts no segfault on retrieve. That'd be more work but fits your existing harness cleanly. Let me know.

Diff vs upstream/v2/master is now just the three checks in apache2/persist_dbm.c:

  • the original name_len < 1 and value_len < 1 guards from 4f33f5b, plus
  • the new blob_offset + 1 >= blob_size check that Copilot caught.

CI / SonarCloud should pass on the new commit. Ping me if anything else surfaces.

1. Add missing bound check before reading value_len (Copilot owasp-modsecurity#1 — real
   bug). After consuming the name field, blob_offset can advance to
   exactly blob_size; the subsequent 16-bit read of value_len from
   blob[blob_offset] / blob[blob_offset+1] would then OOB-read on a
   truncated blob. Fixed with the standard 2-byte check.

2. Drop tests/regression/persist_dbm/ (Copilot owasp-modsecurity#2-owasp-modsecurity#5). The directory
   was not wired into the Autotools build (no AC_CONFIG_FILES nor
   parent SUBDIRS entry), and the existing tests/regression/ is a
   Perl-based HTTP integration harness that doesn't fit a unit test
   of a static function. Wiring it into tests/Makefile.am where
   msc_test lives would require non-trivial restructuring; keeping
   the standalone harness outside the upstream tree (in the security
   advisory's PoC archive) is the cleaner path for now.

Refs: PR owasp-modsecurity#3560 review comments by github-actions[bot] / Copilot.
@g4mm4-VCF g4mm4-VCF force-pushed the fix/persist-dbm-value-len-regression branch from f239b02 to fbdd35a Compare May 10, 2026 17:51
@g4mm4-VCF
Copy link
Copy Markdown
Author

SonarCloud caught a follow-up: cognitive complexity tipped to 26/25 because of the new bound check. Force-pushed fbdd35a4 that folds Copilot's catch into the existing name-bound check instead of adding a new line:

- if (blob_offset + var->name_len > blob_size) return NULL;
+ /* Need name_len bytes for the name body plus 2 more for the value_len header. */
+ if (var->name_len < 1 || blob_offset + var->name_len + 2 > blob_size) return NULL;

The strengthened predicate now requires room for both the name body and the 2-byte value_len header. That's a strict superset of the original check, covers the truncated-blob OOB read Copilot flagged, and adds zero new branches → cognitive complexity stays at the previous baseline.

Final diff vs upstream/v2/master is now exactly three semantic changes + one comment line in apache2/persist_dbm.c:

  1. name_len < 1 || — the original 4f33f5b guard that 649aea7 dropped
  2. + 2 on the upper bound — covers the new value_len-header read site
  3. value_len < 1 || — the matching 4f33f5b guard for the value path

CI/SonarCloud should be green again on fbdd35a4.

@sonarqubecloud
Copy link
Copy Markdown

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