{"repro_id":"REPRO-2026-00284","version":6,"title":"XRING: Alibaba XQUIC QPACK ring-buffer resize underflow","repro_type":"security","status":"published","severity":"high","description":"Novel unknown 0-day investigation in the latest available Alibaba XQUIC release. We are confident a fresh exploitable issue exists, but the vulnerable path, trigger, sink, and exact attack surface are unknown. Agents must independently discover the attack surface, identify the execution primitive, and reproduce code execution or crash-from-exploitation from scratch against the latest release. This is a discovery-first task and should not be treated as a simple replay of a prior disclosure; search broadly for any new HTTP/3, QPACK, ring-buffer, or transport parsing path that yields exploitation.","root_cause":"# RCA Report — CVE-2026-XRING (Alibaba XQUIC HTTP/3/QPACK ring buffer resize underflow)\n\n## Summary\n\nAlibaba XQUIC (all versions through v1.9.4, latest at time of disclosure) contains a\nremote, unauthenticated denial-of-service vulnerability in its HTTP/3 QPACK dynamic\ntable implementation. The bug is in `xqc_ring_mem_resize()` (`src/common/utils/ringmem/xqc_ring_mem.c`).\nWhen the QPACK dynamic-table ring buffer has wrapped (data is split across the end and\nthe beginning of the buffer — the \"both-truncated\" resize case) and the QPACK decoder\nreceives a `Set Dynamic Table Capacity` instruction that grows the table, the resize\ncode sizes the first block of the *original* buffer using the **new** capacity (`mcap`)\ninstead of the **old** capacity (`rmem->capacity`). This produces a heap out-of-bounds\nread of the old buffer followed by a `size_t` underflow (`rmem->used - ori_sz1`) into the\nthird `xqc_memcpy`, which glibc `_FORTIFY_SOURCE` aborts (`*** buffer overflow detected ***:\nterminated`). A remote, unauthenticated QUIC/HTTP3 client can trigger this with\nspec-compliant QPACK encoder-stream instructions, crashing the server process.\n\n## Impact\n\n- **Package/component affected:** `xquic` — `src/common/utils/ringmem/xqc_ring_mem.c`\n  (`xqc_ring_mem_resize`), reached via `xqc_dtable_set_capacity` (`src/http3/qpack/dtable/xqc_dtable.c`)\n  ← `xqc_decoder_set_dtable_cap` (`src/http3/qpack/xqc_decoder.c`) when the decoder\n  processes a `Set Dynamic Table Capacity` instruction on the QPACK encoder stream.\n- **Affected versions:** All published XQUIC versions through v1.9.4. The disclosure\n  states no upstream patch is available. Reproduced here against XQUIC v1.9.4\n  (commit `96155cf`, tag `v1.9.4`).\n- **Risk level / consequences:** High. Remote, unauthenticated, deterministic process\n  crash (DoS) of any XQUIC HTTP/3 endpoint that allows the QPACK dynamic table\n  (default configuration). The OOB read + underflowed memcpy also imply potential heap\n  memory-safety impact beyond a simple crash, although only the crash (DoS) is claimed\n  and reproduced.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Remote unauthenticated crash (DoS); the blog\n  additionally describes a 64-byte heap OOB read and a `size_t` underflow into `memcpy`.\n  No code execution is claimed.\n- **Reproduced impact from this run:** Deterministic remote crash of the real XQUIC\n  `demo_server` (HTTP/3 over QUIC/UDP) via a real `quic-go` client sending spec-compliant\n  QPACK encoder-stream instructions. Observed: server process terminated with exit code\n  `134` (SIGABRT) and glibc message `*** buffer overflow detected ***: terminated`\n  (the `_FORTIFY_SOURCE` abort of the underflowed `memcpy`). 2/2 vulnerable attempts\n  crashed; 2/2 negative-control (one-line-fix) attempts stayed alive.\n- **Parity:** `full` for the claimed DoS (remote unauthenticated crash) via the claimed\n  network protocol surface.\n- **Not demonstrated:** Code execution / arbitrary heap write. Only the crash (DoS) and\n  the underlying OOB-read + underflow symptom are demonstrated, matching the claim.\n\n## Root Cause\n\nIn `xqc_ring_mem_resize(xqc_ring_mem_t *rmem, size_t cap)`, when `cap > rmem->capacity`\nthe function allocates a new power-of-two buffer `mcap = xqc_pow2_upper(cap)` and copies\nthe used bytes from the old buffer into the new one. The copy has four shape cases. In\nthe \"both-truncated\" case (data wraps in **both** the old and the new buffer,\ni.e. `soffset_ori >= eoffset_ori` and `soffset_new >= eoffset_new`):\n\n```c\nsize_t new_sz1 = mcap - soffset_new;    /* size of first block in new buffer  */\nsize_t ori_sz1 = mcap - soffset_ori;    /* BUG: uses NEW cap; should be rmem->capacity */\n```\n\n`soffset_ori` is computed with the **old** mask (`rmem->sidx & rmem->mask`), so it is an\noffset inside the old buffer of size `rmem->capacity`. The first block of the old buffer\nthat must be copied therefore has size `rmem->capacity - soffset_ori`, **not**\n`mcap - soffset_ori`. Because `mcap > rmem->capacity`, `ori_sz1` is too large. In the\n\"first block of new buffer is smaller than original buffer\" sub-branch this causes:\n\n1. `xqc_memcpy(buf, rmem->buf + soffset_ori + new_sz1, ori_sz1 - new_sz1)` — reads\n   `ori_sz1 - new_sz1` bytes starting past the end of the old buffer (64-byte OOB read\n   in the reproduced scenario);\n2. `xqc_memcpy(buf + ori_sz1 - new_sz1, rmem->buf, rmem->used - ori_sz1)` —\n   `rmem->used - ori_sz1` underflows (unsigned), producing a huge `size_t`, so glibc's\n   `__memcpy_chk` (`_FORTIFY_SOURCE`) aborts the process.\n\n**Concrete reproduced arithmetic** (v1.9.4, payload from FoxIO `xring-poc`):\ninitial `Set Dynamic Table Capacity = 64` → `rmem->capacity = 64` (mask 63). 61×\n`Insert \"x\":\"y\"` then `Insert \"AAAAA\":\"BBBBB\"` evict entries and wrap the 64-byte ring,\nleaving `used = 10`, `sidx = 122` (`soffset_ori = 122 & 63 = 58`), `eidx = 132`\n(`eoffset_ori = 132 & 63 = 4`): old buffer is truncated. `Set Dynamic Table Capacity = 65`\n→ `xqc_ring_mem_resize(rmem, 65)`: `mcap = xqc_pow2_upper(65) = 128`, `soffset_new = 122\n& 127 = 122`, `eoffset_new = 132 & 127 = 4`: new buffer also truncated → \"both-truncated\"\nbranch. `new_sz1 = 128 - 122 = 6`; buggy `ori_sz1 = 128 - 58 = 70` (correct: `64 - 58 =\n6`). `new_sz1 < ori_sz1` → reads `70 - 6 = 64` bytes OOB, then `used(10) - 70` underflows\n→ `memcpy_chk` abort (exit 134).\n\n**Fix (no upstream patch exists; applied here as the negative control):**\n```c\n- size_t ori_sz1 = mcap - soffset_ori;    /* size of first block in original buffer */\n+ size_t ori_sz1 = rmem->capacity - soffset_ori;    /* size of first block in original buffer */\n```\nWith this one-line change the identical malicious payload no longer crashes the server\n(verified: 2/2 fixed attempts stay alive).\n\n## Reproduction Steps\n\n1. Script: `bundle/repro/reproduction_steps.sh` (self-contained; reuses the durable\n   project cache when present, otherwise clones/builds XQUIC v1.9.4 + Tongsuo 8.4-stable\n   + a `quic-go` client from source).\n2. What it does:\n   - Locates the durable project cache via `bundle/project_cache_context.json`; uses\n     prebuilt `demo_server_vuln` (XQUIC v1.9.4, `-Werror` removed, vulnerable line\n     intact) and `demo_server_fixed` (same tree with the one-line `ori_sz1` fix) plus a\n     prebuilt `quic-go` client that reproduces the FoxIO XRING payload.\n   - Generates a self-signed EC cert, starts the real XQUIC `demo_server` (HTTP/3 over\n     QUIC/UDP on `127.0.0.1:8443`), then runs the `quic-go` client which performs a real\n     QUIC/TLS handshake, opens the HTTP/3 control stream (`0x00 0x04 0x00`) and the QPACK\n     encoder unidirectional stream (`0x02` + payload), and sends: `Set Dynamic Table\n     Capacity=64`, 61× `Insert \"x\":\"y\"`, `Insert \"AAAAA\":\"BBBBB\"`,\n     `Set Dynamic Table Capacity=65`.\n   - Runs **2 vulnerable** attempts and **2 fixed** attempts with per-attempt logs, then\n     writes `bundle/repro/runtime_manifest.json`.\n3. Expected evidence: vulnerable server exits `134` (SIGABRT) with stderr\n   `*** buffer overflow detected ***: terminated`; fixed server stays alive. Script\n   exits `0` when vulnerable crashes ≥1/2 and fixed alive ≥1/2.\n\n## Evidence\n\n- `bundle/repro/reproduction_steps.sh` — the reproducer.\n- `bundle/repro/runtime_manifest.json` — runtime evidence manifest (`entrypoint_kind=tcp_peer`,\n  `service_started=true`, `healthcheck_passed=true`, `target_path_reached=true`,\n  proof artifact list).\n- `bundle/logs/repro_run1.log`, `bundle/logs/repro_run2.log` — full logs of two\n  consecutive successful runs.\n- `bundle/logs/vuln_attempt{1,2}_server.stderr` — contain\n  `*** buffer overflow detected ***: terminated`.\n- `bundle/logs/vuln_attempt{1,2}_result.txt` — `RESULT: server CRASHED exit=134`.\n- `bundle/logs/fixed_attempt{1,2}_result.txt` — `RESULT: server ALIVE (no crash)`.\n- `bundle/logs/vuln_attempt{1,2}_client.stdout` — `payload len 260` / `payload sent`.\n\nKey excerpts (run 1):\n```\n[vuln#1] server pid=22035 listening on udp/8443\n[vuln#1] RESULT: server CRASHED exit=134\n[vuln#2] server pid=22062 listening on udp/8443\n[vuln#2] RESULT: server CRASHED exit=134\n[fixed#1] server pid=22089 listening on udp/8443\n[fixed#1] RESULT: server ALIVE (no crash)\n[fixed#2] server pid=22112 listening on udp/8443\n[fixed#2] RESULT: server ALIVE (no crash)\n[*] vulnerable crashes: 2 / 2\n[*] fixed alive (no crash): 2 / 2\n[*] glibc _FORTIFY_SOURCE abort observed: 2 / 2\n```\nServer stderr (vulnerable): `*** buffer overflow detected ***: terminated`\n\nEnvironment: Ubuntu 26.04 LTS, GCC 15.2.0, XQUIC v1.9.4 (commit `96155cf`), Tongsuo\n8.4-stable (BabaSSL) as the QUIC TLS backend, `quic-go` v0.60.0 client, Go 1.26.5,\nglibc `_FORTIFY_SOURCE` enabled in the Release build. No sanitizers used for the\nprimary proof (`sanitizer_used=false`); the crash is the native product-visible abort.\n\n## Recommendations / Next Steps\n\n- **Fix:** In `xqc_ring_mem_resize`, change the both-truncated branch to size the\n  original-buffer block with the old capacity:\n  `ori_sz1 = rmem->capacity - soffset_ori;`. (This is the negative-control patch\n  applied and verified here.) Audit the other resize sub-branches for the same\n  old-vs-new-capacity confusion and add unit tests covering wrapped-ring grow scenarios.\n- **Mitigation (deploy now):** Advertise `SETTINGS_QPACK_MAX_TABLE_CAPACITY = 0` to\n  disable the QPACK dynamic table, preventing the decoder from ever creating/growing the\n  ring memory that contains the bug.\n- **Upgrade guidance:** No patched upstream release exists at disclosure time; users\n  should apply the one-line fix or the mitigation until an official patch is published.\n- **Testing:** Add regression tests that fill the QPACK dynamic table until the ring\n  wraps, then grow via `Set Dynamic Table Capacity`, asserting no OOB read / underflow\n  (e.g., under ASAN/UBSAN and with `_FORTIFY_SOURCE`).\n\n## Additional Notes\n\n- **Idempotency:** Confirmed — the script was run twice consecutively; both runs exited\n  `0` with identical results (vuln 2/2 crash, fixed 2/2 alive, fortify abort 2/2).\n- **Surface match:** The claim surface is `network_protocol` /\n  `required_entrypoint_kind=tcp_peer`. The proof drives a real QUIC/HTTP3 peer over UDP\n  (a `quic-go` client dialing the real XQUIC `demo_server`), exercising the real QPACK\n  decoder encoder-stream path end-to-end. QUIC uses UDP datagrams rather than a TCP\n  byte stream, but the peer/listener boundary and protocol exchange are real.\n- **No sanitizers in the primary proof:** The crash is glibc `_FORTIFY_SOURCE` aborting\n  the underflowed `memcpy` in a normal Release build — a product-visible native crash,\n  not an ASAN/UBSAN report.\n- **Claimed vs observed impact:** Only DoS (crash) is claimed and reproduced. The\n  underlying 64-byte OOB read + `size_t` underflow suggest possible heap memory-safety\n  impact beyond DoS, but code execution was not claimed and is not demonstrated here.\n- **Limitations:** Reproduced against the `demo_server` sample shipped with XQUIC; any\n  XQUIC-based HTTP/3 server exposing the QPACK decoder to remote clients is expected to\n  be similarly affected (default dynamic-table configuration).\n","source_url":"https://github.com/alibaba/xquic","package":{"name":"Alibaba XQUIC","ecosystem":"github","affected_versions":"All XQUIC versions through v1.9.4 (since first public release Jan 2022)"},"reproduced_at":"2026-07-11T13:15:02.325974+00:00","duration_secs":1253.0,"tool_calls":242,"handoffs":2,"total_cost_usd":3.56987329,"agent_costs":{"hypothesis_generator":0.0157786,"judge":0.01570275,"repro":1.6519008899999996,"support":0.11900985,"vuln_variant":1.7674811999999998},"cost_breakdown":{"hypothesis_generator":{"accounts/fireworks/models/glm-5p2":0.0157786},"judge":{"gpt-5.4-mini":0.01570275},"repro":{"accounts/fireworks/routers/glm-5p2-fast":1.6519008899999996},"support":{"accounts/fireworks/routers/glm-5p2-fast":0.11900985},"vuln_variant":{"accounts/fireworks/routers/glm-5p2-fast":1.7674811999999998}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-11T13:15:39.322595+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":14719,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":11166,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":16862,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":11525,"category":"analysis"},{"path":"bundle/artifact_promotion_manifest.json","filename":"artifact_promotion_manifest.json","size":17112,"category":"other"},{"path":"bundle/artifact_promotion_report.json","filename":"artifact_promotion_report.json","size":17130,"category":"other"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":2664,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":4735,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1134,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":1216,"category":"other"},{"path":"bundle/logs/vuln_attempt1_server.stderr","filename":"vuln_attempt1_server.stderr","size":45,"category":"other"},{"path":"bundle/logs/repro_run1.log","filename":"repro_run1.log","size":1977,"category":"log"},{"path":"bundle/logs/vuln_attempt1_client.stdout","filename":"vuln_attempt1_client.stdout","size":29,"category":"other"},{"path":"bundle/logs/vuln_attempt1_result.txt","filename":"vuln_attempt1_result.txt","size":89,"category":"other"},{"path":"bundle/logs/fixed_attempt1_server.stderr","filename":"fixed_attempt1_server.stderr","size":0,"category":"other"},{"path":"bundle/logs/fixed_attempt1_client.stdout","filename":"fixed_attempt1_client.stdout","size":29,"category":"other"},{"path":"bundle/logs/fixed_attempt1_result.txt","filename":"fixed_attempt1_result.txt","size":91,"category":"other"},{"path":"bundle/logs/vuln_attempt2_server.stderr","filename":"vuln_attempt2_server.stderr","size":45,"category":"other"},{"path":"bundle/logs/vuln_attempt2_client.stdout","filename":"vuln_attempt2_client.stdout","size":29,"category":"other"},{"path":"bundle/logs/vuln_attempt2_result.txt","filename":"vuln_attempt2_result.txt","size":89,"category":"other"},{"path":"bundle/logs/fixed_attempt2_server.stderr","filename":"fixed_attempt2_server.stderr","size":0,"category":"other"},{"path":"bundle/logs/fixed_attempt2_client.stdout","filename":"fixed_attempt2_client.stdout","size":29,"category":"other"},{"path":"bundle/logs/fixed_attempt2_result.txt","filename":"fixed_attempt2_result.txt","size":91,"category":"other"},{"path":"bundle/logs/vuln_variant/variant_matrix_summary.txt","filename":"variant_matrix_summary.txt","size":394,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln_P1_server.stderr","filename":"vuln_P1_server.stderr","size":9172,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln_P3_server.stderr","filename":"vuln_P3_server.stderr","size":9173,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_P1_server.stderr","filename":"fixed_P1_server.stderr","size":0,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":4479,"category":"other"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":5148,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":9194,"category":"documentation"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":2223,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln_P1_client.stdout","filename":"vuln_P1_client.stdout","size":57,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln_P1_result.txt","filename":"vuln_P1_result.txt","size":88,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_P1_client.stdout","filename":"fixed_P1_client.stdout","size":57,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_P1_result.txt","filename":"fixed_P1_result.txt","size":90,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln_P2_server.stderr","filename":"vuln_P2_server.stderr","size":9172,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln_P2_client.stdout","filename":"vuln_P2_client.stdout","size":57,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln_P2_result.txt","filename":"vuln_P2_result.txt","size":88,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_P2_server.stderr","filename":"fixed_P2_server.stderr","size":0,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_P2_client.stdout","filename":"fixed_P2_client.stdout","size":57,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_P2_result.txt","filename":"fixed_P2_result.txt","size":90,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln_P3_client.stdout","filename":"vuln_P3_client.stdout","size":59,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln_P3_result.txt","filename":"vuln_P3_result.txt","size":88,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_P3_server.stderr","filename":"fixed_P3_server.stderr","size":0,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_P3_client.stdout","filename":"fixed_P3_client.stdout","size":59,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_P3_result.txt","filename":"fixed_P3_result.txt","size":90,"category":"other"}]}