From: Álvaro Herrera Date: Thu, 11 Jun 2026 14:17:58 +0000 (+0200) Subject: IS JSON/JSON(): Protect against expressions uncoercible to text X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=60abb3c7317586911606f35ac192a647a699ac12;p=thirdparty%2Fpostgresql.git IS JSON/JSON(): Protect against expressions uncoercible to text 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 Reported-by: Chi Zhang <798604270@qq.com> Reviewed-by: Srinath Reddy Sadipiralla Backpatch-through: 16 Discussion: https://postgr.es/m/19491-7aafc221ec63f288@postgresql.org --- diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 6ed16254c93..c131cac408b 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -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; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 26e862ce107..0c0376b1cb4 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -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; } diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index 7a5a6325229..4808b4575ed 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -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? ---------- diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index f9e9429fba6..0b20498be12 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -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;