Skip to content

Commit 71ea0d6

Browse files
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
1 parent 70693c6 commit 71ea0d6

22 files changed

+435
-13
lines changed

doc/src/sgml/ref/pg_dump.sgml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ PostgreSQL documentation
9696
light of the limitations listed below.
9797
</para>
9898

99+
<warning>
100+
<para>
101+
Restoring a dump causes the destination to execute arbitrary code of the
102+
source superusers' choice. Partial dumps and partial restores do not limit
103+
that. If the source superusers are not trusted, the dumped SQL statements
104+
must be inspected before restoring. Non-plain-text dumps can be inspected
105+
by using <application>pg_restore</application>'s <option>--file</option>
106+
option. Note that the client running the dump and restore need not trust
107+
the source or destination superusers.
108+
</para>
109+
</warning>
110+
99111
</refsect1>
100112

101113
<refsect1 id="pg-dump-options">
@@ -1252,6 +1264,29 @@ PostgreSQL documentation
12521264
</listitem>
12531265
</varlistentry>
12541266

1267+
<varlistentry>
1268+
<term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
1269+
<listitem>
1270+
<para>
1271+
Use the provided string as the <application>psql</application>
1272+
<command>\restrict</command> key in the dump output. This can only be
1273+
specified for plain-text dumps, i.e., when <option>--format</option> is
1274+
set to <literal>plain</literal> or the <option>--format</option> option
1275+
is omitted. If no restrict key is specified,
1276+
<application>pg_dump</application> will generate a random one as
1277+
needed. Keys may contain only alphanumeric characters.
1278+
</para>
1279+
<para>
1280+
This option is primarily intended for testing purposes and other
1281+
scenarios that require repeatable output (e.g., comparing dump files).
1282+
It is not recommended for general use, as a malicious server with
1283+
advance knowledge of the key may be able to inject arbitrary code that
1284+
will be executed on the machine that runs
1285+
<application>psql</application> with the dump output.
1286+
</para>
1287+
</listitem>
1288+
</varlistentry>
1289+
12551290
<varlistentry>
12561291
<term><option>--rows-per-insert=<replaceable class="parameter">nrows</replaceable></option></term>
12571292
<listitem>

doc/src/sgml/ref/pg_dumpall.sgml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ PostgreSQL documentation
6666
linkend="libpq-pgpass"/> for more information.
6767
</para>
6868

69+
<warning>
70+
<para>
71+
Restoring a dump causes the destination to execute arbitrary code of the
72+
source superusers' choice. Partial dumps and partial restores do not limit
73+
that. If the source superusers are not trusted, the dumped SQL statements
74+
must be inspected before restoring. Note that the client running the dump
75+
and restore need not trust the source or destination superusers.
76+
</para>
77+
</warning>
78+
6979
</refsect1>
7080

7181
<refsect1>
@@ -591,6 +601,26 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
591601
</listitem>
592602
</varlistentry>
593603

604+
<varlistentry>
605+
<term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
606+
<listitem>
607+
<para>
608+
Use the provided string as the <application>psql</application>
609+
<command>\restrict</command> key in the dump output. If no restrict
610+
key is specified, <application>pg_dumpall</application> will generate a
611+
random one as needed. Keys may contain only alphanumeric characters.
612+
</para>
613+
<para>
614+
This option is primarily intended for testing purposes and other
615+
scenarios that require repeatable output (e.g., comparing dump files).
616+
It is not recommended for general use, as a malicious server with
617+
advance knowledge of the key may be able to inject arbitrary code that
618+
will be executed on the machine that runs
619+
<application>psql</application> with the dump output.
620+
</para>
621+
</listitem>
622+
</varlistentry>
623+
594624
<varlistentry>
595625
<term><option>--rows-per-insert=<replaceable class="parameter">nrows</replaceable></option></term>
596626
<listitem>

doc/src/sgml/ref/pg_restore.sgml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,18 @@ PostgreSQL documentation
6868
<application>pg_restore</application> will not be able to load the data
6969
using <command>COPY</command> statements.
7070
</para>
71+
72+
<warning>
73+
<para>
74+
Restoring a dump causes the destination to execute arbitrary code of the
75+
source superusers' choice. Partial dumps and partial restores do not limit
76+
that. If the source superusers are not trusted, the dumped SQL statements
77+
must be inspected before restoring. Non-plain-text dumps can be inspected
78+
by using <application>pg_restore</application>'s <option>--file</option>
79+
option. Note that the client running the dump and restore need not trust
80+
the source or destination superusers.
81+
</para>
82+
</warning>
7183
</refsect1>
7284

7385
<refsect1 id="app-pgrestore-options">
@@ -796,6 +808,28 @@ PostgreSQL documentation
796808
</listitem>
797809
</varlistentry>
798810

811+
<varlistentry>
812+
<term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
813+
<listitem>
814+
<para>
815+
Use the provided string as the <application>psql</application>
816+
<command>\restrict</command> key in the dump output. This can only be
817+
specified for SQL script output, i.e., when the <option>--file</option>
818+
option is used. If no restrict key is specified,
819+
<application>pg_restore</application> will generate a random one as
820+
needed. Keys may contain only alphanumeric characters.
821+
</para>
822+
<para>
823+
This option is primarily intended for testing purposes and other
824+
scenarios that require repeatable output (e.g., comparing dump files).
825+
It is not recommended for general use, as a malicious server with
826+
advance knowledge of the key may be able to inject arbitrary code that
827+
will be executed on the machine that runs
828+
<application>psql</application> with the dump output.
829+
</para>
830+
</listitem>
831+
</varlistentry>
832+
799833
<varlistentry>
800834
<term><option>--section=<replaceable class="parameter">sectionname</replaceable></option></term>
801835
<listitem>

doc/src/sgml/ref/pgupgrade.sgml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ PostgreSQL documentation
7070
<application>pg_upgrade</application> supports upgrades from 9.2.X and later to the current
7171
major release of <productname>PostgreSQL</productname>, including snapshot and beta releases.
7272
</para>
73+
74+
<warning>
75+
<para>
76+
Upgrading a cluster causes the destination to execute arbitrary code of the
77+
source superusers' choice. Ensure that the source superusers are trusted
78+
before upgrading.
79+
</para>
80+
</warning>
7381
</refsect1>
7482

7583
<refsect1>

doc/src/sgml/ref/psql-ref.sgml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3551,6 +3551,24 @@ SELECT $1 \parse stmt1
35513551
</varlistentry>
35523552

35533553

3554+
<varlistentry id="app-psql-meta-command-restrict">
3555+
<term><literal>\restrict <replaceable class="parameter">restrict_key</replaceable></literal></term>
3556+
<listitem>
3557+
<para>
3558+
Enter "restricted" mode with the provided key. In this mode, the only
3559+
allowed meta-command is <command>\unrestrict</command>, to exit
3560+
restricted mode. The key may contain only alphanumeric characters.
3561+
</para>
3562+
<para>
3563+
This command is primarily intended for use in plain-text dumps
3564+
generated by <application>pg_dump</application>,
3565+
<application>pg_dumpall</application>, and
3566+
<application>pg_restore</application>, but it may be useful elsewhere.
3567+
</para>
3568+
</listitem>
3569+
</varlistentry>
3570+
3571+
35543572
<varlistentry id="app-psql-meta-command-s">
35553573
<term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
35563574
<listitem>
@@ -3802,6 +3820,24 @@ SELECT 1 \bind \sendpipeline
38023820
</varlistentry>
38033821

38043822

3823+
<varlistentry id="app-psql-meta-command-unrestrict">
3824+
<term><literal>\unrestrict <replaceable class="parameter">restrict_key</replaceable></literal></term>
3825+
<listitem>
3826+
<para>
3827+
Exit "restricted" mode (i.e., where all other meta-commands are
3828+
blocked), provided the specified key matches the one given to
3829+
<command>\restrict</command> when restricted mode was entered.
3830+
</para>
3831+
<para>
3832+
This command is primarily intended for use in plain-text dumps
3833+
generated by <application>pg_dump</application>,
3834+
<application>pg_dumpall</application>, and
3835+
<application>pg_restore</application>, but it may be useful elsewhere.
3836+
</para>
3837+
</listitem>
3838+
</varlistentry>
3839+
3840+
38053841
<varlistentry id="app-psql-meta-command-unset">
38063842
<term><literal>\unset <replaceable class="parameter">name</replaceable></literal></term>
38073843

src/bin/pg_combinebackup/t/002_compare_backups.pl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
$pitr1->command_ok(
175175
[
176176
'pg_dumpall',
177+
'--restrict-key=test',
177178
'--no-sync',
178179
'--no-unlogged-table-data',
179180
'--file' => $dump1,
@@ -183,6 +184,7 @@
183184
$pitr2->command_ok(
184185
[
185186
'pg_dumpall',
187+
'--restrict-key=test',
186188
'--no-sync',
187189
'--no-unlogged-table-data',
188190
'--file' => $dump2,

src/bin/pg_dump/dumputils.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "dumputils.h"
2222
#include "fe_utils/string_utils.h"
2323

24+
static const char restrict_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
2425

2526
static bool parseAclItem(const char *item, const char *type,
2627
const char *name, const char *subname, int remoteVersion,
@@ -957,3 +958,40 @@ create_or_open_dir(const char *dirname)
957958
pg_fatal("directory \"%s\" is not empty", dirname);
958959
}
959960
}
961+
962+
/*
963+
* Generates a valid restrict key (i.e., an alphanumeric string) for use with
964+
* psql's \restrict and \unrestrict meta-commands. For safety, the value is
965+
* chosen at random.
966+
*/
967+
char *
968+
generate_restrict_key(void)
969+
{
970+
uint8 buf[64];
971+
char *ret = palloc(sizeof(buf));
972+
973+
if (!pg_strong_random(buf, sizeof(buf)))
974+
return NULL;
975+
976+
for (int i = 0; i < sizeof(buf) - 1; i++)
977+
{
978+
uint8 idx = buf[i] % strlen(restrict_chars);
979+
980+
ret[i] = restrict_chars[idx];
981+
}
982+
ret[sizeof(buf) - 1] = '\0';
983+
984+
return ret;
985+
}
986+
987+
/*
988+
* Checks that a given restrict key (intended for use with psql's \restrict and
989+
* \unrestrict meta-commands) contains only alphanumeric characters.
990+
*/
991+
bool
992+
valid_restrict_key(const char *restrict_key)
993+
{
994+
return restrict_key != NULL &&
995+
restrict_key[0] != '\0' &&
996+
strspn(restrict_key, restrict_chars) == strlen(restrict_key);
997+
}

src/bin/pg_dump/dumputils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,7 @@ extern void makeAlterConfigCommand(PGconn *conn, const char *configitem,
6565
PQExpBuffer buf);
6666
extern void create_or_open_dir(const char *dirname);
6767

68+
extern char *generate_restrict_key(void);
69+
extern bool valid_restrict_key(const char *restrict_key);
70+
6871
#endif /* DUMPUTILS_H */

src/bin/pg_dump/pg_backup.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ typedef struct _restoreOptions
163163
bool dumpSchema;
164164
bool dumpData;
165165
bool dumpStatistics;
166+
167+
char *restrict_key;
166168
} RestoreOptions;
167169

168170
typedef struct _dumpOptions
@@ -213,6 +215,8 @@ typedef struct _dumpOptions
213215
bool dumpSchema;
214216
bool dumpData;
215217
bool dumpStatistics;
218+
219+
char *restrict_key;
216220
} DumpOptions;
217221

218222
/*

src/bin/pg_dump/pg_backup_archiver.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt)
197197
dopt->include_everything = ropt->include_everything;
198198
dopt->enable_row_security = ropt->enable_row_security;
199199
dopt->sequence_data = ropt->sequence_data;
200+
dopt->restrict_key = ropt->restrict_key ? pg_strdup(ropt->restrict_key) : NULL;
200201

201202
return dopt;
202203
}
@@ -461,6 +462,17 @@ RestoreArchive(Archive *AHX)
461462

462463
ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
463464

465+
/*
466+
* If generating plain-text output, enter restricted mode to block any
467+
* unexpected psql meta-commands. A malicious source might try to inject
468+
* a variety of things via bogus responses to queries. While we cannot
469+
* prevent such sources from affecting the destination at restore time, we
470+
* can block psql meta-commands so that the client machine that runs psql
471+
* with the dump output remains unaffected.
472+
*/
473+
if (ropt->restrict_key)
474+
ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
475+
464476
if (AH->archiveRemoteVersion)
465477
ahprintf(AH, "-- Dumped from database version %s\n",
466478
AH->archiveRemoteVersion);
@@ -801,6 +813,14 @@ RestoreArchive(Archive *AHX)
801813

802814
ahprintf(AH, "--\n-- PostgreSQL database dump complete\n--\n\n");
803815

816+
/*
817+
* If generating plain-text output, exit restricted mode at the very end
818+
* of the script. This is not pro forma; in particular, pg_dumpall
819+
* requires this when transitioning from one database to another.
820+
*/
821+
if (ropt->restrict_key)
822+
ahprintf(AH, "\\unrestrict %s\n\n", ropt->restrict_key);
823+
804824
/*
805825
* Clean up & we're done.
806826
*/
@@ -3452,11 +3472,21 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname)
34523472
else
34533473
{
34543474
PQExpBufferData connectbuf;
3475+
RestoreOptions *ropt = AH->public.ropt;
3476+
3477+
/*
3478+
* We must temporarily exit restricted mode for \connect, etc.
3479+
* Anything added between this line and the following \restrict must
3480+
* be careful to avoid any possible meta-command injection vectors.
3481+
*/
3482+
ahprintf(AH, "\\unrestrict %s\n", ropt->restrict_key);
34553483

34563484
initPQExpBuffer(&connectbuf);
34573485
appendPsqlMetaConnect(&connectbuf, dbname);
3458-
ahprintf(AH, "%s\n", connectbuf.data);
3486+
ahprintf(AH, "%s", connectbuf.data);
34593487
termPQExpBuffer(&connectbuf);
3488+
3489+
ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
34603490
}
34613491

34623492
/*

0 commit comments

Comments
 (0)