Hi,

The selectivity function _int_matchsel() in contrib/intarray
assumes that the right-hand argument is a valid query_int datum.
If a malformed or binary-incompatible value is passed (for example,
via an implicit cast from a user-defined type created WITHOUT FUNCTION),
the function may dereference an invalid pointer and crash.

Specifically, _int_matchsel() calls DatumGetQueryTypeP() and
immediately accesses the resulting structure without validating
the datum size or structure integrity.

This patch adds a minimal size check before dereferencing the
query pointer. If the datum is not a valid query_int value,
an ERROR with ERRCODE_DATATYPE_MISMATCH is raised instead of
causing a backend crash.

A regression test is included to demonstrate the issue using a
fake type that is implicitly cast to query_int.

Patch attached.

Regards,
Eugeny Goryachev

>From f7f108be286872e7e380e0f11b636d03407758c4 Mon Sep 17 00:00:00 2001
From: Eugeny Goryachev <[email protected]>
Date: Wed, 4 Mar 2026 12:22:33 +0300
Subject: [PATCH] Fix intarray segfault

---
 contrib/intarray/Makefile                     |  4 +-
 contrib/intarray/_int_selfuncs.c              |  5 +++
 contrib/intarray/expected/fake_query_type.out | 38 ++++++++++++++++
 contrib/intarray/sql/fake_query_type.sql      | 43 +++++++++++++++++++
 4 files changed, 89 insertions(+), 1 deletion(-)
 create mode 100644 contrib/intarray/expected/fake_query_type.out
 create mode 100644 contrib/intarray/sql/fake_query_type.sql

diff --git a/contrib/intarray/Makefile b/contrib/intarray/Makefile
index 3817c1669ab..c064c091047 100644
--- a/contrib/intarray/Makefile
+++ b/contrib/intarray/Makefile
@@ -17,7 +17,9 @@ DATA = intarray--1.4--1.5.sql intarray--1.3--1.4.sql
intarray--1.2--1.3.sql \
    intarray--1.0--1.1.sql
 PGFILEDESC = "intarray - functions and operators for arrays of integers"

-REGRESS = _int
+REGRESS = \
+   _int \
+   fake_query_type

 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/contrib/intarray/_int_selfuncs.c
b/contrib/intarray/_int_selfuncs.c
index f75da3a09d2..26e715cfce6 100644
--- a/contrib/intarray/_int_selfuncs.c
+++ b/contrib/intarray/_int_selfuncs.c
@@ -186,6 +186,11 @@ _int_matchsel(PG_FUNCTION_ARGS)

    query = DatumGetQueryTypeP(((Const *) other)->constvalue);

+   if (VARSIZE(query) < HDRSIZEQT)
+       ereport(ERROR,
+           (errcode(ERRCODE_DATATYPE_MISMATCH),
+            errmsg("argument must be of type querytype")));
+
    /* Empty query matches nothing */
    if (query->size == 0)
    {
diff --git a/contrib/intarray/expected/fake_query_type.out
b/contrib/intarray/expected/fake_query_type.out
new file mode 100644
index 00000000000..c3cc64eb2dd
--- /dev/null
+++ b/contrib/intarray/expected/fake_query_type.out
@@ -0,0 +1,38 @@
+-- test for missing type validation in _int_matchsel
+CREATE FUNCTION fake_query_int_in(cstring)
+RETURNS fake_query_int
+AS 'textin'
+LANGUAGE internal IMMUTABLE STRICT;
+NOTICE:  type "fake_query_int" is not yet defined
+DETAIL:  Creating a shell type definition.
+CREATE FUNCTION fake_query_int_out(fake_query_int)
+RETURNS cstring
+AS 'textout'
+LANGUAGE internal IMMUTABLE STRICT;
+NOTICE:  argument type fake_query_int is only a shell
+CREATE TYPE fake_query_int (
+    INPUT = fake_query_int_in,
+    OUTPUT = fake_query_int_out,
+    LIKE = text
+);
+CREATE CAST (fake_query_int AS query_int)
+WITHOUT FUNCTION
+AS IMPLICIT;
+CREATE OR REPLACE FUNCTION fake_query_int_match(integer[], fake_query_int)
+RETURNS boolean
+AS $$
+    SELECT $1 @@ $2::query_int;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR @@ (
+    LEFTARG = integer[],
+    RIGHTARG = fake_query_int,
+    PROCEDURE = fake_query_int_match,
+    RESTRICT = _int_matchsel
+);
+CREATE TABLE test_arr (
+    id serial,
+    arr integer[]
+);
+SELECT * FROM test_arr
+WHERE arr @@ '1&2'::fake_query_int;
+ERROR:  argument must be of type querytype
diff --git a/contrib/intarray/sql/fake_query_type.sql
b/contrib/intarray/sql/fake_query_type.sql
new file mode 100644
index 00000000000..a36c6eee1d4
--- /dev/null
+++ b/contrib/intarray/sql/fake_query_type.sql
@@ -0,0 +1,43 @@
+-- test for missing type validation in _int_matchsel
+
+CREATE FUNCTION fake_query_int_in(cstring)
+RETURNS fake_query_int
+AS 'textin'
+LANGUAGE internal IMMUTABLE STRICT;
+
+CREATE FUNCTION fake_query_int_out(fake_query_int)
+RETURNS cstring
+AS 'textout'
+LANGUAGE internal IMMUTABLE STRICT;
+
+CREATE TYPE fake_query_int (
+    INPUT = fake_query_int_in,
+    OUTPUT = fake_query_int_out,
+    LIKE = text
+);
+
+CREATE CAST (fake_query_int AS query_int)
+WITHOUT FUNCTION
+AS IMPLICIT;
+
+CREATE OR REPLACE FUNCTION fake_query_int_match(integer[], fake_query_int)
+RETURNS boolean
+AS $$
+    SELECT $1 @@ $2::query_int;
+$$ LANGUAGE sql IMMUTABLE;
+
+CREATE OPERATOR @@ (
+    LEFTARG = integer[],
+    RIGHTARG = fake_query_int,
+    PROCEDURE = fake_query_int_match,
+    RESTRICT = _int_matchsel
+);
+
+
+CREATE TABLE test_arr (
+    id serial,
+    arr integer[]
+);
+
+SELECT * FROM test_arr
+WHERE arr @@ '1&2'::fake_query_int;
-- 
2.42.4

Reply via email to