BUG #19678: int16 Overflow in tsquery Phrase Distance After Stopword Removal

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: 1950233439(at)qq(dot)com
Subject: BUG #19678: int16 Overflow in tsquery Phrase Distance After Stopword Removal
Date: 2026-09-07 14:21:21
Message-ID: 19678-790bf8888cef8cb5@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19678
Logged by: Tianyu Shi
Email address: 1950233439(at)qq(dot)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.

Browse pgsql-bugs by date

  From Date Subject
Next Message Tom Lane 2026-09-07 15:08:39 Re: BUG #19653: "variable not found in subplan target list" during planning with parallel parameterized nested loop,
Previous Message PG Bug reporting form 2026-09-07 14:19:49 BUG #19677: Silent int64→int32 Truncation in timestamp_izone() Produces Wrong Timestamps