summaryrefslogtreecommitdiff
path: root/src/bin/psql/command.c
diff options
context:
space:
mode:
authorNathan Bossart <nathan@postgresql.org>2025-08-11 09:00:00 -0500
committerNathan Bossart <nathan@postgresql.org>2025-08-11 09:00:00 -0500
commit71ea0d6795438f95f4ee6e35867058c44b270d51 (patch)
treebeaa29a50fdee7dec68151f94179549ccee58b7b /src/bin/psql/command.c
parent70693c645f6e490b9ed450e8611e94ab7af3aad2 (diff)
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
Diffstat (limited to 'src/bin/psql/command.c')
-rw-r--r--src/bin/psql/command.c94
1 files changed, 92 insertions, 2 deletions
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 0e00d73487c..cc602087db2 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -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)
@@ -2755,6 +2776,35 @@ exec_command_reset(PsqlScanState scan_state, bool active_branch,
}
/*
+ * \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
*/
static backslashResult
@@ -3136,6 +3186,46 @@ exec_command_timing(PsqlScanState scan_state, bool active_branch)
}
/*
+ * \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
*/
static backslashResult