From 16b9de5907ce02017479c504888acc98cb38802a Mon Sep 17 00:00:00 2001 From: fox0430 Date: Sun, 5 Jul 2026 21:00:03 +0900 Subject: [PATCH] fix: add recursion depth limit to parseTsQueryNode (1000) to prevent stack overflow --- async_postgres/pg_types/decoding.nim | 21 +++++++++++++-------- tests/test_protocol_fuzz.nim | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/async_postgres/pg_types/decoding.nim b/async_postgres/pg_types/decoding.nim index 72978a4..8e8e2af 100644 --- a/async_postgres/pg_types/decoding.nim +++ b/async_postgres/pg_types/decoding.nim @@ -653,7 +653,12 @@ proc decodeBinaryTsVector*(data: openArray[byte]): string = parts[i] = part parts.join(" ") -proc parseTsQueryNode(data: openArray[byte], pos: var int): string = +proc parseTsQueryNode(data: openArray[byte], pos: var int, depth: int = 0): string = + const maxDepth = 1000 + if depth >= maxDepth: + raise newException( + PgTypeError, "tsquery binary: nesting depth exceeds limit (" & $maxDepth & ")" + ) if pos >= data.len: raise newException(PgTypeError, "tsquery binary truncated") let tokenType = data[pos] @@ -698,23 +703,23 @@ proc parseTsQueryNode(data: openArray[byte], pos: var int): string = inc pos case op of 1: # NOT - let arg = parseTsQueryNode(data, pos) + let arg = parseTsQueryNode(data, pos, depth + 1) "!" & arg of 2: # AND - let left = parseTsQueryNode(data, pos) - let right = parseTsQueryNode(data, pos) + let left = parseTsQueryNode(data, pos, depth + 1) + let right = parseTsQueryNode(data, pos, depth + 1) left & " & " & right of 3: # OR - let left = parseTsQueryNode(data, pos) - let right = parseTsQueryNode(data, pos) + let left = parseTsQueryNode(data, pos, depth + 1) + let right = parseTsQueryNode(data, pos, depth + 1) "( " & left & " | " & right & " )" of 4: # PHRASE if pos + 1 >= data.len: raise newException(PgTypeError, "tsquery PHRASE distance truncated") let distance = int(fromBE16(data.toOpenArray(pos, pos + 1))) pos += 2 - let left = parseTsQueryNode(data, pos) - let right = parseTsQueryNode(data, pos) + let left = parseTsQueryNode(data, pos, depth + 1) + let right = parseTsQueryNode(data, pos, depth + 1) if distance == 1: left & " <-> " & right else: diff --git a/tests/test_protocol_fuzz.nim b/tests/test_protocol_fuzz.nim index f1f86d4..4e2352d 100644 --- a/tests/test_protocol_fuzz.nim +++ b/tests/test_protocol_fuzz.nim @@ -579,6 +579,22 @@ suite "Binary type decoders: malformed input": expectTypeError: discard decodeBinaryTsQuery(data) + test "decodeBinaryTsQuery nesting depth limit": + # Build a chain of 1001 NOT operators (depth >= 1000) wrapping an operand. + # Each NOT = [2, 1], operand = [1, 0, 0, 'x', 0]. + const nNot = 1001 + const operand = @[byte 1, 0, 0, byte('x'), 0] + var data = newSeq[byte](4 + nNot * 2 + operand.len) + data[0 .. 3] = toBE32((nNot + 1).int32) + var pos = 4 + for _ in 0 ..< nNot: + data[pos] = 2 + data[pos + 1] = 1 + pos += 2 + data[pos ..^ 1] = operand + expectTypeError: + discard decodeBinaryTsQuery(data) + # Seeded random fuzz on binary type decoders suite "Binary type decoders: seeded random fuzz":