coprocessor UNCOMPRESS trusts attacker-controlled declared length — remote memory-exhaustion DoS (never fixed since #9141)
Bug Report
What version of TiKV are you using?
master 67fccdb16f (also present in the deployed nightly binary); introduced by #9141 (2021), never fixedWhat operating system and CPU are you using?
Verified on darwin/arm64 (locally built binaries) and in the deployed nightly binarySteps to reproduce
- Craft one varbinary value in MySQL COMPRESS format with a large declared-length header:
[4-byte LE length][zlib stream](e.g. length = 0xffffffff). - 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 withScalarFuncSig::Uncompress. - 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.rs — Vec::with_capacity(len) with no cap, while sibling COMPRESS uses (input.len()+5).min(isize::MAX). Remote memory-exhaustion DoS.
Root cause:
// 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.
Source: tikv/tikv