Skip to content

Commit

Permalink
Fix insecure parsing of server command-line switches.
Browse files Browse the repository at this point in the history
An oversight in commit e710b65 allowed
database names beginning with "-" to be treated as though they were secure
command-line switches; and this switch processing occurs before client
authentication, so that even an unprivileged remote attacker could exploit
the bug, needing only connectivity to the postmaster's port.  Assorted
exploits for this are possible, some requiring a valid database login,
some not.  The worst known problem is that the "-r" switch can be invoked
to redirect the process's stderr output, so that subsequent error messages
will be appended to any file the server can write.  This can for example be
used to corrupt the server's configuration files, so that it will fail when
next restarted.  Complete destruction of database tables is also possible.

Fix by keeping the database name extracted from a startup packet fully
separate from command-line switches, as had already been done with the
user name field.

The Postgres project thanks Mitsumasa Kondo for discovering this bug,
Kyotaro Horiguchi for drafting the fix, and Noah Misch for recognizing
the full extent of the danger.

Security: CVE-2013-1899
  • Loading branch information
tglsfdc committed Apr 1, 2013
1 parent ce9ab88 commit 17fe279
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 32 deletions.
4 changes: 3 additions & 1 deletion src/backend/main/main.c
Expand Up @@ -189,7 +189,9 @@ main(int argc, char *argv[])
else if (argc > 1 && strcmp(argv[1], "--describe-config") == 0)
GucInfoMain(); /* does not return */
else if (argc > 1 && strcmp(argv[1], "--single") == 0)
PostgresMain(argc, argv, get_current_username(progname)); /* does not return */
PostgresMain(argc, argv,
NULL, /* no dbname */
get_current_username(progname)); /* does not return */
else
PostmasterMain(argc, argv); /* does not return */
abort(); /* should not get here */
Expand Down
9 changes: 2 additions & 7 deletions src/backend/postmaster/postmaster.c
Expand Up @@ -3943,7 +3943,7 @@ BackendRun(Port *port)
* from ExtraOptions is (strlen(ExtraOptions) + 1) / 2; see
* pg_split_opts().
*/
maxac = 5; /* for fixed args supplied below */
maxac = 2; /* for fixed args supplied below */
maxac += (strlen(ExtraOptions) + 1) / 2;

av = (char **) MemoryContextAlloc(TopMemoryContext,
Expand All @@ -3959,11 +3959,6 @@ BackendRun(Port *port)
*/
pg_split_opts(av, &ac, ExtraOptions);

/*
* Tell the backend which database to use.
*/
av[ac++] = port->database_name;

av[ac] = NULL;

Assert(ac < maxac);
Expand All @@ -3986,7 +3981,7 @@ BackendRun(Port *port)
*/
MemoryContextSwitchTo(TopMemoryContext);

PostgresMain(ac, av, port->user_name);
PostgresMain(ac, av, port->database_name, port->user_name);
}


Expand Down
41 changes: 21 additions & 20 deletions src/backend/tcop/postgres.c
Expand Up @@ -3247,13 +3247,14 @@ get_stats_option_name(const char *arg)
* coming from the client, or PGC_SUSET for insecure options coming from
* a superuser client.
*
* Returns the database name extracted from the command line, if any.
* If a database name is present in the command line arguments, it's
* returned into *dbname (this is allowed only if *dbname is initially NULL).
* ----------------------------------------------------------------
*/
const char *
process_postgres_switches(int argc, char *argv[], GucContext ctx)
void
process_postgres_switches(int argc, char *argv[], GucContext ctx,
const char **dbname)
{
const char *dbname;
bool secure = (ctx == PGC_POSTMASTER);
int errs = 0;
GucSource gucsource;
Expand Down Expand Up @@ -3304,7 +3305,8 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx)

case 'b':
/* Undocumented flag used for binary upgrades */
IsBinaryUpgrade = true;
if (secure)
IsBinaryUpgrade = true;
break;

case 'C':
Expand All @@ -3321,7 +3323,8 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx)
break;

case 'E':
EchoQuery = true;
if (secure)
EchoQuery = true;
break;

case 'e':
Expand All @@ -3346,7 +3349,8 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx)
break;

case 'j':
UseNewLine = 0;
if (secure)
UseNewLine = 0;
break;

case 'k':
Expand Down Expand Up @@ -3464,13 +3468,10 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx)
}

/*
* Should be no more arguments except an optional database name, and
* that's only in the secure case.
* Optional database name should be there only if *dbname is NULL.
*/
if (!errs && secure && argc - optind >= 1)
dbname = strdup(argv[optind++]);
else
dbname = NULL;
if (!errs && dbname && *dbname == NULL && argc - optind >= 1)
*dbname = strdup(argv[optind++]);

if (errs || argc != optind)
{
Expand Down Expand Up @@ -3499,8 +3500,6 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx)
#ifdef HAVE_INT_OPTRESET
optreset = 1; /* some systems need this too */
#endif

return dbname;
}


Expand All @@ -3510,14 +3509,16 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx)
*
* argc/argv are the command line arguments to be used. (When being forked
* by the postmaster, these are not the original argv array of the process.)
* username is the (possibly authenticated) PostgreSQL user name to be used
* for the session.
* dbname is the name of the database to connect to, or NULL if the database
* name should be extracted from the command line arguments or defaulted.
* username is the PostgreSQL user name to be used for the session.
* ----------------------------------------------------------------
*/
void
PostgresMain(int argc, char *argv[], const char *username)
PostgresMain(int argc, char *argv[],
const char *dbname,
const char *username)
{
const char *dbname;
int firstchar;
StringInfoData input_message;
sigjmp_buf local_sigjmp_buf;
Expand Down Expand Up @@ -3564,7 +3565,7 @@ PostgresMain(int argc, char *argv[], const char *username)
/*
* Parse command-line options.
*/
dbname = process_postgres_switches(argc, argv, PGC_POSTMASTER);
process_postgres_switches(argc, argv, PGC_POSTMASTER, &dbname);

/* Must have gotten a database name, or have a default (the username) */
if (dbname == NULL)
Expand Down
2 changes: 1 addition & 1 deletion src/backend/utils/init/postinit.c
Expand Up @@ -969,7 +969,7 @@ process_startup_options(Port *port, bool am_superuser)

Assert(ac < maxac);

(void) process_postgres_switches(ac, av, gucctx);
(void) process_postgres_switches(ac, av, gucctx, NULL);
}

/*
Expand Down
8 changes: 5 additions & 3 deletions src/include/tcop/tcopprot.h
Expand Up @@ -69,9 +69,11 @@ extern void RecoveryConflictInterrupt(ProcSignalReason reason); /* called from S
* handler */
extern void prepare_for_client_read(void);
extern void client_read_ended(void);
extern const char *process_postgres_switches(int argc, char *argv[],
GucContext ctx);
extern void PostgresMain(int argc, char *argv[], const char *username) __attribute__((noreturn));
extern void process_postgres_switches(int argc, char *argv[],
GucContext ctx, const char **dbname);
extern void PostgresMain(int argc, char *argv[],
const char *dbname,
const char *username) __attribute__((noreturn));
extern long get_stack_depth_rlimit(void);
extern void ResetUsage(void);
extern void ShowUsage(const char *title);
Expand Down

0 comments on commit 17fe279

Please sign in to comment.