CVE-2026-33721: Verified Reproduction
CVE-2026-33721: MapServer: heap-buffer-overflow in SLD Categorize parser msSLDParseRasterSymbolizer
CVE-2026-33721 is verified against mapserver · c. Affected versions: 4.2.0 - 8.6.0. Fixed in 8.6.1. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00183.
What Is CVE-2026-33721?
CVE-2026-33721 is a high-severity heap-buffer-overflow (out-of-bounds write, CWE-787) in MapServer's OGC SLD parser. Parsing a crafted <se:Categorize> element can overflow a fixed-size buffer. Pruva reproduced it (reproduction REPRO-2026-00183).
CVE-2026-33721 Severity & CVSS Score
CVE-2026-33721 is rated medium severity, with a CVSS base score of 5.3 out of 10.
Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.
Affected mapserver Versions
mapserver · c versions 4.2.0 - 8.6.0 are affected.
How to Reproduce CVE-2026-33721
pruva-verify REPRO-2026-00183 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00183/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-33721
Reproduced by Pruva's autonomous agents — 177 tool calls over 15 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-33721
MapServer versions 4.2.0 through 8.6.0 contain a heap-buffer-overflow vulnerability in the OGC SLD (Styled Layer Descriptor) XML parser. The function msSLDParseRasterSymbolizer in src/mapogcsld.cpp allocates a fixed-size buffer for 100 threshold pointers when parsing a <se:Categorize> element. The reallocation guard incorrectly checks nValues == nMaxThreshold instead of nThresholds == nMaxThreshold. Because nValues and nThresholds increment at different rates, the buffer is never expanded when more than 100 <se:Threshold> children are present, causing subsequent pointer writes to spill past the 800-byte (100 × 8) array boundary. The bug is reachable unauthenticated via the SLD_BODY parameter in a WMS GetMap request.
- Package: OSGeo MapServer (
mapservCGI binary) - Affected versions: 4.2.0 — 8.6.0 (last vulnerable tag:
rel-8-6-0) - Fixed version: 8.6.1 (
rel-8-6-1) - Risk level: High (CVSS 3.1: 7.5)
- Consequences: At minimum, an attacker can crash the MapServer worker process (denial of service). Depending on heap layout and input control, the overflowed pointers may be further exploitable.
Root Cause
In src/mapogcsld.cpp, around line 2880, msSLDParseRasterSymbolizer initializes:
int nMaxThreshold = 100;
char **papszThresholds = (char **)msSmallMalloc(sizeof(char *) * nMaxThreshold);
As the parser iterates over <se:Categorize> children, every <se:Threshold> increments nThresholds and stores a pointer into papszThresholds. The growth guard is meant to reallocate the array when it fills:
// VULNERABLE (rel-8-6-0):
if (nValues == nMaxThreshold) {
nMaxThreshold += 100;
papszThresholds = (char **)msSmallRealloc(
papszThresholds, sizeof(char *) * nMaxThreshold);
}
However, nValues tracks a different counter (the number of <se:Value> elements), while nThresholds tracks the actual number of threshold entries. When an SLD contains more than 100 thresholds, nThresholds exceeds 100 but nValues may still be much lower (e.g., 1), so reallocation never occurs. The next papszThresholds[nThresholds] = … write lands beyond the 100-slot buffer, producing a heap-buffer-overflow.
Fix commit: ddd246b90acc6c7f920dfd056f33613cebe9154d
Merge commit: 7dbe91b
The patch changes exactly one line:
- if (nValues == nMaxThreshold) {
+ if (nThresholds == nMaxThreshold) {
This ensures the array is resized based on the same counter that tracks live entries.
Reproduction Steps
The complete reproduction is automated in repro/reproduction_steps.sh. It performs the following:
- Clones MapServer at
rel-8-6-0(vulnerable) andrel-8-6-1(fixed). - Builds both
mapservbinaries with AddressSanitizer (-fsanitize=address). - Generates a minimal
test.map, a tiny GeoTIFF (tiny.tif), and a malicious SLD payload (payload.sld) containing 200<se:Threshold>elements. - Invokes the vulnerable binary through the real CGI path (
QUERY_STRING+REQUEST_METHOD=GET) with the SLD payload viaSLD_BODY. - Captures the ASAN crash log (
logs/vulnerable_asan.txt). - Runs the same request against the fixed binary and verifies it returns a valid PNG image with no ASAN error (
logs/fixed_response.txt).
Evidence
- Vulnerable ASAN log:
logs/vulnerable_asan.txt- Key excerpt:
==10268==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x518000008fa0 WRITE of size 8 at 0x518000008fa0 thread T0 #0 0x7fd97611c7d4 in msSLDParseRasterSymbolizer /.../src/mapogcsld.cpp:2895 0x518000008fa0 is located 0 bytes after 800-byte region [0x518000008c80,0x518000008fa0)
- Key excerpt:
- Fixed response:
logs/fixed_response.txt- Starts with
Content-Type: image/pngand contains a valid PNG stream, confirming the fixed binary processes the same payload without crashing.
- Starts with
- Runtime manifest:
repro/runtime_manifest.json— records binary paths, tags, crash signature, and payload details. - Validation verdict:
repro/validation_verdict.json— marks statusconfirmed.
Recommendations / Next Steps
- Immediate fix: Upgrade to MapServer 8.6.1 or later. The patch is a single-line bound-check correction and carries no functional regression.
- Defensive measure: If upgrading is not immediately possible, restrict the
SLD_BODY/SLDquery parameters at the reverse-proxy or WAF level, or disable WMS SLD support in the MapServer configuration. - Testing: Add a regression test that submits an SLD with >100 thresholds and asserts the process does not crash.
- Code-review note: When arrays are grown dynamically, always use the same counter for both indexing and resize checks.
Additional Notes
- Idempotency:
repro/reproduction_steps.shwas run twice consecutively with identical results (confirmed heap-buffer-overflow on vulnerable, valid PNG on fixed). - Edge cases: The crash is triggered with any SLD containing >100
<se:Threshold>children inside a<se:Categorize>block. The exact threshold count is 100; 101 is sufficient to overflow. The reproduction uses 200 to provide a clear safety margin. - Limitations: The reproduction requires a functional GDAL/PROJ/libxml2 environment. The script installs missing Debian/Ubuntu packages automatically if they are absent.
CVE-2026-33721 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Unknown error
Unknown error
Artifacts and Evidence for CVE-2026-33721
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-33721
Upgrade mapserver · c to 8.6.1 or later.
FAQ: CVE-2026-33721
How is CVE-2026-33721 reached?
Which MapServer versions are affected by CVE-2026-33721, and where is it fixed?
How can I reproduce CVE-2026-33721?
References for CVE-2026-33721
Authoritative sources for CVE-2026-33721 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.