#20057·tikv

coprocessor UNCOMPRESS trusts attacker-controlled declared length — remote memory-exhaustion DoS (never fixed since #9141)

Author: wjhuang2016Created Sep 1, 2026Updated Sep 1, 2026
Labelstype/bugseverity/criticalmay-affects-7.5may-affects-8.1impact/oommay-affects-8.5AI-Bug-Finder

Bug Report

What version of TiKV are you using?

bash
master 67fccdb16f (also present in the deployed nightly binary); introduced by #9141 (2021), never fixed

What operating system and CPU are you using?

bash
Verified on darwin/arm64 (locally built binaries) and in the deployed nightly binary

Steps to reproduce

  1. Craft one varbinary value in MySQL COMPRESS format with a large declared-length header: [4-byte LE length][zlib stream] (e.g. length = 0xffffffff).
  2. Issue a query that evaluates UNCOMPRESS() on it inside TiKV — via SQL pushdown (WHERE uncompress(b) IS NOT NULL) on released TiDB versions, or via a direct coprocessor DAG request with ScalarFuncSig::Uncompress.
  3. Observe memory reservation before decoding.

Capacity probe (real allocation, global allocator): 0x7fffffff → immediate 2 GiB reservation; 0xffffffff → 4 GiB. A batch scan over crafted rows exhausts coprocessor/TiKV memory.

What did you expect?

Memory usage bounded by a sane cap, mirroring COMPRESS.

What did happened?

UNCOMPRESS trusts the attacker-controlled declared length and reserves it before decoding: components/tidb_query_expr/src/impl_encryption.rsVec::with_capacity(len) with no cap, while sibling COMPRESS uses (input.len()+5).min(isize::MAX). Remote memory-exhaustion DoS.

Root cause:

rust
// COMPRESS (line ~84): capacity capped
let mut vec = Vec::with_capacity((input.len() + 5).min(isize::MAX as usize));

// UNCOMPRESS (line ~119): capacity from the input header, NO cap
let len = LittleEndian::read_u32(&input[0..4]) as usize;
let mut d = ZlibDecoder::new(&input[4..]);
let mut vec = Vec::with_capacity(len);

Reachability: all released TiDB before #70199 pushed UNCOMPRESS down; TiDB #70199 fixed TiDB-side evaluation and stopped pushdown in new versions, but the TiKV-side code was never fixed — direct coprocessor clients still reach it, and clusters running DAGs compiled before the fix still hit TiKV.

Production reachability: high — a single crafted row suffices; SQL-level or coprocessor-level access is enough. Consequence: TiKV memory exhaustion → node OOM/restart, availability impact on the whole cluster.

Suggested fix: cap the declared length (reject values above a memory bound such as max_allowed_packet), mirroring COMPRESS and the TiDB #70199 limit; keep the guard for direct coprocessor clients.