Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions ext/pgsql/pgsql.c
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ static void pgsql_lob_free_obj(zend_object *obj)

/* Compatibility definitions */

static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link, const zend_string *table);

static zend_string *_php_pgsql_trim_message(const char *message)
{
size_t i = strlen(message);
Expand Down Expand Up @@ -3348,8 +3350,7 @@ PHP_FUNCTION(pg_copy_to)
zend_string *table_name;
zend_string *pg_delimiter = NULL;
char *pg_null_as = "\\\\N";
size_t pg_null_as_len = 0;
char *query;
size_t pg_null_as_len = sizeof("\\\\N") - 1;
PGconn *pgsql;
PGresult *pgsql_result;
ExecStatusType status;
Expand All @@ -3373,14 +3374,36 @@ PHP_FUNCTION(pg_copy_to)
zend_argument_value_error(3, "must be one character");
RETURN_THROWS();
}
smart_str querystr = {0};
smart_str_appends(&querystr, "COPY ");
if (ZSTR_LEN(table_name) > 0 && ZSTR_VAL(table_name)[0] == '(') {
smart_str_appendc(&querystr, '(');
smart_str_append(&querystr, table_name);
smart_str_appendc(&querystr, ')');
} else if (build_tablename(&querystr, pgsql, table_name) == FAILURE) {
smart_str_free(&querystr);
RETURN_FALSE;
}

spprintf(&query, 0, "COPY %s TO STDOUT DELIMITER E'%c' NULL AS E'%s'", ZSTR_VAL(table_name), *ZSTR_VAL(pg_delimiter), pg_null_as);
char *escaped_delimiter = PQescapeLiteral(pgsql, ZSTR_VAL(pg_delimiter), 1);
char *escaped_null_as = PQescapeLiteral(pgsql, pg_null_as, pg_null_as_len);
if (!escaped_delimiter || !escaped_null_as) {
php_error_docref(NULL, E_WARNING, "Failed to escape COPY parameters");
if (escaped_delimiter) PQfreemem(escaped_delimiter);
if (escaped_null_as) PQfreemem(escaped_null_as);
smart_str_free(&querystr);
RETURN_FALSE;
}
smart_str_append_printf(&querystr, " TO STDOUT DELIMITER %s NULL AS %s", escaped_delimiter, escaped_null_as);
smart_str_0(&querystr);
PQfreemem(escaped_delimiter);
PQfreemem(escaped_null_as);

while ((pgsql_result = PQgetResult(pgsql))) {
PQclear(pgsql_result);
}
pgsql_result = PQexec(pgsql, query);
efree(query);
pgsql_result = PQexec(pgsql, ZSTR_VAL(querystr.s));
smart_str_free(&querystr);

if (pgsql_result) {
status = PQresultStatus(pgsql_result);
Expand Down Expand Up @@ -3463,8 +3486,7 @@ PHP_FUNCTION(pg_copy_from)
zend_string *table_name;
zend_string *pg_delimiter = NULL;
char *pg_null_as = "\\\\N";
size_t pg_null_as_len;
char *query;
size_t pg_null_as_len = sizeof("\\\\N") - 1;
PGconn *pgsql;
PGresult *pgsql_result;
ExecStatusType status;
Expand All @@ -3488,14 +3510,33 @@ PHP_FUNCTION(pg_copy_from)
zend_argument_value_error(4, "must be one character");
RETURN_THROWS();
}
smart_str querystr = {0};
smart_str_appends(&querystr, "COPY ");
if (build_tablename(&querystr, pgsql, table_name) == FAILURE) {
smart_str_free(&querystr);
RETURN_FALSE;
}

char *escaped_delimiter = PQescapeLiteral(pgsql, ZSTR_VAL(pg_delimiter), 1);
char *escaped_null_as = PQescapeLiteral(pgsql, pg_null_as, pg_null_as_len);
if (!escaped_delimiter || !escaped_null_as) {
php_error_docref(NULL, E_WARNING, "Failed to escape COPY parameters");
if (escaped_delimiter) PQfreemem(escaped_delimiter);
if (escaped_null_as) PQfreemem(escaped_null_as);
smart_str_free(&querystr);
RETURN_FALSE;
}
smart_str_append_printf(&querystr, " FROM STDIN DELIMITER %s NULL AS %s", escaped_delimiter, escaped_null_as);
smart_str_0(&querystr);
PQfreemem(escaped_delimiter);
PQfreemem(escaped_null_as);

spprintf(&query, 0, "COPY %s FROM STDIN DELIMITER E'%c' NULL AS E'%s'", ZSTR_VAL(table_name), *ZSTR_VAL(pg_delimiter), pg_null_as);
while ((pgsql_result = PQgetResult(pgsql))) {
PQclear(pgsql_result);
}
pgsql_result = PQexec(pgsql, query);
pgsql_result = PQexec(pgsql, ZSTR_VAL(querystr.s));

efree(query);
smart_str_free(&querystr);

if (pgsql_result) {
status = PQresultStatus(pgsql_result);
Expand Down
43 changes: 43 additions & 0 deletions ext/pgsql/tests/pg_copy_from_null_as_escape.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
pg_copy_from() escapes the null_as argument
--EXTENSIONS--
pgsql
--SKIPIF--
<?php include("inc/skipif.inc"); ?>
--FILE--
<?php

include('inc/config.inc');

$db = pg_connect($conn_str);
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_null_as_target');
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_null_as_injected');
pg_query($db, 'CREATE TABLE pg_copy_null_as_target (v text)');

$evil = "X'; CREATE TABLE pg_copy_null_as_injected (v text); --";
var_dump(pg_copy_from($db, 'pg_copy_null_as_target', ["row\n"], "\t", $evil));

$r = pg_query($db, "SELECT 1 FROM pg_tables WHERE tablename = 'pg_copy_null_as_injected'");
var_dump(pg_num_rows($r));

$r = pg_query($db, 'SELECT v FROM pg_copy_null_as_target ORDER BY v');
var_dump(pg_fetch_all($r));

?>
--CLEAN--
<?php
include('inc/config.inc');
$db = pg_connect($conn_str);
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_null_as_target');
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_null_as_injected');
?>
--EXPECT--
bool(true)
int(0)
array(1) {
[0]=>
array(1) {
["v"]=>
string(3) "row"
}
}
36 changes: 36 additions & 0 deletions ext/pgsql/tests/pg_copy_from_table_name_escape.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
pg_copy_from() escapes the table name argument
--EXTENSIONS--
pgsql
--SKIPIF--
<?php include("inc/skipif.inc"); ?>
--FILE--
<?php

include('inc/config.inc');

$db = pg_connect($conn_str);
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_from_target');
pg_query($db, 'CREATE TABLE pg_copy_from_target (v text)');
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_from_other');
pg_query($db, 'CREATE TABLE pg_copy_from_other (v text)');

$evil = "pg_copy_from_other FROM STDIN --";
var_dump(pg_copy_from($db, $evil, ["redirected\n"]));

$rows = pg_fetch_all(pg_query($db, 'SELECT v FROM pg_copy_from_other')) ?: [];
var_dump($rows);

?>
--CLEAN--
<?php
include('inc/config.inc');
$db = pg_connect($conn_str);
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_from_target');
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_from_other');
?>
--EXPECTF--
Warning: pg_copy_from(): %s in %s on line %d
bool(false)
array(0) {
}
50 changes: 50 additions & 0 deletions ext/pgsql/tests/pg_copy_to_query_injection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
pg_copy_to() rejects statement injection through the (query) source form
--EXTENSIONS--
pgsql
--SKIPIF--
<?php include("inc/skipif.inc"); ?>
--FILE--
<?php

include('inc/config.inc');

$db = pg_connect($conn_str);
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_to_qsource');
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_to_qinjected');
pg_query($db, 'CREATE TABLE pg_copy_to_qsource (v text)');
pg_query($db, "INSERT INTO pg_copy_to_qsource VALUES ('a'), ('b')");

var_dump(pg_copy_to($db, '(SELECT v FROM pg_copy_to_qsource ORDER BY v)'));

$evil = '(SELECT 1); DROP TABLE pg_copy_to_qsource; --';
var_dump(pg_copy_to($db, $evil));

$r = pg_query($db, "SELECT 1 FROM pg_tables WHERE tablename = 'pg_copy_to_qsource'");
var_dump(pg_num_rows($r));

$r = pg_query($db, "SELECT 1 FROM pg_tables WHERE tablename = 'pg_copy_to_qinjected'");
var_dump(pg_num_rows($r));

?>
--CLEAN--
<?php
include('inc/config.inc');
$db = pg_connect($conn_str);
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_to_qsource');
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_to_qinjected');
?>
--EXPECTF--
array(2) {
[0]=>
string(2) "a
"
[1]=>
string(2) "b
"
}

Warning: pg_copy_to(): %ain %s on line %d
bool(false)
int(1)
int(0)
34 changes: 34 additions & 0 deletions ext/pgsql/tests/pg_copy_to_table_name_escape.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
pg_copy_to() escapes the table name argument
--EXTENSIONS--
pgsql
--SKIPIF--
<?php include("inc/skipif.inc"); ?>
--FILE--
<?php

include('inc/config.inc');

$db = pg_connect($conn_str);
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_to_source');
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_to_injected');
pg_query($db, 'CREATE TABLE pg_copy_to_source (v text)');

$evil = "pg_copy_to_source; CREATE TABLE pg_copy_to_injected (v text); --";
var_dump(pg_copy_to($db, $evil));

$r = pg_query($db, "SELECT 1 FROM pg_tables WHERE tablename = 'pg_copy_to_injected'");
var_dump(pg_num_rows($r));

?>
--CLEAN--
<?php
include('inc/config.inc');
$db = pg_connect($conn_str);
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_to_source');
pg_query($db, 'DROP TABLE IF EXISTS pg_copy_to_injected');
?>
--EXPECTF--
Warning: pg_copy_to(): %s in %s on line %d
bool(false)
int(0)
Loading