diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index e02b0c8..1f5d50b 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1284,6 +1284,22 @@ include_dir 'conf.d' + + allow_multiple_queries (boolean) + + allow_multiple_queries configuration parameter + + + + + When this parameter is on, the PostgreSQL server + allow multiple SQL commands without being a transaction block in the given string + in simple query protocol. setting this parameter off is use for providing + an additional defense against SQL-injection attacks. + + + + diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 4bc5bf3..de48b0c 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2160,6 +2160,14 @@ PGresult *PQexec(PGconn *conn, const char *command); of the last command executed from the string. Should one of the commands fail, processing of the string stops with it and the returned PGresult describes the error condition. + + + The server may be configure to disallow multiple sql commands without being + a transaction block to add an extra defense against SQL-injection attacks + so it is always a good practice to add BEGIN/COMMIT + commands for multiple sql commands + + diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 75c2d9a..3e1a2fc 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -96,7 +96,7 @@ int max_stack_depth = 100; /* wait N seconds to allow attach from a debugger */ int PostAuthDelay = 0; - +bool allow_multiple_queries = true; /* ---------------- * private variables @@ -185,6 +185,8 @@ static void finish_xact_command(void); static bool IsTransactionExitStmt(Node *parsetree); static bool IsTransactionExitStmtList(List *pstmts); static bool IsTransactionStmtList(List *pstmts); +static bool IsTransactionStartStmt(Node *parsetree); +static bool IsTransactionEndStmt(Node *parsetree); static void drop_unnamed_stmt(void); static void SigHupHandler(SIGNAL_ARGS); static void log_disconnections(int code, Datum arg); @@ -937,6 +939,25 @@ exec_simple_query(const char *query_string) */ parsetree_list = pg_parse_query(query_string); + if (!allow_multiple_queries && (list_length(parsetree_list) > 1)) + { + /* + * we use head and tail cell for checking weather it is a transaction + * block or not + */ + RawStmt *parsetreehead = lfirst_node(RawStmt, list_head(parsetree_list)); + RawStmt *parsetreetail = lfirst_node(RawStmt, list_tail(parsetree_list)); + + /* + * We only allow a single user query or a transaction block per call . + * This is provide an additional defense against SQL-injection attacks. + */ + + if (!IsTransactionStartStmt(parsetreehead->stmt) || !IsTransactionEndStmt(parsetreetail->stmt) ) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("cannot execute multiple commands unless it is a transaction block"))); + } + /* Log immediately if dictated by log_statement */ if (check_log_statement(parsetree_list)) { @@ -2532,6 +2553,44 @@ IsTransactionStmtList(List *pstmts) return false; } +/* + *Convenience routines for checking whether a statement is transaction Start statment. + */ + +/* Test a bare parsetree */ +static bool +IsTransactionStartStmt(Node *parsetree) +{ + if (parsetree && IsA(parsetree, TransactionStmt)) + { + TransactionStmt *stmt = (TransactionStmt *) parsetree; + + if (stmt->kind == TRANS_STMT_BEGIN || + stmt->kind == TRANS_STMT_START) + return true; + } + return false; +} + +/* + * Convenience routines for checking whether a statement is transaction end statment. + */ + +/* Test a bare parsetree */ +static bool +IsTransactionEndStmt(Node *parsetree) +{ + if (parsetree && IsA(parsetree, TransactionStmt)) + { + TransactionStmt *stmt = (TransactionStmt *) parsetree; + + if (stmt->kind == TRANS_STMT_COMMIT|| + stmt->kind == TRANS_STMT_ROLLBACK) + return true; + } + return false; +} + /* Release any existing unnamed prepared statement */ static void drop_unnamed_stmt(void) diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index a414fb2..0aada36 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1670,6 +1670,16 @@ static struct config_bool ConfigureNamesBool[] = NULL, NULL, NULL }, + { + {"allow_multiple_queries", PGC_SUSET, CLIENT_CONN_OTHER, + gettext_noop("Allow multiple queries per query string."), + NULL + }, + &allow_multiple_queries, + true, + NULL, NULL, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index f1a34a1..e11ea23 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -47,6 +47,8 @@ typedef enum extern int log_statement; +extern bool allow_multiple_queries; + extern List *pg_parse_query(const char *query_string); extern List *pg_analyze_and_rewrite(RawStmt *parsetree, const char *query_string,