BUG #19678: int16 Overflow in tsquery Phrase Distance After Stopword Removal
The following bug has been logged on the website:
Bug reference: 19678
Logged by: Tianyu Shi
Email address: 1950233439@qq.com
PostgreSQL version: 19beta3
Operating system: Ubuntu22.04
Description:
### Summary
In `clean_stopword_intree()` (`src/backend/utils/adt/tsquery_cleanup.c`,
line 344), when a stopword is removed from between two `OP_PHRASE`
operators, the child operator's accumulated distance is added back to the
surviving parent via `node->valnode->qoperator.distance += lradd + rladd;`.
Because `qoperator.distance` is `int16`, the addition overflows silently
when the combined distances exceed INT16_MAX (32767). With two operators
each at the maximum valid input distance of 16384, the sum 32768 wraps to
−32768, producing a tsquery with a negative phrase distance. The corrupted
tsquery causes `TS_phrase_execute()` to emit silent false negatives —
documents that genuinely satisfy the phrase query are not returned — which
can subvert keyword-based security filters or content-gating logic without
raising any error.
### PoC
Reproducible by any user with the default PUBLIC privilege to call
`to_tsquery()`; no superuser or special role required.
```sql
-- Connect to any database
-- ./build/bin/psql -h ./build/run -p 55433 -U postgres postgres
-- Step 1: Observe the corrupted tsquery (distance becomes negative due to
int16 overflow)
SELECT to_tsquery('english', 'cat <16384> the <16384> dog') AS
corrupted_query;
-- Expected: 'cat' <32768> 'dog' (or a valid large distance)
-- Actual: 'cat' <-32768> 'dog' (int16 overflow: 16384+16384=32768 wraps
to -32768)
-- Step 2: Confirm false negative — the document contains both words but the
match fails
SELECT
to_tsvector('english', 'cat the dog')
@@
to_tsquery('english', 'cat <16384> the <16384> dog') AS matches;
-- Expected: t
-- Actual: f (false negative caused by corrupted negative distance)
-- Step 3: Sanity check — small positive distance still works
SELECT
to_tsvector('english', 'cat the dog')
@@
to_tsquery('english', 'cat <2> dog') AS sanity;
-- Expected and actual: t
```
### Result
`to_tsquery('english', 'cat <16384> the <16384> dog')` returns a tsquery
with a negative phrase distance instead of the mathematically correct
positive value:
```
corrupted_query
----------------------
'cat' <-32768> 'dog'
```
The corrupted distance causes a false negative in phrase matching:
```
matches_large_distance
------------------------
f
```
where `t` is expected (both words are present in the document). The overflow
is confirmed by extracting the distance directly:
```
actual_distance | mathematically_correct_distance | analysis
-----------------+---------------------------------+----------------------------------------------
-32768 | 32768 | BUG: distance is
negative, expected positive.
| | int16 overflow
confirmed.
```
No error or warning is emitted; the corrupted tsquery is silently accepted
and stored as a valid datum, making the failure invisible to callers.