]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
IS JSON/JSON(): Protect against expressions uncoercible to text
authorÁlvaro Herrera <alvherre@kurilemu.de>
Thu, 11 Jun 2026 14:17:58 +0000 (16:17 +0200)
committerÁlvaro Herrera <alvherre@kurilemu.de>
Thu, 11 Jun 2026 14:17:58 +0000 (16:17 +0200)
transformJsonParseArg() was not careful enough on generation of
transformed expressions when starting from expressions that are not
coercible to text but are in the string type category: it failed to
verify that coerce_to_target_type() succeeds, and returned a NULL
pointer.  This leads to a later NULL dereference and crash at executor
time.

This escaped noticed because it cannot happen for built-in types, all of
which have casts to text.  Only user-created types are potentially
problematic.

Fix by raising an error when a cast to text doesn't exist.

This mistake came in with commit 6ee30209a6f1.

Author: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Reported-by: Chi Zhang <798604270@qq.com>
Reviewed-by: Srinath Reddy Sadipiralla <srinath2133@gmail.com>
Backpatch-through: 16
Discussion: https://postgr.es/m/19491-7aafc221ec63f288@postgresql.org

src/backend/nodes/makefuncs.c
src/backend/parser/parse_expr.c
src/test/regress/expected/sqljson.out
src/test/regress/sql/sqljson.sql

index 6ed16254c93e3d9fd5a75c0eb1c7dea923c4b257..c131cac408bdfbbfe4d03f640a08b8468c23eaae 100644 (file)
@@ -953,6 +953,8 @@ makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType item_type,
 {
        JsonIsPredicate *n = makeNode(JsonIsPredicate);
 
+       Assert(expr != NULL);
+
        n->expr = expr;
        n->format = format;
        n->item_type = item_type;
index 26e862ce107eaa429b05fcd4dfa65e94eea227fa..0c0376b1cb43f7eae6c382a5bfd6400227f1e81c 100644 (file)
@@ -3927,10 +3927,20 @@ transformJsonParseArg(ParseState *pstate, Node *jsexpr, JsonFormat *format,
 
                if (*exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING)
                {
+                       int                     location = exprLocation(expr);
+
                        expr = coerce_to_target_type(pstate, (Node *) expr, *exprtype,
                                                                                 TEXTOID, -1,
                                                                                 COERCION_IMPLICIT,
                                                                                 COERCE_IMPLICIT_CAST, -1);
+                       if (expr == NULL)
+                               ereport(ERROR,
+                                               errcode(ERRCODE_CANNOT_COERCE),
+                                               errmsg("cannot cast type %s to %s",
+                                                          format_type_be(*exprtype),
+                                                          format_type_be(TEXTOID)),
+                                               parser_errposition(pstate, location));
+
                        *exprtype = TEXTOID;
                }
 
index 7a5a6325229607063688aa2eae8469ae0cd355f5..4808b4575ede1a2f0a885a9b890887dce87b6132 100644 (file)
@@ -798,6 +798,37 @@ SELECT NULL::bytea IS JSON;
 
 SELECT NULL::int IS JSON;
 ERROR:  cannot use type integer in IS JSON predicate
+-- A user-defined string-category type with no implicit cast to text must
+-- produce a clean error rather than crash for IS JSON input (per bug #19491).
+CREATE FUNCTION sqljson_mystr_in(cstring) RETURNS sqljson_mystr
+       AS 'textin' LANGUAGE internal IMMUTABLE STRICT;
+NOTICE:  type "sqljson_mystr" is not yet defined
+DETAIL:  Creating a shell type definition.
+CREATE FUNCTION sqljson_mystr_out(sqljson_mystr) RETURNS cstring
+       AS 'textout' LANGUAGE internal IMMUTABLE STRICT;
+NOTICE:  argument type sqljson_mystr is only a shell
+CREATE TYPE sqljson_mystr (
+       INPUT = sqljson_mystr_in,
+       OUTPUT = sqljson_mystr_out,
+       LIKE = text,
+       CATEGORY = 'S'
+);
+SELECT '{"a":1}'::sqljson_mystr IS JSON;                -- error
+ERROR:  cannot cast type sqljson_mystr to text
+LINE 1: SELECT '{"a":1}'::sqljson_mystr IS JSON;
+               ^
+-- An implicit cast to text lets the same query work normally.
+CREATE CAST (sqljson_mystr AS text) WITHOUT FUNCTION AS IMPLICIT;
+SELECT '{"a":1}'::sqljson_mystr IS JSON;
+ ?column? 
+----------
+ t
+(1 row)
+
+\set VERBOSITY terse
+DROP TYPE sqljson_mystr CASCADE;
+NOTICE:  drop cascades to 3 other objects
+\set VERBOSITY default
 SELECT '' IS JSON;
  ?column? 
 ----------
index f9e9429fba606bc718a1627369a2df8a6104b236..0b20498be12e7627bf56476d3e6c038c0963d46d 100644 (file)
@@ -299,6 +299,26 @@ SELECT NULL::text IS JSON;
 SELECT NULL::bytea IS JSON;
 SELECT NULL::int IS JSON;
 
+-- A user-defined string-category type with no implicit cast to text must
+-- produce a clean error rather than crash for IS JSON input (per bug #19491).
+CREATE FUNCTION sqljson_mystr_in(cstring) RETURNS sqljson_mystr
+       AS 'textin' LANGUAGE internal IMMUTABLE STRICT;
+CREATE FUNCTION sqljson_mystr_out(sqljson_mystr) RETURNS cstring
+       AS 'textout' LANGUAGE internal IMMUTABLE STRICT;
+CREATE TYPE sqljson_mystr (
+       INPUT = sqljson_mystr_in,
+       OUTPUT = sqljson_mystr_out,
+       LIKE = text,
+       CATEGORY = 'S'
+);
+SELECT '{"a":1}'::sqljson_mystr IS JSON;                -- error
+-- An implicit cast to text lets the same query work normally.
+CREATE CAST (sqljson_mystr AS text) WITHOUT FUNCTION AS IMPLICIT;
+SELECT '{"a":1}'::sqljson_mystr IS JSON;
+\set VERBOSITY terse
+DROP TYPE sqljson_mystr CASCADE;
+\set VERBOSITY default
+
 SELECT '' IS JSON;
 
 SELECT bytea '\x00' IS JSON;