Skip to content

Commit

Permalink
Obstruct shell, SQL, and conninfo injection via database and role names.
Browse files Browse the repository at this point in the history
Due to simplistic quoting and confusion of database names with conninfo
strings, roles with the CREATEDB or CREATEROLE option could escalate to
superuser privileges when a superuser next ran certain maintenance
commands.  The new coding rule for PQconnectdbParams() calls, documented
at conninfo_array_parse(), is to pass expand_dbname=true and wrap
literal database names in a trivial connection string.  Escape
zero-length values in appendConnStrVal().  Back-patch to 9.1 (all
supported versions).

Nathan Bossart, Michael Paquier, and Noah Misch.  Reviewed by Peter
Eisentraut.  Reported by Nathan Bossart.

Security: CVE-2016-5424
  • Loading branch information
nmisch committed Aug 8, 2016
1 parent 41f18f0 commit fcd15f1
Show file tree
Hide file tree
Showing 20 changed files with 315 additions and 67 deletions.
12 changes: 10 additions & 2 deletions src/bin/pg_basebackup/streamutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,15 @@ GetConnection(void)
PQconninfoOption *conn_opt;
char *err_msg = NULL;

/* pg_recvlogical uses dbname only; others use connection_string only. */
Assert(dbname == NULL || connection_string == NULL);

/*
* Merge the connection info inputs given in form of connection string,
* options and default values (dbname=replication, replication=true, etc.)
* Explicitly discard any dbname value in the connection string;
* otherwise, PQconnectdbParams() would interpret that value as being
* itself a connection string.
*/
i = 0;
if (connection_string)
Expand All @@ -80,7 +86,8 @@ GetConnection(void)

for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
{
if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
if (conn_opt->val != NULL && conn_opt->val[0] != '\0' &&
strcmp(conn_opt->keyword, "dbname") != 0)
argcount++;
}

Expand All @@ -89,7 +96,8 @@ GetConnection(void)

for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
{
if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
if (conn_opt->val != NULL && conn_opt->val[0] != '\0' &&
strcmp(conn_opt->keyword, "dbname") != 0)
{
keywords[i] = conn_opt->keyword;
values[i] = conn_opt->val;
Expand Down
4 changes: 2 additions & 2 deletions src/bin/pg_dump/pg_backup.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ typedef struct _restoreOptions
SimpleStringList tableNames;

int useDB;
char *dbname;
char *dbname; /* subject to expand_dbname */
char *pgport;
char *pghost;
char *username;
Expand All @@ -121,7 +121,7 @@ typedef struct _restoreOptions

typedef struct _dumpOptions
{
const char *dbname;
const char *dbname; /* subject to expand_dbname */
const char *pghost;
const char *pgport;
const char *username;
Expand Down
34 changes: 25 additions & 9 deletions src/bin/pg_dump/pg_backup_archiver.c
Original file line number Diff line number Diff line change
Expand Up @@ -771,9 +771,16 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel)
/* If we created a DB, connect to it... */
if (strcmp(te->desc, "DATABASE") == 0)
{
PQExpBufferData connstr;

initPQExpBuffer(&connstr);
appendPQExpBufferStr(&connstr, "dbname=");
appendConnStrVal(&connstr, te->tag);
/* Abandon struct, but keep its buffer until process exit. */

ahlog(AH, 1, "connecting to new database \"%s\"\n", te->tag);
_reconnectToDB(AH, te->tag);
ropt->dbname = pg_strdup(te->tag);
ropt->dbname = connstr.data;
}
}

Expand Down Expand Up @@ -2984,12 +2991,17 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname)
ReconnectToServer(AH, dbname, NULL);
else
{
PQExpBuffer qry = createPQExpBuffer();
if (dbname)
{
PQExpBufferData connectbuf;

appendPQExpBuffer(qry, "\\connect %s\n\n",
dbname ? fmtId(dbname) : "-");
ahprintf(AH, "%s", qry->data);
destroyPQExpBuffer(qry);
initPQExpBuffer(&connectbuf);
appendPsqlMetaConnect(&connectbuf, dbname);
ahprintf(AH, "%s\n", connectbuf.data);
termPQExpBuffer(&connectbuf);
}
else
ahprintf(AH, "%s\n", "\\connect -\n");
}

/*
Expand Down Expand Up @@ -4463,7 +4475,7 @@ CloneArchive(ArchiveHandle *AH)
}
else
{
char *dbname;
PQExpBufferData connstr;
char *pghost;
char *pgport;
char *username;
Expand All @@ -4476,14 +4488,18 @@ CloneArchive(ArchiveHandle *AH)
* because all just return a pointer and do not actually send/receive
* any data to/from the database.
*/
dbname = PQdb(AH->connection);
initPQExpBuffer(&connstr);
appendPQExpBuffer(&connstr, "dbname=");
appendConnStrVal(&connstr, PQdb(AH->connection));
pghost = PQhost(AH->connection);
pgport = PQport(AH->connection);
username = PQuser(AH->connection);

/* this also sets clone->connection */
ConnectDatabase((Archive *) clone, dbname, pghost, pgport, username, TRI_NO);
ConnectDatabase((Archive *) clone, connstr.data,
pghost, pgport, username, TRI_NO);

termPQExpBuffer(&connstr);
/* setupDumpWorker will fix up connection state */
}

Expand Down
10 changes: 9 additions & 1 deletion src/bin/pg_dump/pg_backup_db.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "postgres_fe.h"

#include "dumputils.h"
#include "fe_utils/string_utils.h"
#include "parallel.h"
#include "pg_backup_archiver.h"
#include "pg_backup_db.h"
Expand Down Expand Up @@ -128,6 +129,7 @@ ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
static PGconn *
_connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
{
PQExpBufferData connstr;
PGconn *newConn;
const char *newdb;
const char *newuser;
Expand Down Expand Up @@ -156,6 +158,10 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
exit_horribly(modulename, "out of memory\n");
}

initPQExpBuffer(&connstr);
appendPQExpBuffer(&connstr, "dbname=");
appendConnStrVal(&connstr, newdb);

do
{
const char *keywords[7];
Expand All @@ -170,7 +176,7 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
keywords[3] = "password";
values[3] = password;
keywords[4] = "dbname";
values[4] = newdb;
values[4] = connstr.data;
keywords[5] = "fallback_application_name";
values[5] = progname;
keywords[6] = NULL;
Expand Down Expand Up @@ -222,6 +228,8 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
if (password)
free(password);

termPQExpBuffer(&connstr);

/* check for version mismatch */
_check_database_version(AH);

Expand Down
18 changes: 13 additions & 5 deletions src/bin/pg_dump/pg_dumpall.c
Original file line number Diff line number Diff line change
Expand Up @@ -1507,7 +1507,7 @@ dumpCreateDB(PGconn *conn)
fdbname, fmtId(dbtablespace));

/* connect to original database */
appendPQExpBuffer(buf, "\\connect %s\n", fdbname);
appendPsqlMetaConnect(buf, dbname);
}

if (binary_upgrade)
Expand Down Expand Up @@ -1740,11 +1740,15 @@ dumpDatabases(PGconn *conn)
int ret;

char *dbname = PQgetvalue(res, i, 0);
PQExpBufferData connectbuf;

if (verbose)
fprintf(stderr, _("%s: dumping database \"%s\"...\n"), progname, dbname);

fprintf(OPF, "\\connect %s\n\n", fmtId(dbname));
initPQExpBuffer(&connectbuf);
appendPsqlMetaConnect(&connectbuf, dbname);
fprintf(OPF, "%s\n", connectbuf.data);
termPQExpBuffer(&connectbuf);

/*
* Restore will need to write to the target cluster. This connection
Expand Down Expand Up @@ -1900,7 +1904,9 @@ connectDatabase(const char *dbname, const char *connection_string,

/*
* Merge the connection info inputs given in form of connection string
* and other options.
* and other options. Explicitly discard any dbname value in the
* connection string; otherwise, PQconnectdbParams() would interpret
* that value as being itself a connection string.
*/
if (connection_string)
{
Expand All @@ -1913,7 +1919,8 @@ connectDatabase(const char *dbname, const char *connection_string,

for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
{
if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
if (conn_opt->val != NULL && conn_opt->val[0] != '\0' &&
strcmp(conn_opt->keyword, "dbname") != 0)
argcount++;
}

Expand All @@ -1922,7 +1929,8 @@ connectDatabase(const char *dbname, const char *connection_string,

for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
{
if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
if (conn_opt->val != NULL && conn_opt->val[0] != '\0' &&
strcmp(conn_opt->keyword, "dbname") != 0)
{
keywords[i] = conn_opt->keyword;
values[i] = conn_opt->val;
Expand Down
3 changes: 2 additions & 1 deletion src/bin/pg_upgrade/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ OBJS = check.o controldata.o dump.o exec.o file.o function.o info.o \
tablespace.o util.o version.o $(WIN32RES)

override CPPFLAGS := -DDLSUFFIX=\"$(DLSUFFIX)\" -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq


all: pg_upgrade

pg_upgrade: $(OBJS) | submake-libpq submake-libpgport
pg_upgrade: $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
$(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)

install: all installdirs
Expand Down
19 changes: 12 additions & 7 deletions src/bin/pg_upgrade/check.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "postgres_fe.h"

#include "catalog/pg_authid.h"
#include "fe_utils/string_utils.h"
#include "mb/pg_wchar.h"
#include "pg_upgrade.h"

Expand Down Expand Up @@ -414,12 +415,17 @@ void
create_script_for_cluster_analyze(char **analyze_script_file_name)
{
FILE *script = NULL;
char *user_specification = "";
PQExpBufferData user_specification;

prep_status("Creating script to analyze new cluster");

initPQExpBuffer(&user_specification);
if (os_info.user_specified)
user_specification = psprintf("-U \"%s\" ", os_info.user);
{
appendPQExpBufferStr(&user_specification, "-U ");
appendShellString(&user_specification, os_info.user);
appendPQExpBufferChar(&user_specification, ' ');
}

*analyze_script_file_name = psprintf("%sanalyze_new_cluster.%s",
SCRIPT_PREFIX, SCRIPT_EXT);
Expand Down Expand Up @@ -459,18 +465,18 @@ create_script_for_cluster_analyze(char **analyze_script_file_name)
fprintf(script, "echo %sthis script and run:%s\n",
ECHO_QUOTE, ECHO_QUOTE);
fprintf(script, "echo %s \"%s/vacuumdb\" %s--all %s%s\n", ECHO_QUOTE,
new_cluster.bindir, user_specification,
new_cluster.bindir, user_specification.data,
/* Did we copy the free space files? */
(GET_MAJOR_VERSION(old_cluster.major_version) >= 804) ?
"--analyze-only" : "--analyze", ECHO_QUOTE);
fprintf(script, "echo%s\n\n", ECHO_BLANK);

fprintf(script, "\"%s/vacuumdb\" %s--all --analyze-in-stages\n",
new_cluster.bindir, user_specification);
new_cluster.bindir, user_specification.data);
/* Did we copy the free space files? */
if (GET_MAJOR_VERSION(old_cluster.major_version) < 804)
fprintf(script, "\"%s/vacuumdb\" %s--all\n", new_cluster.bindir,
user_specification);
user_specification.data);

fprintf(script, "echo%s\n\n", ECHO_BLANK);
fprintf(script, "echo %sDone%s\n",
Expand All @@ -484,8 +490,7 @@ create_script_for_cluster_analyze(char **analyze_script_file_name)
*analyze_script_file_name, getErrorText());
#endif

if (os_info.user_specified)
pg_free(user_specification);
termPQExpBuffer(&user_specification);

check_ok();
}
Expand Down
16 changes: 14 additions & 2 deletions src/bin/pg_upgrade/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "pg_upgrade.h"

#include <sys/types.h>
#include "fe_utils/string_utils.h"


void
Expand Down Expand Up @@ -46,17 +47,28 @@ generate_old_dump(void)
char sql_file_name[MAXPGPATH],
log_file_name[MAXPGPATH];
DbInfo *old_db = &old_cluster.dbarr.dbs[dbnum];
PQExpBufferData connstr,
escaped_connstr;

initPQExpBuffer(&connstr);
appendPQExpBuffer(&connstr, "dbname=");
appendConnStrVal(&connstr, old_db->db_name);
initPQExpBuffer(&escaped_connstr);
appendShellString(&escaped_connstr, connstr.data);
termPQExpBuffer(&connstr);

pg_log(PG_STATUS, "%s", old_db->db_name);
snprintf(sql_file_name, sizeof(sql_file_name), DB_DUMP_FILE_MASK, old_db->db_oid);
snprintf(log_file_name, sizeof(log_file_name), DB_DUMP_LOG_FILE_MASK, old_db->db_oid);

parallel_exec_prog(log_file_name, NULL,
"\"%s/pg_dump\" %s --schema-only --quote-all-identifiers "
"--binary-upgrade --format=custom %s --file=\"%s\" \"%s\"",
"--binary-upgrade --format=custom %s --file=\"%s\" %s",
new_cluster.bindir, cluster_conn_opts(&old_cluster),
log_opts.verbose ? "--verbose" : "",
sql_file_name, old_db->db_name);
sql_file_name, escaped_connstr.data);

termPQExpBuffer(&escaped_connstr);
}

/* reap all children */
Expand Down
16 changes: 14 additions & 2 deletions src/bin/pg_upgrade/pg_upgrade.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

#include "pg_upgrade.h"
#include "common/restricted_token.h"
#include "fe_utils/string_utils.h"

#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
Expand Down Expand Up @@ -305,6 +306,15 @@ create_new_objects(void)
char sql_file_name[MAXPGPATH],
log_file_name[MAXPGPATH];
DbInfo *old_db = &old_cluster.dbarr.dbs[dbnum];
PQExpBufferData connstr,
escaped_connstr;

initPQExpBuffer(&connstr);
appendPQExpBuffer(&connstr, "dbname=");
appendConnStrVal(&connstr, old_db->db_name);
initPQExpBuffer(&escaped_connstr);
appendShellString(&escaped_connstr, connstr.data);
termPQExpBuffer(&connstr);

pg_log(PG_STATUS, "%s", old_db->db_name);
snprintf(sql_file_name, sizeof(sql_file_name), DB_DUMP_FILE_MASK, old_db->db_oid);
Expand All @@ -316,11 +326,13 @@ create_new_objects(void)
*/
parallel_exec_prog(log_file_name,
NULL,
"\"%s/pg_restore\" %s --exit-on-error --verbose --dbname \"%s\" \"%s\"",
"\"%s/pg_restore\" %s --exit-on-error --verbose --dbname %s \"%s\"",
new_cluster.bindir,
cluster_conn_opts(&new_cluster),
old_db->db_name,
escaped_connstr.data,
sql_file_name);

termPQExpBuffer(&escaped_connstr);
}

/* reap all children */
Expand Down
Loading

0 comments on commit fcd15f1

Please sign in to comment.