From 3f0ed74a7ca9b7ff6ec31ab6a251ab0683caaa2a Mon Sep 17 00:00:00 2001 From: himmel Date: Mon, 17 Aug 2026 09:18:27 +0000 Subject: [PATCH] Fix backend crash in age_create_barbell_graph with a null node label node_label is declared "name = NULL" in the SQL signature, so leaving it out - or passing NULL explicitly - is a supported call. Both crashed the backend with SIGSEGV, which makes the postmaster reinitialize and drops every other session on the instance. There were two separate faults on that path. First, the default label was copied into a null pointer: Name node_label_name = NULL; ... if (PG_ARGISNULL(3)) namestrcpy(node_label_name, AG_DEFAULT_LABEL_VERTEX); namestrcpy() writes through the pointer it is given and does not allocate, so give the default its own NameData. This also makes the default actually take effect, which it never did. Second, the node label was forwarded to create_complete_graph() as args[3].value. DirectFunctionCall4() marks every argument as not null, so a null node label arrived there as a non-null NULL pointer and was dereferenced by the vertex/edge label comparison. Forward the resolved label instead. create_complete_graph() already handles its own null node label correctly; follow the same approach here. Extend the graph_generation test with the previously untested cases. The existing barbell tests always passed a node label, except for the all-arguments-null case which errors out on the graph name before ever reaching this code. --- regress/expected/graph_generation.out | 65 +++++++++++++++++++++++++++ regress/sql/graph_generation.sql | 15 +++++++ src/backend/utils/graph_generation.c | 23 +++++++--- 3 files changed, 98 insertions(+), 5 deletions(-) diff --git a/regress/expected/graph_generation.out b/regress/expected/graph_generation.out index ca511eafa..c038f9d9e 100644 --- a/regress/expected/graph_generation.out +++ b/regress/expected/graph_generation.out @@ -203,6 +203,60 @@ ERROR: edge label can not be NULL -- Should error out because same labels are used for both vertices and edges SELECT * FROM age_create_barbell_graph('gp6',5,10,'label',NULL,'label',NULL); ERROR: vertex and edge label can not be same +/* + * node_label is declared with a NULL default, so passing NULL for it - either + * explicitly or by leaving the trailing arguments out - has to fall back to + * the default vertex label rather than crash the backend. + */ +SELECT * FROM age_create_barbell_graph('gp7',5,0,NULL,NULL,'edges',NULL); +NOTICE: graph "gp7" has been created +NOTICE: ELabel "edges" has been created + age_create_barbell_graph +-------------------------- + +(1 row) + +SELECT COUNT(*) FROM gp7."_ag_label_vertex"; + count +------- + 10 +(1 row) + +SELECT COUNT(*) FROM gp7."edges"; + count +------- + 21 +(1 row) + +SELECT * FROM cypher('gp7', $$MATCH (a)-[e]->(b) RETURN e$$) as (n agtype); + n +--------------------------------------------------------------------------------------------------------------------------- + {"id": 844424930131969, "label": "edges", "end_id": 281474976710658, "start_id": 281474976710657, "properties": {}}::edge + {"id": 844424930131970, "label": "edges", "end_id": 281474976710659, "start_id": 281474976710657, "properties": {}}::edge + {"id": 844424930131971, "label": "edges", "end_id": 281474976710660, "start_id": 281474976710657, "properties": {}}::edge + {"id": 844424930131972, "label": "edges", "end_id": 281474976710661, "start_id": 281474976710657, "properties": {}}::edge + {"id": 844424930131973, "label": "edges", "end_id": 281474976710659, "start_id": 281474976710658, "properties": {}}::edge + {"id": 844424930131974, "label": "edges", "end_id": 281474976710660, "start_id": 281474976710658, "properties": {}}::edge + {"id": 844424930131975, "label": "edges", "end_id": 281474976710661, "start_id": 281474976710658, "properties": {}}::edge + {"id": 844424930131976, "label": "edges", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {}}::edge + {"id": 844424930131977, "label": "edges", "end_id": 281474976710661, "start_id": 281474976710659, "properties": {}}::edge + {"id": 844424930131978, "label": "edges", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge + {"id": 844424930131979, "label": "edges", "end_id": 281474976710663, "start_id": 281474976710662, "properties": {}}::edge + {"id": 844424930131980, "label": "edges", "end_id": 281474976710664, "start_id": 281474976710662, "properties": {}}::edge + {"id": 844424930131981, "label": "edges", "end_id": 281474976710665, "start_id": 281474976710662, "properties": {}}::edge + {"id": 844424930131982, "label": "edges", "end_id": 281474976710666, "start_id": 281474976710662, "properties": {}}::edge + {"id": 844424930131983, "label": "edges", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {}}::edge + {"id": 844424930131984, "label": "edges", "end_id": 281474976710665, "start_id": 281474976710663, "properties": {}}::edge + {"id": 844424930131985, "label": "edges", "end_id": 281474976710666, "start_id": 281474976710663, "properties": {}}::edge + {"id": 844424930131986, "label": "edges", "end_id": 281474976710665, "start_id": 281474976710664, "properties": {}}::edge + {"id": 844424930131987, "label": "edges", "end_id": 281474976710666, "start_id": 281474976710664, "properties": {}}::edge + {"id": 844424930131988, "label": "edges", "end_id": 281474976710666, "start_id": 281474976710665, "properties": {}}::edge + {"id": 844424930131989, "label": "edges", "end_id": 281474976710666, "start_id": 281474976710657, "properties": {}}::edge +(21 rows) + +-- SHOULD FAIL, but with an error rather than a crash +SELECT * FROM age_create_barbell_graph('gp8',5,0); +ERROR: edge label can not be NULL -- DROPPING GRAPHS SELECT drop_graph('gp1', true); NOTICE: drop cascades to 4 other objects @@ -228,3 +282,14 @@ NOTICE: graph "gp2" has been dropped (1 row) +SELECT drop_graph('gp7', true); +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table gp7._ag_label_vertex +drop cascades to table gp7._ag_label_edge +drop cascades to table gp7.edges +NOTICE: graph "gp7" has been dropped + drop_graph +------------ + +(1 row) + diff --git a/regress/sql/graph_generation.sql b/regress/sql/graph_generation.sql index e9ee8ea81..ee359ce9b 100644 --- a/regress/sql/graph_generation.sql +++ b/regress/sql/graph_generation.sql @@ -76,7 +76,22 @@ SELECT * FROM age_create_barbell_graph('gp5',5,0,'vertices',NULL,NULL,NULL); -- Should error out because same labels are used for both vertices and edges SELECT * FROM age_create_barbell_graph('gp6',5,10,'label',NULL,'label',NULL); +/* + * node_label is declared with a NULL default, so passing NULL for it - either + * explicitly or by leaving the trailing arguments out - has to fall back to + * the default vertex label rather than crash the backend. + */ +SELECT * FROM age_create_barbell_graph('gp7',5,0,NULL,NULL,'edges',NULL); + +SELECT COUNT(*) FROM gp7."_ag_label_vertex"; +SELECT COUNT(*) FROM gp7."edges"; +SELECT * FROM cypher('gp7', $$MATCH (a)-[e]->(b) RETURN e$$) as (n agtype); + +-- SHOULD FAIL, but with an error rather than a crash +SELECT * FROM age_create_barbell_graph('gp8',5,0); + -- DROPPING GRAPHS SELECT drop_graph('gp1', true); SELECT drop_graph('gp2', true); +SELECT drop_graph('gp7', true); diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c index ea8e1bd5b..4dea04260 100644 --- a/src/backend/utils/graph_generation.c +++ b/src/backend/utils/graph_generation.c @@ -238,6 +238,8 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) int64 start_node_index, end_node_index, nextval; Name node_label_name = NULL; + NameData default_node_label; + Datum node_label_datum; int32 node_label_id; char* node_label_str; @@ -284,16 +286,22 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) errmsg("Bridge size cannot be NULL or lower than 0"))); } - /* node label: if null, gets default label, which is "_ag_label_vertex" */ + /* + * node label: if null, gets default label, which is "_ag_label_vertex". + * The default needs storage of its own - namestrcpy() writes through the + * pointer it is given and does not allocate. + */ if (PG_ARGISNULL(3)) { - namestrcpy(node_label_name, AG_DEFAULT_LABEL_VERTEX); + namestrcpy(&default_node_label, AG_DEFAULT_LABEL_VERTEX); + node_label_name = &default_node_label; } else { node_label_name = PG_GETARG_NAME(3); } node_label_str = NameStr(*node_label_name); + node_label_datum = NameGetDatum(node_label_name); /* Name edge_label */ if (PG_ARGISNULL(5)) @@ -306,15 +314,20 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) edge_label_str = NameStr(*edge_label_name); - /* create two separate complete graphs */ + /* + * Create two separate complete graphs. node_label_datum is used rather + * than args[3].value because DirectFunctionCall4() marks every argument + * as not null, so a null node label would reach create_complete_graph() + * as a non-null NULL pointer. + */ DirectFunctionCall4(create_complete_graph, arguments->args[0].value, arguments->args[1].value, arguments->args[5].value, - arguments->args[3].value); + node_label_datum); DirectFunctionCall4(create_complete_graph, arguments->args[0].value, arguments->args[1].value, arguments->args[5].value, - arguments->args[3].value); + node_label_datum); graph_oid = get_graph_oid(graph_name_str); node_label_id = get_label_id(node_label_str, graph_oid);