From 8fe56a5f3c1abb5ea6ba201706585812303e6bc8 Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Thu, 6 Jun 2024 16:28:07 +0900
Subject: [PATCH v2] SQL/JSON: Correct jsonpath variable name matching

Previously, GetJsonPathVar() allowed a jsonpath expression to
reference any prefix of a PASSING variable's name. For example, the
following query would incorrectly work:

SELECT JSON_QUERY(context_item, jsonpath '$xy' PASSING val AS xyz);

The fix ensures that the length of the variable name mentioned in a
jsonpath expression matches exactly with the name of the PASSING
variable before comparing the strings using strncmp().

Reported-by: Alvaro Herrera (off-list)
Discussion: https://postgr.es/m/CA+HiwqFGkLWMvELBH6E4SQ45qUHthgcRH6gCJL20OsYDRtFx_w@mail.gmail.com
---
 src/backend/executor/execExpr.c                  |  1 +
 src/backend/utils/adt/jsonpath_exec.c            |  4 +++-
 src/include/utils/jsonpath.h                     |  1 +
 src/test/regress/expected/sqljson_queryfuncs.out | 11 +++++++++++
 src/test/regress/sql/sqljson_queryfuncs.sql      |  5 +++++
 5 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index b9ebc827a7..2bf86d06ef 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -4278,6 +4278,7 @@ ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state,
 		JsonPathVariable *var = palloc(sizeof(*var));
 
 		var->name = argname->sval;
+		var->namelen = strlen(var->name);
 		var->typid = exprType((Node *) argexpr);
 		var->typmod = exprTypmod((Node *) argexpr);
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 8a0a2dbc85..0979b9023e 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -2992,7 +2992,8 @@ GetJsonPathVar(void *cxt, char *varName, int varNameLen,
 	{
 		JsonPathVariable *curvar = lfirst(lc);
 
-		if (!strncmp(curvar->name, varName, varNameLen))
+		if (curvar->namelen == varNameLen &&
+			strncmp(curvar->name, varName, varNameLen) == 0)
 		{
 			var = curvar;
 			break;
@@ -4116,6 +4117,7 @@ JsonTableInitOpaque(TableFuncScanState *state, int natts)
 			JsonPathVariable *var = palloc(sizeof(*var));
 
 			var->name = pstrdup(name->sval);
+			var->namelen = strlen(var->name);
 			var->typid = exprType((Node *) state->expr);
 			var->typmod = exprTypmod((Node *) state->expr);
 
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 69c180c2e2..70ce4b565e 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -287,6 +287,7 @@ extern bool jspConvertRegexFlags(uint32 xflags, int *result,
 typedef struct JsonPathVariable
 {
 	char	   *name;
+	int			namelen;	/* strlen(name) as cache for GetJsonPathVar() */
 	Oid			typid;
 	int32		typmod;
 	Datum		value;
diff --git a/src/test/regress/expected/sqljson_queryfuncs.out b/src/test/regress/expected/sqljson_queryfuncs.out
index 21e0fc6417..98117b346d 100644
--- a/src/test/regress/expected/sqljson_queryfuncs.out
+++ b/src/test/regress/expected/sqljson_queryfuncs.out
@@ -1334,3 +1334,14 @@ SELECT json_value('"aaa"', path RETURNING json) FROM jsonpaths;
  "aaa"
 (1 row)
 
+-- Test PASSING argument parsing
+SELECT JSON_QUERY(jsonb 'null', '$xyz' PASSING 1 AS xy);
+ERROR:  could not find jsonpath variable "xyz"
+SELECT JSON_QUERY(jsonb 'null', '$xy' PASSING 1 AS xyz);
+ERROR:  could not find jsonpath variable "xy"
+SELECT JSON_QUERY(jsonb 'null', '$xyz' PASSING 1 AS xyz);
+ json_query 
+------------
+ 1
+(1 row)
+
diff --git a/src/test/regress/sql/sqljson_queryfuncs.sql b/src/test/regress/sql/sqljson_queryfuncs.sql
index c46489e2dd..d9dbb1ceaa 100644
--- a/src/test/regress/sql/sqljson_queryfuncs.sql
+++ b/src/test/regress/sql/sqljson_queryfuncs.sql
@@ -454,3 +454,8 @@ SELECT JSON_QUERY(NULL FORMAT JSON, '$');
 -- Test non-const jsonpath
 CREATE TEMP TABLE jsonpaths (path) AS SELECT '$';
 SELECT json_value('"aaa"', path RETURNING json) FROM jsonpaths;
+
+-- Test PASSING argument parsing
+SELECT JSON_QUERY(jsonb 'null', '$xyz' PASSING 1 AS xy);
+SELECT JSON_QUERY(jsonb 'null', '$xy' PASSING 1 AS xyz);
+SELECT JSON_QUERY(jsonb 'null', '$xyz' PASSING 1 AS xyz);
-- 
2.43.0

