1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-21 05:21:08 +03:00

Restrict psql meta-commands in plain-text dumps.

A malicious server could inject psql meta-commands into plain-text
dump output (i.e., scripts created with pg_dump --format=plain,
pg_dumpall, or pg_restore --file) that are run at restore time on
the machine running psql.  To fix, introduce a new "restricted"
mode in psql that blocks all meta-commands (except for \unrestrict
to exit the mode), and teach pg_dump, pg_dumpall, and pg_restore to
use this mode in plain-text dumps.

While at it, encourage users to only restore dumps generated from
trusted servers or to inspect it beforehand, since restoring causes
the destination to execute arbitrary code of the source superusers'
choice.  However, the client running the dump and restore needn't
trust the source or destination superusers.

Reported-by: Martin Rakhmanov
Reported-by: Matthieu Denais <litezeraw@gmail.com>
Reported-by: RyotaK <ryotak.mail@gmail.com>
Suggested-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Security: CVE-2025-8714
Backpatch-through: 13
This commit is contained in:
Nathan Bossart
2025-08-11 09:00:00 -05:00
parent 13a67ce603
commit 67a2fbb8f9
22 changed files with 435 additions and 13 deletions

View File

@@ -130,6 +130,8 @@ static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_b
static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf);
static backslashResult exec_command_restrict(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_sendpipeline(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch);
@@ -142,6 +144,8 @@ static backslashResult exec_command_syncpipeline(PsqlScanState scan_state, bool
static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch,
@@ -192,6 +196,8 @@ static char *pset_value_string(const char *param, printQueryOpt *popt);
static void checkWin32Codepage(void);
#endif
static bool restricted;
static char *restrict_key;
/*----------
@@ -237,8 +243,19 @@ HandleSlashCmds(PsqlScanState scan_state,
/* Parse off the command name */
cmd = psql_scan_slash_command(scan_state);
/* And try to execute it */
status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
/*
* And try to execute it.
*
* If we are in "restricted" mode, the only allowable backslash command is
* \unrestrict (to exit restricted mode).
*/
if (restricted && strcmp(cmd, "unrestrict") != 0)
{
pg_log_error("backslash commands are restricted; only \\unrestrict is allowed");
status = PSQL_CMD_ERROR;
}
else
status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
if (status == PSQL_CMD_UNKNOWN)
{
@@ -416,6 +433,8 @@ exec_command(const char *cmd,
status = exec_command_quit(scan_state, active_branch);
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
status = exec_command_reset(scan_state, active_branch, query_buf);
else if (strcmp(cmd, "restrict") == 0)
status = exec_command_restrict(scan_state, active_branch, cmd);
else if (strcmp(cmd, "s") == 0)
status = exec_command_s(scan_state, active_branch);
else if (strcmp(cmd, "sendpipeline") == 0)
@@ -438,6 +457,8 @@ exec_command(const char *cmd,
status = exec_command_T(scan_state, active_branch);
else if (strcmp(cmd, "timing") == 0)
status = exec_command_timing(scan_state, active_branch);
else if (strcmp(cmd, "unrestrict") == 0)
status = exec_command_unrestrict(scan_state, active_branch, cmd);
else if (strcmp(cmd, "unset") == 0)
status = exec_command_unset(scan_state, active_branch, cmd);
else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
@@ -2754,6 +2775,35 @@ exec_command_reset(PsqlScanState scan_state, bool active_branch,
return PSQL_CMD_SKIP_LINE;
}
/*
* \restrict -- enter "restricted mode" with the provided key
*/
static backslashResult
exec_command_restrict(PsqlScanState scan_state, bool active_branch,
const char *cmd)
{
if (active_branch)
{
char *opt;
Assert(!restricted);
opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
if (opt == NULL || opt[0] == '\0')
{
pg_log_error("\\%s: missing required argument", cmd);
return PSQL_CMD_ERROR;
}
restrict_key = pstrdup(opt);
restricted = true;
}
else
ignore_slash_options(scan_state);
return PSQL_CMD_SKIP_LINE;
}
/*
* \s -- save history in a file or show it on the screen
*/
@@ -3135,6 +3185,46 @@ exec_command_timing(PsqlScanState scan_state, bool active_branch)
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
}
/*
* \unrestrict -- exit "restricted mode" if provided key matches
*/
static backslashResult
exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
const char *cmd)
{
if (active_branch)
{
char *opt;
opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
if (opt == NULL || opt[0] == '\0')
{
pg_log_error("\\%s: missing required argument", cmd);
return PSQL_CMD_ERROR;
}
if (!restricted)
{
pg_log_error("\\%s: not currently in restricted mode", cmd);
return PSQL_CMD_ERROR;
}
else if (strcmp(opt, restrict_key) == 0)
{
pfree(restrict_key);
restricted = false;
}
else
{
pg_log_error("\\%s: wrong key", cmd);
return PSQL_CMD_ERROR;
}
}
else
ignore_slash_options(scan_state);
return PSQL_CMD_SKIP_LINE;
}
/*
* \unset -- unset variable
*/