]> 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 e2d9e9be41a65f79d7da662b234b3f873e2e290d..bc48966c5d1e141fbb6a0b5326feefc639711815 100644 (file)
@@ -988,6 +988,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 b6d30ddc29823ce25d60616ef0a2a3e638abab5a..a00b46a25ca00f5bd4b5ea64f4817522b7365553 100644 (file)
@@ -4088,10 +4088,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 625acf3019a3818f5966d6486f9703ec3fa1d19d..6b0d881550839951025776974a0d1c50a7bfbe0e 100644 (file)
@@ -1148,6 +1148,44 @@ 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 / 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
+LINE 1: CREATE FUNCTION sqljson_mystr_out(sqljson_mystr) RETURNS cst...
+                                          ^
+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;
+               ^
+SELECT JSON('{"a":1}'::sqljson_mystr WITH UNIQUE KEYS); -- error
+ERROR:  cannot cast type sqljson_mystr to text
+LINE 1: SELECT JSON('{"a":1}'::sqljson_mystr WITH UNIQUE KEYS);
+                    ^
+-- 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 343d344d2707f40bbb4f9acd17b0ec5e0772b545..0b77deb3b24b77b53faff38f1a4a6c7aeb294874 100644 (file)
@@ -395,6 +395,28 @@ 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 / 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
+SELECT JSON('{"a":1}'::sqljson_mystr WITH UNIQUE KEYS); -- 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;