Fix transform_cypher_param to pass agtype_access_operator a VARIADIC array - #2489
Open
vividy163 wants to merge 2 commits into
Open
Fix transform_cypher_param to pass agtype_access_operator a VARIADIC array#2489vividy163 wants to merge 2 commits into
vividy163 wants to merge 2 commits into
Conversation
…array
agtype_access_operator is declared VARIADIC agtype[] and every other
call site in cypher_expr.c packs its arguments into a single agtype[]
ArrayExpr via make_agtype_array_expr() and sets funcvariadic = true on
the resulting FuncExpr. transform_cypher_param() was the sole exception:
it passed the Param (the bound $1 agtype argument to cypher()) and the
key Const directly as separate list elements to makeFuncExpr, never
wrapped them in an ArrayExpr, and never set funcvariadic.
With the Simple Query protocol PostgreSQL implicitly assembles the
variadic array from the separate arguments at execution time, which
masked the bug. Under the Extended Query protocol (prepared statements
via Parse/Bind/Execute) the executor did not perform that implicit
assembly, so agtype_access_operator read past its single agtype argument
treating adjacent memory as additional array elements. The resulting
out-of-bounds read surfaced as corrupted relation names in 'relation
does not exist' error responses (random bytes mixed with fragments of
in-flight query text / property values such as 'November', 'customer',
'communication', control bytes including 0x01), which client drivers
then failed to UTF-8 decode:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9e in position 51:
invalid start byte
The fix matches every other call site: wrap the two arguments in
make_agtype_array_expr() and set func_expr->funcvariadic = true.
A regression test is added that PREPAREs a CREATE EDGE statement whose
MATCH clause resolves $src_id/$tgt_id scalar parameters (the exact
shape used by LightRAG / asyncpg) and EXECUTEs it repeatedly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
agtype_access_operatoris declaredVARIADIC agtype[]. Every other call site incypher_expr.cpacks its arguments into a singleagtype[]ArrayExprviamake_agtype_array_expr()and setsfuncvariadic = trueon the resultingFuncExpr(seetransform_cypher_map_projection,transform_cypher_indirection).transform_cypher_param()— the path that turns a Cypher$paramreference into an AGE function call — was the only exception. It passed the bound Param and the key-name Const directly as separateFuncExprarguments without wrapping them and without settingfuncvariadic.With the Simple Query protocol PostgreSQL implicitly assembles the variadic array from separate arguments at execution time, which masked the bug. Under the Extended Query protocol (Parse/Bind/Execute prepared statements) the executor does not perform that implicit assembly, so
agtype_access_operatorread past its singleagtypeargument and treated adjacent memory as additional array elements. The resulting out-of-bounds read surfaced as corrupted relation names in "relation does not exist" error responses (random bytes mixed with fragments of in-flight query text / property values such as "November", "customer", "communication", and control bytes like0x01). Drivers that UTF-8 decode the server error then raised:UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9e in position 51: invalid start byte
The bug reproduces with any Open/asyncpg/JDBC driver that uses prepared statements with bound
agtypeparameters against a Cypher query that references$param(for exampleMERGE (n:base {entity_id: $entity_id})orMATCH (s:base {id: $src_id}), (t:base {id: $tgt_id}) CREATE (s)-[r:DIRECTED {…}]->(t), the shape used by LightRAG). The error content is unrelated to the payload — edges with pure-ASCII property values failed identically.Fix
Pack the two arguments in
make_agtype_array_expr()and setfunc_expr->funcvariadic = true, exactly matching every other call site.Regression test
Added p_create_edge to regress/sql/cypher_create.sql, which PREPAREs a MATCH … {id: $src_id}, … {id: $tgt_id} CREATE ()-[:e {id: $edge_id}]->() statement (the exact shape used by LightRAG/asyncpg) and EXECUTEs it repeatedly.