agora inbox for pgsql-bugs@postgresql.org  
help / color / mirror / Atom feed
BUG #19603: Vuln47: distance_taxicab and distance_chebyshev silently return 0 instead of NaN when a cube coordin
2+ messages / 2 participants
[nested] [flat]

* BUG #19603: Vuln47: distance_taxicab and distance_chebyshev silently return 0 instead of NaN when a cube coordin
@ 2026-08-03 07:00 PG Bug reporting form <noreply@postgresql.org>
  2026-08-03 13:16 ` Re: BUG #19603: Vuln47: distance_taxicab and distance_chebyshev silently return 0 instead of NaN when a cube coordin Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  0 siblings, 1 reply; 2+ messages in thread

From: PG Bug reporting form @ 2026-08-03 07:00 UTC (permalink / raw)
  To: pgsql-bugs@lists.postgresql.org; +Cc: 1217816127@qq.com

The following bug has been logged on the website:

Bug reference:      19603
Logged by:          Yuelin Wang
Email address:      1217816127@qq.com
PostgreSQL version: 19beta2
Operating system:   Linux (Ubuntu 24.04, x86_64)
Description:        

### Summary

The static helper distance_1D() in contrib/cube/cube.c classifies two
intervals as "left of", "right of", or "intersecting" using direct floating
point comparisons. When a coordinate is NaN, every comparison evaluates to
false, so the interval falls through to the intersecting branch and the
function returns 0.0 instead of NaN. distance_taxicab and distance_chebyshev
call distance_1D per dimension and sum or max the results, so a single NaN
coordinate silently produces a finite, plausible-looking distance instead of
propagating NaN as IEEE 754 arithmetic normally would.

CWE: CWE-1339. Severity: Low.

### PoC

```sql
CREATE EXTENSION cube;
SELECT distance_chebyshev('(nan,nan)'::cube, '(1,1)'::cube);
SELECT distance_chebyshev('(5,5)'::cube, '(1,nan)'::cube);
SELECT distance_taxicab('(nan)'::cube, '(1)'::cube);
```

### Result

Real captured output from the independent verification run:

```
CREATE EXTENSION
 distance_chebyshev
--------------------
                  0
(1 row)

 distance_chebyshev
--------------------
                  4
(1 row)

 distance_taxicab
------------------
                0
(1 row)
```

### Impact

A database user who stores or queries cube values containing NaN coordinates
can get silently wrong distance results (e.g. 0 instead of NaN) from
distance_taxicab and distance_chebyshev, which can corrupt nearest-neighbor
search results, ranking, or KNN-index-backed queries that rely on these
operators.








^ permalink  raw  reply  [nested|flat] 2+ messages in thread

* Re: BUG #19603: Vuln47: distance_taxicab and distance_chebyshev silently return 0 instead of NaN when a cube coordin
  2026-08-03 07:00 BUG #19603: Vuln47: distance_taxicab and distance_chebyshev silently return 0 instead of NaN when a cube coordin PG Bug reporting form <noreply@postgresql.org>
@ 2026-08-03 13:16 ` Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  0 siblings, 0 replies; 2+ messages in thread

From: Ayush Tiwari @ 2026-08-03 13:16 UTC (permalink / raw)
  To: 1217816127@qq.com; pgsql-bugs@lists.postgresql.org

Hi,

On Mon, 3 Aug 2026 at 17:21, PG Bug reporting form <noreply@postgresql.org>
wrote:

> The following bug has been logged on the website:
>
> Bug reference:      19603
> Logged by:          Yuelin Wang
> Email address:      1217816127@qq.com
> PostgreSQL version: 19beta2
> Operating system:   Linux (Ubuntu 24.04, x86_64)
> Description:
>
> ### Summary
>
> The static helper distance_1D() in contrib/cube/cube.c classifies two
> intervals as "left of", "right of", or "intersecting" using direct floating
> point comparisons. When a coordinate is NaN, every comparison evaluates to
> false, so the interval falls through to the intersecting branch and the
> function returns 0.0 instead of NaN. distance_taxicab and
> distance_chebyshev
> call distance_1D per dimension and sum or max the results, so a single NaN
> coordinate silently produces a finite, plausible-looking distance instead
> of
> propagating NaN as IEEE 754 arithmetic normally would.
>
> CWE: CWE-1339. Severity: Low.
>
> ### PoC
>
> ```sql
> CREATE EXTENSION cube;
> SELECT distance_chebyshev('(nan,nan)'::cube, '(1,1)'::cube);
> SELECT distance_chebyshev('(5,5)'::cube, '(1,nan)'::cube);
> SELECT distance_taxicab('(nan)'::cube, '(1)'::cube);
> ```
>
> ### Result
>
> Real captured output from the independent verification run:
>
> ```
> CREATE EXTENSION
>  distance_chebyshev
> --------------------
>                   0
> (1 row)
>
>  distance_chebyshev
> --------------------
>                   4
> (1 row)
>
>  distance_taxicab
> ------------------
>                 0
> (1 row)
> ```
>
> ### Impact
>
> A database user who stores or queries cube values containing NaN
> coordinates
> can get silently wrong distance results (e.g. 0 instead of NaN) from
> distance_taxicab and distance_chebyshev, which can corrupt nearest-neighbor
> search results, ranking, or KNN-index-backed queries that rely on these
> operators.
>

Thanks for the report and repro.

It looks to me like the root is distance_1D(): with a NaN coordinate none of
its comparisons can be true, so it seems to fall through to the overlapping
case and return 0.  My first thought was to fix it there and return NaN when
any endpoint is NaN, which seems enough for taxicab and Euclidean (<->),
which
accumulate with +=.  Chebyshev needs a bit more, since its running max uses
"d > distance" and NaN > x is false, so I added an explicit NaN check there
too.

Regards,
Ayush

Attachments:

  [application/octet-stream] 0001-cube-propagate-NaN-in-taxicab-Chebyshev-and-Euclidea.patch (5.0K, ../../CAJTYsWVq0zmdkKpjpRFEeQf1AddvKeYt3pRHZ7KqPmda=joc0g@mail.gmail.com/3-0001-cube-propagate-NaN-in-taxicab-Chebyshev-and-Euclidea.patch)
  download | inline diff:
From 8c1372f61b11a1b904f220d87aa4e8b82aa85f06 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Mon, 3 Aug 2026 18:41:19 +0530
Subject: [PATCH] cube: propagate NaN in taxicab, Chebyshev and Euclidean
 distances

The static helper distance_1D() classifies the 1-D projections of two
cubes as left-of, right-of, or intersecting using plain floating-point
comparisons.  When a coordinate is NaN every comparison is false, so the
projection was treated as intersecting and the helper returned 0.
distance_taxicab(), distance_chebyshev() and cube_distance() are all
built on distance_1D(), so a single NaN coordinate silently produced a
finite, plausible-looking distance instead of propagating NaN the way
IEEE 754 arithmetic normally would.

Return NaN from distance_1D() when any endpoint is NaN.  distance_taxicab()
and cube_distance() then propagate it through their running sums, but
distance_chebyshev() keeps a running maximum with "d > distance", and
NaN > x is false, so test for NaN explicitly there as well.

Add regression tests covering the NaN cases.

Reported-by: Yuelin Wang <1217816127@qq.com>
Discussion: https://postgr.es/m/19603-7b1f783d5bbfe791@postgresql.org
---
 contrib/cube/cube.c            | 12 ++++++++--
 contrib/cube/expected/cube.out | 44 ++++++++++++++++++++++++++++++++++
 contrib/cube/sql/cube.sql      |  9 +++++++
 3 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index 28d48549b36..f1a95585781 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -1377,7 +1377,7 @@ distance_chebyshev(PG_FUNCTION_ARGS)
 	{
 		d = fabs(distance_1D(LL_COORD(a, i), UR_COORD(a, i),
 							 LL_COORD(b, i), UR_COORD(b, i)));
-		if (d > distance)
+		if (isnan(d) || d > distance)
 			distance = d;
 	}
 
@@ -1385,7 +1385,7 @@ distance_chebyshev(PG_FUNCTION_ARGS)
 	for (i = DIM(b); i < DIM(a); i++)
 	{
 		d = fabs(distance_1D(LL_COORD(a, i), UR_COORD(a, i), 0.0, 0.0));
-		if (d > distance)
+		if (isnan(d) || d > distance)
 			distance = d;
 	}
 
@@ -1511,6 +1511,14 @@ g_cube_distance(PG_FUNCTION_ARGS)
 static double
 distance_1D(double a1, double a2, double b1, double b2)
 {
+	/*
+	 * If any endpoint is NaN, all the comparisons below are false and we'd
+	 * fall through to the "intersecting" case and return 0.  Return NaN
+	 * instead, so that callers propagate it as ordinary float arithmetic does.
+	 */
+	if (isnan(a1) || isnan(a2) || isnan(b1) || isnan(b2))
+		return get_float8_nan();
+
 	/* interval (a) is entirely on the left of (b) */
 	if ((a1 <= b1) && (a2 <= b1) && (a1 <= b2) && (a2 <= b2))
 		return (Min(b1, b2) - Max(a1, a2));
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index 47787c50bd9..95b85c4f2d5 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -1440,6 +1440,50 @@ SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
                 0
 (1 row)
 
+-- NaN coordinates propagate to a NaN distance, instead of being treated
+-- as a zero-distance (overlapping) projection
+SELECT distance_chebyshev('(nan,nan)'::cube, '(1,1)'::cube);
+ distance_chebyshev 
+--------------------
+                NaN
+(1 row)
+
+SELECT distance_chebyshev('(5,5)'::cube, '(1,nan)'::cube);
+ distance_chebyshev 
+--------------------
+                NaN
+(1 row)
+
+SELECT distance_taxicab('(nan)'::cube, '(1)'::cube);
+ distance_taxicab 
+------------------
+              NaN
+(1 row)
+
+SELECT cube_distance('(nan,nan)'::cube, '(1,1)'::cube);
+ cube_distance 
+---------------
+           NaN
+(1 row)
+
+SELECT '(nan,nan)'::cube <-> '(1,1)'::cube as d_e_nan;
+ d_e_nan 
+---------
+     NaN
+(1 row)
+
+SELECT '(5,5)'::cube <=> '(1,nan)'::cube as d_c_nan;
+ d_c_nan 
+---------
+     NaN
+(1 row)
+
+SELECT '(nan)'::cube <#> '(1)'::cube as d_t_nan;
+ d_t_nan 
+---------
+     NaN
+(1 row)
+
 -- coordinate access
 SELECT cube(array[10,20,30], array[40,50,60])->1;
  ?column? 
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index eec90d21ee3..f71b62b069a 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -356,6 +356,15 @@ SELECT '(1,1)'::cube <#> '(4,5)'::cube as d_t;
 SELECT cube_distance('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
 SELECT distance_chebyshev('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
 SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
+-- NaN coordinates propagate to a NaN distance, instead of being treated
+-- as a zero-distance (overlapping) projection
+SELECT distance_chebyshev('(nan,nan)'::cube, '(1,1)'::cube);
+SELECT distance_chebyshev('(5,5)'::cube, '(1,nan)'::cube);
+SELECT distance_taxicab('(nan)'::cube, '(1)'::cube);
+SELECT cube_distance('(nan,nan)'::cube, '(1,1)'::cube);
+SELECT '(nan,nan)'::cube <-> '(1,1)'::cube as d_e_nan;
+SELECT '(5,5)'::cube <=> '(1,nan)'::cube as d_c_nan;
+SELECT '(nan)'::cube <#> '(1)'::cube as d_t_nan;
 -- coordinate access
 SELECT cube(array[10,20,30], array[40,50,60])->1;
 SELECT cube(array[40,50,60], array[10,20,30])->1;
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 2+ messages in thread


end of thread, other threads:[~2026-08-03 13:16 UTC | newest]

Thread overview: 2+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-08-03 07:00 BUG #19603: Vuln47: distance_taxicab and distance_chebyshev silently return 0 instead of NaN when a cube coordin PG Bug reporting form <noreply@postgresql.org>
2026-08-03 13:16 ` Ayush Tiwari <ayushtiwari.slg01@gmail.com>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox