From bf7b44340f5831a200e2d445e431de88a8eb0e84 Mon Sep 17 00:00:00 2001 From: Evgen Byelozorov Date: Sun, 19 Jul 2026 17:08:28 +0200 Subject: [PATCH] fix: bounds-check Q decompression to prevent heap overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit q_decompress() sized the output buffer from the frame's declared uncompressed size but never checked that a back-reference's expansion (2 literal bytes + up to 255 copied bytes) stayed within it. A malformed or hostile frame can declare a tiny out_size yet emit opcodes that write past the allocation — a heap buffer overflow. This is remotely reachable on the server: q_read_body() passes the client-controlled `compressed` header flag straight into q_decode(), so a peer can request the decompression path with a crafted body. Read the back-reference index and length first, then reject the frame if `s + 2 + n` would exceed out_size, instead of writing past `result`. The source reads are already in bounds (r < s <= out_size). Verified with an AddressSanitizer harness: the crafted frame trips ASan before the change and is rejected cleanly after. --- q.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/q.c b/q.c index 165e22b..4554ed3 100644 --- a/q.c +++ b/q.c @@ -983,13 +983,21 @@ static int q_decompress(const uint8_t *src, int64_t src_len, uint8_t **out_buf, return -1; } int64_t r = buffer[src[d++]]; - result[s++] = result[r++]; - result[s++] = result[r++]; if (d >= src_len) { free(result); return -1; } n = src[d++]; + /* A back-reference expands to 2 literal + n copied bytes at result[s]. + * A malformed or hostile frame can declare a tiny out_size yet expand + * past it, so reject that here instead of writing past the allocation. + * (r < s <= out_size, so the source reads below stay in bounds.) */ + if (s + 2 + n > out_size) { + free(result); + return -1; + } + result[s++] = result[r++]; + result[s++] = result[r++]; for (int64_t m = 0; m < n; m++) result[s + m] = result[r + m]; } else {