Skip to content
Merged
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
4 changes: 4 additions & 0 deletions cpp/ql/lib/change-notes/2026-08-27-libpq-sql-sinks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added the PostgreSQL libpq (asynchronous) query-execution functions `PQexec`, `PQexecParams`, `PQprepare`, `PQsendQuery`, `PQsendQueryParams`, `PQsendPrepare` as `sql-injection` sinks.
16 changes: 16 additions & 0 deletions cpp/ql/lib/ext/Postgres.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# partial model of the PostgreSQL libpq C API
extensions:
- addsTo:
pack: codeql/cpp-all
extensible: sinkModel
data: # namespace, type, subtypes, name, signature, ext, input, kind, provenance
# The `*Params`/`*Prepare` functions interpret their command/query text as SQL,
# so injection into that text is a sink even though the parameter values are
# passed separately. The `*Prepared` functions take a prepared-statement name
# rather than SQL text and are therefore not modeled here.
- ["", "", False, "PQexec", "", "", "Argument[*1]", "sql-injection", "manual"]
- ["", "", False, "PQexecParams", "", "", "Argument[*1]", "sql-injection", "manual"]
- ["", "", False, "PQprepare", "", "", "Argument[*2]", "sql-injection", "manual"]
- ["", "", False, "PQsendQuery", "", "", "Argument[*1]", "sql-injection", "manual"]
- ["", "", False, "PQsendQueryParams", "", "", "Argument[*1]", "sql-injection", "manual"]
- ["", "", False, "PQsendPrepare", "", "", "Argument[*2]", "sql-injection", "manual"]
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
| test.c:106:24:106:29 | query1 | test.c:101:8:101:16 | gets output argument | test.c:106:24:106:29 | query1 | This argument to a SQL query function is derived from $@. | test.c:101:8:101:16 | gets output argument | user input (string read by gets) |
| test.c:107:28:107:33 | query1 | test.c:101:8:101:16 | gets output argument | test.c:107:28:107:33 | query1 | This argument to a SQL query function is derived from $@. | test.c:101:8:101:16 | gets output argument | user input (string read by gets) |
| test.cpp:43:27:43:33 | *access to array | test.cpp:39:27:39:30 | **argv | test.cpp:43:27:43:33 | *access to array | This argument to a SQL query function is derived from $@ and then passed to pqxx::work::exec1((unnamed parameter 0)). | test.cpp:39:27:39:30 | **argv | user input (a command-line argument) |
| test_libpq.c:26:16:26:24 | userInput | test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:26:16:26:24 | userInput | This argument to a SQL query function is derived from $@. | test_libpq.c:23:8:23:16 | gets output argument | user input (string read by gets) |
| test_libpq.c:27:22:27:30 | userInput | test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:27:22:27:30 | userInput | This argument to a SQL query function is derived from $@. | test_libpq.c:23:8:23:16 | gets output argument | user input (string read by gets) |
| test_libpq.c:28:27:28:35 | userInput | test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:28:27:28:35 | userInput | This argument to a SQL query function is derived from $@. | test_libpq.c:23:8:23:16 | gets output argument | user input (string read by gets) |
| test_libpq.c:29:21:29:29 | userInput | test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:29:21:29:29 | userInput | This argument to a SQL query function is derived from $@. | test_libpq.c:23:8:23:16 | gets output argument | user input (string read by gets) |
| test_libpq.c:30:27:30:35 | userInput | test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:30:27:30:35 | userInput | This argument to a SQL query function is derived from $@. | test_libpq.c:23:8:23:16 | gets output argument | user input (string read by gets) |
| test_libpq.c:31:31:31:39 | userInput | test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:31:31:31:39 | userInput | This argument to a SQL query function is derived from $@. | test_libpq.c:23:8:23:16 | gets output argument | user input (string read by gets) |
edges
| test.c:14:27:14:30 | **argv | test.c:15:20:15:26 | *access to array | provenance | |
| test.c:15:20:15:26 | *access to array | test.c:21:18:21:23 | *query1 | provenance | TaintFunction |
Expand All @@ -20,9 +26,21 @@ edges
| test.c:101:8:101:16 | gets output argument | test.c:106:24:106:29 | query1 | provenance | TaintFunction Sink:MaD:2 |
| test.c:101:8:101:16 | gets output argument | test.c:107:28:107:33 | query1 | provenance | TaintFunction Sink:MaD:1 |
| test.cpp:39:27:39:30 | **argv | test.cpp:43:27:43:33 | *access to array | provenance | |
| test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:26:16:26:24 | userInput | provenance | Sink:MaD:3 |
| test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:27:22:27:30 | userInput | provenance | Sink:MaD:4 |
| test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:28:27:28:35 | userInput | provenance | Sink:MaD:5 |
| test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:29:21:29:29 | userInput | provenance | Sink:MaD:7 |
| test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:30:27:30:35 | userInput | provenance | Sink:MaD:8 |
| test_libpq.c:23:8:23:16 | gets output argument | test_libpq.c:31:31:31:39 | userInput | provenance | Sink:MaD:6 |
models
| 1 | Sink: ; ; false; OCIStmtPrepare2; ; ; Argument[*3]; sql-injection; manual |
| 2 | Sink: ; ; false; OCIStmtPrepare; ; ; Argument[*2]; sql-injection; manual |
| 3 | Sink: ; ; false; PQexec; ; ; Argument[*1]; sql-injection; manual |
| 4 | Sink: ; ; false; PQexecParams; ; ; Argument[*1]; sql-injection; manual |
| 5 | Sink: ; ; false; PQprepare; ; ; Argument[*2]; sql-injection; manual |
| 6 | Sink: ; ; false; PQsendPrepare; ; ; Argument[*2]; sql-injection; manual |
| 7 | Sink: ; ; false; PQsendQuery; ; ; Argument[*1]; sql-injection; manual |
| 8 | Sink: ; ; false; PQsendQueryParams; ; ; Argument[*1]; sql-injection; manual |
nodes
| test.c:14:27:14:30 | **argv | semmle.label | **argv |
| test.c:15:20:15:26 | *access to array | semmle.label | *access to array |
Expand All @@ -41,4 +59,11 @@ nodes
| test.c:107:28:107:33 | query1 | semmle.label | query1 |
| test.cpp:39:27:39:30 | **argv | semmle.label | **argv |
| test.cpp:43:27:43:33 | *access to array | semmle.label | *access to array |
| test_libpq.c:23:8:23:16 | gets output argument | semmle.label | gets output argument |
| test_libpq.c:26:16:26:24 | userInput | semmle.label | userInput |
| test_libpq.c:27:22:27:30 | userInput | semmle.label | userInput |
| test_libpq.c:28:27:28:35 | userInput | semmle.label | userInput |
| test_libpq.c:29:21:29:29 | userInput | semmle.label | userInput |
| test_libpq.c:30:27:30:35 | userInput | semmle.label | userInput |
| test_libpq.c:31:31:31:39 | userInput | semmle.label | userInput |
subpaths
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
typedef unsigned long size_t;
typedef unsigned int Oid;
typedef struct pg_conn PGconn;
typedef struct pg_result PGresult;

PGresult *PQexec(PGconn *conn, const char *query);
PGresult *PQexecParams(PGconn *conn, const char *command, int nParams,
const Oid *paramTypes, const char *const *paramValues,
const int *paramLengths, const int *paramFormats, int resultFormat);
PGresult *PQprepare(PGconn *conn, const char *stmtName, const char *query, int nParams,
const Oid *paramTypes);
int PQsendQuery(PGconn *conn, const char *query);
int PQsendQueryParams(PGconn *conn, const char *command, int nParams, const Oid *paramTypes,
const char *const *paramValues, const int *paramLengths,
const int *paramFormats, int resultFormat);
int PQsendPrepare(PGconn *conn, const char *stmtName, const char *query, int nParams,
const Oid *paramTypes);

char *gets(char *s);

void libpqTests(PGconn *conn) {
char userInput[1000];
gets(userInput); // $ Source

// A user-controlled string is interpreted as SQL.
PQexec(conn, userInput); // $ Alert
PQexecParams(conn, userInput, 0, 0, 0, 0, 0, 0); // $ Alert
PQprepare(conn, "stmt", userInput, 0, 0); // $ Alert
PQsendQuery(conn, userInput); // $ Alert
PQsendQueryParams(conn, userInput, 0, 0, 0, 0, 0, 0); // $ Alert
PQsendPrepare(conn, "stmt", userInput, 0, 0); // $ Alert

// A constant query is safe.
PQexec(conn, "SELECT 1"); // GOOD
}
Loading