Skip to content

Commit 1f12627

Browse files
committed
protect against nulls in deparse
1 parent 4f4664e commit 1f12627

5 files changed

Lines changed: 181 additions & 9 deletions

File tree

c_src/libpg_query/src/pg_query_readfuncs_protobuf.c

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,28 @@ static List * _readList(PgQuery__List *msg)
9696

9797
static Node * _readNode(PgQuery__Node *msg)
9898
{
99+
if (msg == NULL)
100+
ereport(ERROR,
101+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
102+
errmsg("unexpected NULL node in protobuf input")));
103+
99104
switch (msg->node_case)
100105
{
106+
/*
107+
* Redefine READ_COND to guard against NULL inner struct pointers.
108+
* When Elixir passes a tagged oneof value like {:column_ref, nil},
109+
* Protox may encode an empty sub-message, causing protobuf-c to set
110+
* the inner pointer to a zeroed struct or NULL. Either way we must
111+
* error cleanly rather than segfault inside the per-type reader.
112+
*/
113+
#undef READ_COND
114+
#define READ_COND(typename, typename_c, typename_underscore, typename_underscore_upcase, typename_cast, outname) \
115+
case PG_QUERY__NODE__NODE_##typename_underscore_upcase: \
116+
if (msg->outname == NULL) \
117+
ereport(ERROR, \
118+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
119+
errmsg("unexpected NULL inner struct in protobuf node"))); \
120+
return (Node *) _read##typename_c(msg->outname);
101121
#include "pg_query_readfuncs_conds.c"
102122

103123
case PG_QUERY__NODE__NODE_INTEGER:
@@ -145,7 +165,10 @@ static Node * _readNode(PgQuery__Node *msg)
145165
case PG_QUERY__NODE__NODE_LIST:
146166
return (Node *) _readList(msg->list);
147167
case PG_QUERY__NODE__NODE__NOT_SET:
148-
return NULL;
168+
ereport(ERROR,
169+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
170+
errmsg("unexpected unset node type in protobuf input")));
171+
return NULL; /* unreachable */
149172
default:
150173
elog(ERROR, "unsupported protobuf node type: %d",
151174
(int) msg->node_case);
@@ -160,11 +183,19 @@ List * pg_query_protobuf_to_nodes(PgQueryProtobuf protobuf)
160183

161184
result = pg_query__parse_result__unpack(NULL, protobuf.len, (const uint8_t *) protobuf.data);
162185

163-
// TODO: Handle this by returning an error instead
164-
Assert(result != NULL);
186+
if (result == NULL)
187+
ereport(ERROR,
188+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
189+
errmsg("failed to unpack protobuf parse result: invalid or truncated input")));
165190

166-
// TODO: Handle this by returning an error instead
167-
Assert(result->version == PG_VERSION_NUM);
191+
if (result->version != PG_VERSION_NUM)
192+
{
193+
pg_query__parse_result__free_unpacked(result, NULL);
194+
ereport(ERROR,
195+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
196+
errmsg("protobuf parse result version mismatch: expected %d, got %d",
197+
PG_VERSION_NUM, result->version)));
198+
}
168199

169200
if (result->n_stmts > 0)
170201
list = list_make1(_readRawStmt(result->stmts[0]));

c_src/libpg_query/src/postgres_deparse.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2730,6 +2730,11 @@ static void deparseUtilityOptionList(DeparseState *state, List *options)
27302730

27312731
static void deparseSelectStmt(DeparseState *state, SelectStmt *stmt, DeparseNodeContext context)
27322732
{
2733+
if (stmt == NULL)
2734+
ereport(ERROR,
2735+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2736+
errmsg("unexpected NULL SelectStmt in protobuf input")));
2737+
27332738
const ListCell *lc = NULL;
27342739
const ListCell *lc2 = NULL;
27352740
bool need_parens = context == DEPARSE_NODE_CONTEXT_SELECT_SETOP && (
@@ -4272,9 +4277,16 @@ static void deparseRowExpr(DeparseState *state, RowExpr *row_expr)
42724277

42734278
static void deparseTypeCast(DeparseState *state, TypeCast *type_cast, DeparseNodeContext context)
42744279
{
4275-
bool need_parens = needsParensAsBExpr(type_cast->arg);
4280+
if (type_cast->arg == NULL)
4281+
ereport(ERROR,
4282+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4283+
errmsg("unexpected NULL arg in TypeCast")));
4284+
if (type_cast->typeName == NULL)
4285+
ereport(ERROR,
4286+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4287+
errmsg("unexpected NULL typeName in TypeCast")));
42764288

4277-
Assert(type_cast->typeName != NULL);
4289+
bool need_parens = needsParensAsBExpr(type_cast->arg);
42784290

42794291
if (context == DEPARSE_NODE_CONTEXT_FUNC_EXPR)
42804292
{
@@ -4356,6 +4368,11 @@ static void deparseTypeCast(DeparseState *state, TypeCast *type_cast, DeparseNod
43564368

43574369
static void deparseTypeName(DeparseState *state, TypeName *type_name)
43584370
{
4371+
if (type_name == NULL)
4372+
ereport(ERROR,
4373+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4374+
errmsg("unexpected NULL TypeName in protobuf input")));
4375+
43594376
ListCell *lc;
43604377
bool skip_typmods = false;
43614378

lib/pg_query/parser.ex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,12 @@ defmodule PgQuery.Parser do
3333
end
3434

3535
def protobuf_to_query(%PgQuery.ParseResult{} = parse_result) do
36-
with {:ok, encoded, _byte_size} <- Protox.encode(parse_result) do
37-
deparse_query(IO.iodata_to_binary(encoded))
36+
try do
37+
with {:ok, encoded, _byte_size} <- Protox.encode(parse_result) do
38+
deparse_query(IO.iodata_to_binary(encoded))
39+
end
40+
rescue
41+
e -> {:error, %{message: "protobuf encoding failed: #{Exception.message(e)}"}}
3842
end
3943
end
4044

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
diff --git a/c_src/libpg_query/src/pg_query_readfuncs_protobuf.c b/c_src/libpg_query/src/pg_query_readfuncs_protobuf.c
2+
index d0a7e21..c3c30fa 100644
3+
--- a/c_src/libpg_query/src/pg_query_readfuncs_protobuf.c
4+
+++ b/c_src/libpg_query/src/pg_query_readfuncs_protobuf.c
5+
@@ -160,11 +160,19 @@ List * pg_query_protobuf_to_nodes(PgQueryProtobuf protobuf)
6+
7+
result = pg_query__parse_result__unpack(NULL, protobuf.len, (const uint8_t *) protobuf.data);
8+
9+
- // TODO: Handle this by returning an error instead
10+
- Assert(result != NULL);
11+
+ if (result == NULL)
12+
+ ereport(ERROR,
13+
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
14+
+ errmsg("failed to unpack protobuf parse result: invalid or truncated input")));
15+
16+
- // TODO: Handle this by returning an error instead
17+
- Assert(result->version == PG_VERSION_NUM);
18+
+ if (result->version != PG_VERSION_NUM)
19+
+ {
20+
+ pg_query__parse_result__free_unpacked(result, NULL);
21+
+ ereport(ERROR,
22+
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
23+
+ errmsg("protobuf parse result version mismatch: expected %d, got %d",
24+
+ PG_VERSION_NUM, result->version)));
25+
+ }
26+
27+
if (result->n_stmts > 0)
28+
list = list_make1(_readRawStmt(result->stmts[0]));
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
diff --git a/c_src/libpg_query/src/pg_query_readfuncs_protobuf.c b/c_src/libpg_query/src/pg_query_readfuncs_protobuf.c
2+
index c3c30fa..3e6f7cf 100644
3+
--- a/c_src/libpg_query/src/pg_query_readfuncs_protobuf.c
4+
+++ b/c_src/libpg_query/src/pg_query_readfuncs_protobuf.c
5+
@@ -96,8 +96,28 @@ static List * _readList(PgQuery__List *msg)
6+
7+
static Node * _readNode(PgQuery__Node *msg)
8+
{
9+
+ if (msg == NULL)
10+
+ ereport(ERROR,
11+
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
12+
+ errmsg("unexpected NULL node in protobuf input")));
13+
+
14+
switch (msg->node_case)
15+
{
16+
+ /*
17+
+ * Redefine READ_COND to guard against NULL inner struct pointers.
18+
+ * When Elixir passes a tagged oneof value like {:column_ref, nil},
19+
+ * Protox may encode an empty sub-message, causing protobuf-c to set
20+
+ * the inner pointer to a zeroed struct or NULL. Either way we must
21+
+ * error cleanly rather than segfault inside the per-type reader.
22+
+ */
23+
+#undef READ_COND
24+
+#define READ_COND(typename, typename_c, typename_underscore, typename_underscore_upcase, typename_cast, outname) \
25+
+ case PG_QUERY__NODE__NODE_##typename_underscore_upcase: \
26+
+ if (msg->outname == NULL) \
27+
+ ereport(ERROR, \
28+
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
29+
+ errmsg("unexpected NULL inner struct in protobuf node"))); \
30+
+ return (Node *) _read##typename_c(msg->outname);
31+
#include "pg_query_readfuncs_conds.c"
32+
33+
case PG_QUERY__NODE__NODE_INTEGER:
34+
@@ -145,7 +165,10 @@ static Node * _readNode(PgQuery__Node *msg)
35+
case PG_QUERY__NODE__NODE_LIST:
36+
return (Node *) _readList(msg->list);
37+
case PG_QUERY__NODE__NODE__NOT_SET:
38+
- return NULL;
39+
+ ereport(ERROR,
40+
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
41+
+ errmsg("unexpected unset node type in protobuf input")));
42+
+ return NULL; /* unreachable */
43+
default:
44+
elog(ERROR, "unsupported protobuf node type: %d",
45+
(int) msg->node_case);
46+
diff --git a/c_src/libpg_query/src/postgres_deparse.c b/c_src/libpg_query/src/postgres_deparse.c
47+
index 9d8e1ea..79f8c47 100644
48+
--- a/c_src/libpg_query/src/postgres_deparse.c
49+
+++ b/c_src/libpg_query/src/postgres_deparse.c
50+
@@ -2730,6 +2730,11 @@ static void deparseUtilityOptionList(DeparseState *state, List *options)
51+
52+
static void deparseSelectStmt(DeparseState *state, SelectStmt *stmt, DeparseNodeContext context)
53+
{
54+
+ if (stmt == NULL)
55+
+ ereport(ERROR,
56+
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
57+
+ errmsg("unexpected NULL SelectStmt in protobuf input")));
58+
+
59+
const ListCell *lc = NULL;
60+
const ListCell *lc2 = NULL;
61+
bool need_parens = context == DEPARSE_NODE_CONTEXT_SELECT_SETOP && (
62+
@@ -4272,9 +4277,16 @@ static void deparseRowExpr(DeparseState *state, RowExpr *row_expr)
63+
64+
static void deparseTypeCast(DeparseState *state, TypeCast *type_cast, DeparseNodeContext context)
65+
{
66+
- bool need_parens = needsParensAsBExpr(type_cast->arg);
67+
+ if (type_cast->arg == NULL)
68+
+ ereport(ERROR,
69+
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
70+
+ errmsg("unexpected NULL arg in TypeCast")));
71+
+ if (type_cast->typeName == NULL)
72+
+ ereport(ERROR,
73+
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
74+
+ errmsg("unexpected NULL typeName in TypeCast")));
75+
76+
- Assert(type_cast->typeName != NULL);
77+
+ bool need_parens = needsParensAsBExpr(type_cast->arg);
78+
79+
if (context == DEPARSE_NODE_CONTEXT_FUNC_EXPR)
80+
{
81+
@@ -4356,6 +4368,11 @@ static void deparseTypeCast(DeparseState *state, TypeCast *type_cast, DeparseNod
82+
83+
static void deparseTypeName(DeparseState *state, TypeName *type_name)
84+
{
85+
+ if (type_name == NULL)
86+
+ ereport(ERROR,
87+
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
88+
+ errmsg("unexpected NULL TypeName in protobuf input")));
89+
+
90+
ListCell *lc;
91+
bool skip_typmods = false;
92+

0 commit comments

Comments
 (0)