REPRO-2026-00284: Verified Reproduction
REPRO-2026-00284: XRING: Alibaba XQUIC QPACK ring-buffer resize underflow
REPRO-2026-00284 is verified against Alibaba XQUIC · github. Affected versions: All XQUIC versions through v1.9.4 (since first public release Jan 2022). This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00284.
What Is REPRO-2026-00284?
REPRO-2026-00284 documents "XRING", a high-severity denial-of-service vulnerability in Alibaba's XQUIC HTTP/3 library, caused by a ring-buffer resize underflow in the QPACK dynamic-table implementation. It affects all published XQUIC versions through v1.9.4. Pruva reproduced it (reproduction REPRO-2026-00284).
REPRO-2026-00284 Severity
REPRO-2026-00284 is rated high severity.
High — serious impact or readily exploitable. Prioritize remediation.
Affected Alibaba XQUIC Versions
Alibaba XQUIC · github versions All XQUIC versions through v1.9.4 (since first public release Jan 2022) are affected.
How to Reproduce REPRO-2026-00284
pruva-verify REPRO-2026-00284 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00284/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for REPRO-2026-00284
- reached the target end-to-end
- crash observed
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
spec-compliant QPACK encoder stream instructions (Set Dynamic Table Capacity=64, 61x Insert x:y, Insert AAAAA:BBBBB, Set Dynamic Table Capacity=65) sent over a QUIC/HTTP3 unidirectional encoder stream by an unauthenticated remote client
- quic-go client dials XQUIC demo_server over QUIC/UDP
- HTTP3 control stream + QPACK encoder uni stream
- xqc_decoder processes Set Dynamic Table Capacity
- xqc_decoder_set_dtable_cap
- xqc_dtable_set_capacity
- xqc_ring_mem_resize both-truncated branch (ori_sz1 = mcap - soffset_ori, should be rmem
- capacity)
- 64-byte heap OOB read + size_t underflow into xqc_memcpy
- glibc _FORTIFY_SOURCE…
How the agent worked
Root Cause and Exploit Chain for REPRO-2026-00284
Alibaba XQUIC (all versions through v1.9.4, latest at time of disclosure) contains a
remote, unauthenticated denial-of-service vulnerability in its HTTP/3 QPACK dynamic
table implementation. The bug is in xqc_ring_mem_resize() (src/common/utils/ringmem/xqc_ring_mem.c).
When the QPACK dynamic-table ring buffer has wrapped (data is split across the end and
the beginning of the buffer — the "both-truncated" resize case) and the QPACK decoder
receives a Set Dynamic Table Capacity instruction that grows the table, the resize
code sizes the first block of the original buffer using the new capacity (mcap)
instead of the old capacity (rmem->capacity). This produces a heap out-of-bounds
read of the old buffer followed by a size_t underflow (rmem->used - ori_sz1) into the
third xqc_memcpy, which glibc _FORTIFY_SOURCE aborts (*** buffer overflow detected ***: terminated). A remote, unauthenticated QUIC/HTTP3 client can trigger this with
spec-compliant QPACK encoder-stream instructions, crashing the server process.
- Package/component affected:
xquic—src/common/utils/ringmem/xqc_ring_mem.c(xqc_ring_mem_resize), reached viaxqc_dtable_set_capacity(src/http3/qpack/dtable/xqc_dtable.c) ←xqc_decoder_set_dtable_cap(src/http3/qpack/xqc_decoder.c) when the decoder processes aSet Dynamic Table Capacityinstruction on the QPACK encoder stream. - Affected versions: All published XQUIC versions through v1.9.4. The disclosure
states no upstream patch is available. Reproduced here against XQUIC v1.9.4
(commit
96155cf, tagv1.9.4). - Risk level / consequences: High. Remote, unauthenticated, deterministic process crash (DoS) of any XQUIC HTTP/3 endpoint that allows the QPACK dynamic table (default configuration). The OOB read + underflowed memcpy also imply potential heap memory-safety impact beyond a simple crash, although only the crash (DoS) is claimed and reproduced.
Impact Parity
- Disclosed/claimed maximum impact: Remote unauthenticated crash (DoS); the blog
additionally describes a 64-byte heap OOB read and a
size_tunderflow intomemcpy. No code execution is claimed. - Reproduced impact from this run: Deterministic remote crash of the real XQUIC
demo_server(HTTP/3 over QUIC/UDP) via a realquic-goclient sending spec-compliant QPACK encoder-stream instructions. Observed: server process terminated with exit code134(SIGABRT) and glibc message*** buffer overflow detected ***: terminated(the_FORTIFY_SOURCEabort of the underflowedmemcpy). 2/2 vulnerable attempts crashed; 2/2 negative-control (one-line-fix) attempts stayed alive. - Parity:
fullfor the claimed DoS (remote unauthenticated crash) via the claimed network protocol surface. - Not demonstrated: Code execution / arbitrary heap write. Only the crash (DoS) and the underlying OOB-read + underflow symptom are demonstrated, matching the claim.
Root Cause
In xqc_ring_mem_resize(xqc_ring_mem_t *rmem, size_t cap), when cap > rmem->capacity
the function allocates a new power-of-two buffer mcap = xqc_pow2_upper(cap) and copies
the used bytes from the old buffer into the new one. The copy has four shape cases. In
the "both-truncated" case (data wraps in both the old and the new buffer,
i.e. soffset_ori >= eoffset_ori and soffset_new >= eoffset_new):
size_t new_sz1 = mcap - soffset_new; /* size of first block in new buffer */
size_t ori_sz1 = mcap - soffset_ori; /* BUG: uses NEW cap; should be rmem->capacity */
soffset_ori is computed with the old mask (rmem->sidx & rmem->mask), so it is an
offset inside the old buffer of size rmem->capacity. The first block of the old buffer
that must be copied therefore has size rmem->capacity - soffset_ori, not
mcap - soffset_ori. Because mcap > rmem->capacity, ori_sz1 is too large. In the
"first block of new buffer is smaller than original buffer" sub-branch this causes:
xqc_memcpy(buf, rmem->buf + soffset_ori + new_sz1, ori_sz1 - new_sz1)— readsori_sz1 - new_sz1bytes starting past the end of the old buffer (64-byte OOB read in the reproduced scenario);xqc_memcpy(buf + ori_sz1 - new_sz1, rmem->buf, rmem->used - ori_sz1)—rmem->used - ori_sz1underflows (unsigned), producing a hugesize_t, so glibc's__memcpy_chk(_FORTIFY_SOURCE) aborts the process.
Concrete reproduced arithmetic (v1.9.4, payload from FoxIO xring-poc):
initial Set Dynamic Table Capacity = 64 → rmem->capacity = 64 (mask 63). 61×
Insert "x":"y" then Insert "AAAAA":"BBBBB" evict entries and wrap the 64-byte ring,
leaving used = 10, sidx = 122 (soffset_ori = 122 & 63 = 58), eidx = 132
(eoffset_ori = 132 & 63 = 4): old buffer is truncated. Set Dynamic Table Capacity = 65
→ xqc_ring_mem_resize(rmem, 65): mcap = xqc_pow2_upper(65) = 128, soffset_new = 122 & 127 = 122, eoffset_new = 132 & 127 = 4: new buffer also truncated → "both-truncated"
branch. new_sz1 = 128 - 122 = 6; buggy ori_sz1 = 128 - 58 = 70 (correct: 64 - 58 = 6). new_sz1 < ori_sz1 → reads 70 - 6 = 64 bytes OOB, then used(10) - 70 underflows
→ memcpy_chk abort (exit 134).
Fix (no upstream patch exists; applied here as the negative control):
- size_t ori_sz1 = mcap - soffset_ori; /* size of first block in original buffer */
+ size_t ori_sz1 = rmem->capacity - soffset_ori; /* size of first block in original buffer */
With this one-line change the identical malicious payload no longer crashes the server (verified: 2/2 fixed attempts stay alive).
Reproduction Steps
- Script:
bundle/repro/reproduction_steps.sh(self-contained; reuses the durable project cache when present, otherwise clones/builds XQUIC v1.9.4 + Tongsuo 8.4-stable- a
quic-goclient from source).
- a
- What it does:
- Locates the durable project cache via
bundle/project_cache_context.json; uses prebuiltdemo_server_vuln(XQUIC v1.9.4,-Werrorremoved, vulnerable line intact) anddemo_server_fixed(same tree with the one-lineori_sz1fix) plus a prebuiltquic-goclient that reproduces the FoxIO XRING payload. - Generates a self-signed EC cert, starts the real XQUIC
demo_server(HTTP/3 over QUIC/UDP on127.0.0.1:8443), then runs thequic-goclient which performs a real QUIC/TLS handshake, opens the HTTP/3 control stream (0x00 0x04 0x00) and the QPACK encoder unidirectional stream (0x02+ payload), and sends:Set Dynamic Table Capacity=64, 61×Insert "x":"y",Insert "AAAAA":"BBBBB",Set Dynamic Table Capacity=65. - Runs 2 vulnerable attempts and 2 fixed attempts with per-attempt logs, then
writes
bundle/repro/runtime_manifest.json.
- Locates the durable project cache via
- Expected evidence: vulnerable server exits
134(SIGABRT) with stderr*** buffer overflow detected ***: terminated; fixed server stays alive. Script exits0when vulnerable crashes ≥1/2 and fixed alive ≥1/2.
Evidence
bundle/repro/reproduction_steps.sh— the reproducer.bundle/repro/runtime_manifest.json— runtime evidence manifest (entrypoint_kind=tcp_peer,service_started=true,healthcheck_passed=true,target_path_reached=true, proof artifact list).bundle/logs/repro_run1.log,bundle/logs/repro_run2.log— full logs of two consecutive successful runs.bundle/logs/vuln_attempt{1,2}_server.stderr— contain*** buffer overflow detected ***: terminated.bundle/logs/vuln_attempt{1,2}_result.txt—RESULT: server CRASHED exit=134.bundle/logs/fixed_attempt{1,2}_result.txt—RESULT: server ALIVE (no crash).bundle/logs/vuln_attempt{1,2}_client.stdout—payload len 260/payload sent.
Key excerpts (run 1):
[vuln#1] server pid=22035 listening on udp/8443
[vuln#1] RESULT: server CRASHED exit=134
[vuln#2] server pid=22062 listening on udp/8443
[vuln#2] RESULT: server CRASHED exit=134
[fixed#1] server pid=22089 listening on udp/8443
[fixed#1] RESULT: server ALIVE (no crash)
[fixed#2] server pid=22112 listening on udp/8443
[fixed#2] RESULT: server ALIVE (no crash)
[*] vulnerable crashes: 2 / 2
[*] fixed alive (no crash): 2 / 2
[*] glibc _FORTIFY_SOURCE abort observed: 2 / 2
Server stderr (vulnerable): *** buffer overflow detected ***: terminated
Environment: Ubuntu 26.04 LTS, GCC 15.2.0, XQUIC v1.9.4 (commit 96155cf), Tongsuo
8.4-stable (BabaSSL) as the QUIC TLS backend, quic-go v0.60.0 client, Go 1.26.5,
glibc _FORTIFY_SOURCE enabled in the Release build. No sanitizers used for the
primary proof (sanitizer_used=false); the crash is the native product-visible abort.
Recommendations / Next Steps
- Fix: In
xqc_ring_mem_resize, change the both-truncated branch to size the original-buffer block with the old capacity:ori_sz1 = rmem->capacity - soffset_ori;. (This is the negative-control patch applied and verified here.) Audit the other resize sub-branches for the same old-vs-new-capacity confusion and add unit tests covering wrapped-ring grow scenarios. - Mitigation (deploy now): Advertise
SETTINGS_QPACK_MAX_TABLE_CAPACITY = 0to disable the QPACK dynamic table, preventing the decoder from ever creating/growing the ring memory that contains the bug. - Upgrade guidance: No patched upstream release exists at disclosure time; users should apply the one-line fix or the mitigation until an official patch is published.
- Testing: Add regression tests that fill the QPACK dynamic table until the ring
wraps, then grow via
Set Dynamic Table Capacity, asserting no OOB read / underflow (e.g., under ASAN/UBSAN and with_FORTIFY_SOURCE).
Additional Notes
- Idempotency: Confirmed — the script was run twice consecutively; both runs exited
0with identical results (vuln 2/2 crash, fixed 2/2 alive, fortify abort 2/2). - Surface match: The claim surface is
network_protocol/required_entrypoint_kind=tcp_peer. The proof drives a real QUIC/HTTP3 peer over UDP (aquic-goclient dialing the real XQUICdemo_server), exercising the real QPACK decoder encoder-stream path end-to-end. QUIC uses UDP datagrams rather than a TCP byte stream, but the peer/listener boundary and protocol exchange are real. - No sanitizers in the primary proof: The crash is glibc
_FORTIFY_SOURCEaborting the underflowedmemcpyin a normal Release build — a product-visible native crash, not an ASAN/UBSAN report. - Claimed vs observed impact: Only DoS (crash) is claimed and reproduced. The
underlying 64-byte OOB read +
size_tunderflow suggest possible heap memory-safety impact beyond DoS, but code execution was not claimed and is not demonstrated here. - Limitations: Reproduced against the
demo_serversample shipped with XQUIC; any XQUIC-based HTTP/3 server exposing the QPACK decoder to remote clients is expected to be similarly affected (default dynamic-table configuration).
REPRO-2026-00284 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.
which cmake gcc g++ make python3 jq git curl 2>/dev/null; echo "---"; ls -la /data/pruva/project-cache/ab38e70b-91c6-4571-9872-2e6f134461d6/ 2>&1/usr/bin/cmake /usr/bin/gcc /usr/bin/g++ /usr/bin/make /usr/bin/python3 /usr/bin/jq /usr/local/bin/git /usr/bin/curl
Artifacts and Evidence for REPRO-2026-00284
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix REPRO-2026-00284
FAQ: REPRO-2026-00284
How does the XRING QPACK resize attack work?
How severe is XRING (REPRO-2026-00284)?
How can I reproduce REPRO-2026-00284?
References for REPRO-2026-00284
Authoritative sources for REPRO-2026-00284 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.