Skip to content

Commit 48fca83

Browse files
authored
Restrict age_load commands (#2274)
This PR applies restrictions to the following age_load commands - load_labels_from_file() load_edges_from_file() They are now tied to a specific root directory and are required to have a specific file extension to eliminate any attempts to force them to access any other files. Nothing else has changed with the actual command formats or parameters, only that they work out of the /tmp/age directory and only access files with an extension of .csv. Added regression tests and updated the location of the csv files for those regression tests. modified: regress/expected/age_load.out modified: regress/sql/age_load.sql modified: src/backend/utils/load/age_load.c
1 parent 838926c commit 48fca83

File tree

3 files changed

+149
-9
lines changed

3 files changed

+149
-9
lines changed

regress/expected/age_load.out

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
\! cp -r regress/age_load/data regress/instance/data/age_load
19+
\! rm -rf /tmp/age/age_load
20+
\! mkdir -p /tmp/age
21+
\! cp -r regress/age_load/data /tmp/age/age_load
2022
LOAD 'age';
2123
SET search_path TO ag_catalog;
2224
-- Create a country using CREATE clause
@@ -401,6 +403,43 @@ SELECT * FROM cypher('agload_conversion', $$ MATCH ()-[e:Edges2]->() RETURN prop
401403
{"bool": "false", "string": "nUll", "numeric": "3.14"}
402404
(6 rows)
403405

406+
--
407+
-- Check sandbox
408+
--
409+
-- check null file name
410+
SELECT load_labels_from_file('agload_conversion', 'Person1', NULL, true, true);
411+
ERROR: file path must not be NULL
412+
SELECT load_edges_from_file('agload_conversion', 'Edges1', NULL, true);
413+
ERROR: file path must not be NULL
414+
-- check no file name
415+
SELECT load_labels_from_file('agload_conversion', 'Person1', '', true, true);
416+
ERROR: file name cannot be zero length
417+
SELECT load_edges_from_file('agload_conversion', 'Edges1', '', true);
418+
ERROR: file name cannot be zero length
419+
-- check for file/path does not exist
420+
SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load_xxx/conversion_vertices.csv', true, true);
421+
ERROR: File or path does not exist [/tmp/age/age_load_xxx/conversion_vertices.csv]
422+
SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load_xxx/conversion_edges.csv', true);
423+
ERROR: File or path does not exist [/tmp/age/age_load_xxx/conversion_edges.csv]
424+
SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load/conversion_vertices.txt', true, true);
425+
ERROR: File or path does not exist [/tmp/age/age_load/conversion_vertices.txt]
426+
SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load/conversion_edges.txt', true);
427+
ERROR: File or path does not exist [/tmp/age/age_load/conversion_edges.txt]
428+
-- check wrong extension
429+
\! touch /tmp/age/age_load/conversion_vertices.txt
430+
\! touch /tmp/age/age_load/conversion_edges.txt
431+
SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load/conversion_vertices.txt', true, true);
432+
ERROR: You can only load files with extension [.csv].
433+
SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load/conversion_edges.txt', true);
434+
ERROR: You can only load files with extension [.csv].
435+
-- check outside sandbox directory
436+
SELECT load_labels_from_file('agload_conversion', 'Person1', '../../etc/passwd', true, true);
437+
ERROR: You can only load files located in [/tmp/age/].
438+
SELECT load_edges_from_file('agload_conversion', 'Edges1', '../../etc/passwd', true);
439+
ERROR: You can only load files located in [/tmp/age/].
440+
--
441+
-- Cleanup
442+
--
404443
SELECT drop_graph('agload_conversion', true);
405444
NOTICE: drop cascades to 6 other objects
406445
DETAIL: drop cascades to table agload_conversion._ag_label_vertex
@@ -415,3 +454,6 @@ NOTICE: graph "agload_conversion" has been dropped
415454

416455
(1 row)
417456

457+
--
458+
-- End
459+
--

regress/sql/age_load.sql

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
* under the License.
1818
*/
1919

20-
\! cp -r regress/age_load/data regress/instance/data/age_load
20+
\! rm -rf /tmp/age/age_load
21+
\! mkdir -p /tmp/age
22+
\! cp -r regress/age_load/data /tmp/age/age_load
2123

2224
LOAD 'age';
2325

@@ -160,4 +162,38 @@ SELECT create_elabel('agload_conversion','Edges2');
160162
SELECT load_edges_from_file('agload_conversion', 'Edges2', 'age_load/conversion_edges.csv', false);
161163
SELECT * FROM cypher('agload_conversion', $$ MATCH ()-[e:Edges2]->() RETURN properties(e) $$) as (a agtype);
162164

165+
--
166+
-- Check sandbox
167+
--
168+
-- check null file name
169+
SELECT load_labels_from_file('agload_conversion', 'Person1', NULL, true, true);
170+
SELECT load_edges_from_file('agload_conversion', 'Edges1', NULL, true);
171+
172+
-- check no file name
173+
SELECT load_labels_from_file('agload_conversion', 'Person1', '', true, true);
174+
SELECT load_edges_from_file('agload_conversion', 'Edges1', '', true);
175+
176+
-- check for file/path does not exist
177+
SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load_xxx/conversion_vertices.csv', true, true);
178+
SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load_xxx/conversion_edges.csv', true);
179+
SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load/conversion_vertices.txt', true, true);
180+
SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load/conversion_edges.txt', true);
181+
182+
-- check wrong extension
183+
\! touch /tmp/age/age_load/conversion_vertices.txt
184+
\! touch /tmp/age/age_load/conversion_edges.txt
185+
SELECT load_labels_from_file('agload_conversion', 'Person1', 'age_load/conversion_vertices.txt', true, true);
186+
SELECT load_edges_from_file('agload_conversion', 'Edges1', 'age_load/conversion_edges.txt', true);
187+
188+
-- check outside sandbox directory
189+
SELECT load_labels_from_file('agload_conversion', 'Person1', '../../etc/passwd', true, true);
190+
SELECT load_edges_from_file('agload_conversion', 'Edges1', '../../etc/passwd', true);
191+
192+
--
193+
-- Cleanup
194+
--
163195
SELECT drop_graph('agload_conversion', true);
196+
197+
--
198+
-- End
199+
--

src/backend/utils/load/age_load.c

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,62 @@ static agtype_value *csv_value_to_agtype_value(char *csv_val);
3131
static Oid get_or_create_graph(const Name graph_name);
3232
static int32 get_or_create_label(Oid graph_oid, char *graph_name,
3333
char *label_name, char label_kind);
34+
static char *build_safe_filename(char *name);
35+
36+
#define AGE_BASE_CSV_DIRECTORY "/tmp/age/"
37+
#define AGE_CSV_FILE_EXTENSION ".csv"
38+
39+
static char *build_safe_filename(char *name)
40+
{
41+
int length;
42+
char path[PATH_MAX];
43+
char *resolved;
44+
45+
if (name == NULL)
46+
{
47+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
48+
errmsg("file name cannot be NULL")));
49+
50+
}
51+
52+
length = strlen(name);
53+
54+
if (length == 0)
55+
{
56+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
57+
errmsg("file name cannot be zero length")));
58+
59+
}
60+
61+
snprintf(path, sizeof(path), "%s%s", AGE_BASE_CSV_DIRECTORY, name);
62+
63+
resolved = realpath(path, NULL);
64+
65+
if (resolved == NULL)
66+
{
67+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
68+
errmsg("File or path does not exist [%s]", path)));
69+
}
70+
71+
if (strncmp(resolved, AGE_BASE_CSV_DIRECTORY,
72+
strlen(AGE_BASE_CSV_DIRECTORY)) != 0)
73+
{
74+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
75+
errmsg("You can only load files located in [%s].",
76+
AGE_BASE_CSV_DIRECTORY)));
77+
}
78+
79+
length = strlen(resolved) - 4;
80+
if (strncmp(resolved+length, AGE_CSV_FILE_EXTENSION,
81+
strlen(AGE_CSV_FILE_EXTENSION)) != 0)
82+
{
83+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
84+
errmsg("You can only load files with extension [%s].",
85+
AGE_CSV_FILE_EXTENSION)));
86+
}
87+
88+
return resolved;
89+
}
3490

3591
agtype *create_empty_agtype(void)
3692
{
@@ -344,7 +400,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS)
344400
{
345401
Name graph_name;
346402
Name label_name;
347-
text* file_path;
403+
text* file_name;
348404
char* graph_name_str;
349405
char* label_name_str;
350406
char* file_path_str;
@@ -373,7 +429,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS)
373429

374430
graph_name = PG_GETARG_NAME(0);
375431
label_name = PG_GETARG_NAME(1);
376-
file_path = PG_GETARG_TEXT_P(2);
432+
file_name = PG_GETARG_TEXT_P(2);
377433
id_field_exists = PG_GETARG_BOOL(3);
378434
load_as_agtype = PG_GETARG_BOOL(4);
379435

@@ -385,7 +441,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS)
385441
label_name_str = AG_DEFAULT_LABEL_VERTEX;
386442
}
387443

388-
file_path_str = text_to_cstring(file_path);
444+
file_path_str = build_safe_filename(text_to_cstring(file_name));
389445

390446
graph_oid = get_or_create_graph(graph_name);
391447
label_id = get_or_create_label(graph_oid, graph_name_str,
@@ -394,6 +450,9 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS)
394450
create_labels_from_csv_file(file_path_str, graph_name_str, graph_oid,
395451
label_name_str, label_id, id_field_exists,
396452
load_as_agtype);
453+
454+
free(file_path_str);
455+
397456
PG_RETURN_VOID();
398457
}
399458

@@ -403,7 +462,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS)
403462

404463
Name graph_name;
405464
Name label_name;
406-
text* file_path;
465+
text* file_name;
407466
char* graph_name_str;
408467
char* label_name_str;
409468
char* file_path_str;
@@ -431,7 +490,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS)
431490

432491
graph_name = PG_GETARG_NAME(0);
433492
label_name = PG_GETARG_NAME(1);
434-
file_path = PG_GETARG_TEXT_P(2);
493+
file_name = PG_GETARG_TEXT_P(2);
435494
load_as_agtype = PG_GETARG_BOOL(3);
436495

437496
graph_name_str = NameStr(*graph_name);
@@ -442,14 +501,17 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS)
442501
label_name_str = AG_DEFAULT_LABEL_EDGE;
443502
}
444503

445-
file_path_str = text_to_cstring(file_path);
504+
file_path_str = build_safe_filename(text_to_cstring(file_name));
446505

447506
graph_oid = get_or_create_graph(graph_name);
448507
label_id = get_or_create_label(graph_oid, graph_name_str,
449508
label_name_str, LABEL_KIND_EDGE);
450509

451510
create_edges_from_csv_file(file_path_str, graph_name_str, graph_oid,
452511
label_name_str, label_id, load_as_agtype);
512+
513+
free(file_path_str);
514+
453515
PG_RETURN_VOID();
454516
}
455517

@@ -599,4 +661,4 @@ void finish_batch_insert(batch_insert_state **batch_state)
599661
pfree((*batch_state)->slots);
600662
pfree(*batch_state);
601663
*batch_state = NULL;
602-
}
664+
}

0 commit comments

Comments
 (0)