Skip to content

Commit 575f54d

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 9b92f11 commit 575f54d

22 files changed

+433
-13
lines changed

doc/src/sgml/ref/pg_dump.sgml

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

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

97109
<refsect1 id="pg-dump-options">
@@ -1207,6 +1219,29 @@ PostgreSQL documentation
12071219
</listitem>
12081220
</varlistentry>
12091221

1222+
<varlistentry>
1223+
<term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
1224+
<listitem>
1225+
<para>
1226+
Use the provided string as the <application>psql</application>
1227+
<command>\restrict</command> key in the dump output. This can only be
1228+
specified for plain-text dumps, i.e., when <option>--format</option> is
1229+
set to <literal>plain</literal> or the <option>--format</option> option
1230+
is omitted. If no restrict key is specified,
1231+
<application>pg_dump</application> will generate a random one as
1232+
needed. Keys may contain only alphanumeric characters.
1233+
</para>
1234+
<para>
1235+
This option is primarily intended for testing purposes and other
1236+
scenarios that require repeatable output (e.g., comparing dump files).
1237+
It is not recommended for general use, as a malicious server with
1238+
advance knowledge of the key may be able to inject arbitrary code that
1239+
will be executed on the machine that runs
1240+
<application>psql</application> with the dump output.
1241+
</para>
1242+
</listitem>
1243+
</varlistentry>
1244+
12101245
<varlistentry>
12111246
<term><option>--rows-per-insert=<replaceable class="parameter">nrows</replaceable></option></term>
12121247
<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>
@@ -555,6 +565,26 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
555565
</listitem>
556566
</varlistentry>
557567

568+
<varlistentry>
569+
<term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
570+
<listitem>
571+
<para>
572+
Use the provided string as the <application>psql</application>
573+
<command>\restrict</command> key in the dump output. If no restrict
574+
key is specified, <application>pg_dumpall</application> will generate a
575+
random one as needed. Keys may contain only alphanumeric characters.
576+
</para>
577+
<para>
578+
This option is primarily intended for testing purposes and other
579+
scenarios that require repeatable output (e.g., comparing dump files).
580+
It is not recommended for general use, as a malicious server with
581+
advance knowledge of the key may be able to inject arbitrary code that
582+
will be executed on the machine that runs
583+
<application>psql</application> with the dump output.
584+
</para>
585+
</listitem>
586+
</varlistentry>
587+
558588
<varlistentry>
559589
<term><option>--rows-per-insert=<replaceable class="parameter">nrows</replaceable></option></term>
560590
<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">
@@ -755,6 +767,28 @@ PostgreSQL documentation
755767
</listitem>
756768
</varlistentry>
757769

770+
<varlistentry>
771+
<term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
772+
<listitem>
773+
<para>
774+
Use the provided string as the <application>psql</application>
775+
<command>\restrict</command> key in the dump output. This can only be
776+
specified for SQL script output, i.e., when the <option>--file</option>
777+
option is used. If no restrict key is specified,
778+
<application>pg_restore</application> will generate a random one as
779+
needed. Keys may contain only alphanumeric characters.
780+
</para>
781+
<para>
782+
This option is primarily intended for testing purposes and other
783+
scenarios that require repeatable output (e.g., comparing dump files).
784+
It is not recommended for general use, as a malicious server with
785+
advance knowledge of the key may be able to inject arbitrary code that
786+
will be executed on the machine that runs
787+
<application>psql</application> with the dump output.
788+
</para>
789+
</listitem>
790+
</varlistentry>
791+
758792
<varlistentry>
759793
<term><option>--section=<replaceable class="parameter">sectionname</replaceable></option></term>
760794
<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
pg_upgrade 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
@@ -3340,6 +3340,24 @@ lo_import 152801
33403340
</varlistentry>
33413341

33423342

3343+
<varlistentry id="app-psql-meta-command-restrict">
3344+
<term><literal>\restrict <replaceable class="parameter">restrict_key</replaceable></literal></term>
3345+
<listitem>
3346+
<para>
3347+
Enter "restricted" mode with the provided key. In this mode, the only
3348+
allowed meta-command is <command>\unrestrict</command>, to exit
3349+
restricted mode. The key may contain only alphanumeric characters.
3350+
</para>
3351+
<para>
3352+
This command is primarily intended for use in plain-text dumps
3353+
generated by <application>pg_dump</application>,
3354+
<application>pg_dumpall</application>, and
3355+
<application>pg_restore</application>, but it may be useful elsewhere.
3356+
</para>
3357+
</listitem>
3358+
</varlistentry>
3359+
3360+
33433361
<varlistentry id="app-psql-meta-command-s">
33443362
<term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
33453363
<listitem>
@@ -3514,6 +3532,24 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
35143532
</varlistentry>
35153533

35163534

3535+
<varlistentry id="app-psql-meta-command-unrestrict">
3536+
<term><literal>\unrestrict <replaceable class="parameter">restrict_key</replaceable></literal></term>
3537+
<listitem>
3538+
<para>
3539+
Exit "restricted" mode (i.e., where all other meta-commands are
3540+
blocked), provided the specified key matches the one given to
3541+
<command>\restrict</command> when restricted mode was entered.
3542+
</para>
3543+
<para>
3544+
This command is primarily intended for use in plain-text dumps
3545+
generated by <application>pg_dump</application>,
3546+
<application>pg_dumpall</application>, and
3547+
<application>pg_restore</application>, but it may be useful elsewhere.
3548+
</para>
3549+
</listitem>
3550+
</varlistentry>
3551+
3552+
35173553
<varlistentry id="app-psql-meta-command-unset">
35183554
<term><literal>\unset <replaceable class="parameter">name</replaceable></literal></term>
35193555

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@
171171
[
172172
'pg_dumpall', '-f',
173173
$dump1, '--no-sync',
174+
'--restrict-key=test',
174175
'--no-unlogged-table-data', '-d',
175176
$pitr1->connstr('postgres'),
176177
],
@@ -179,6 +180,7 @@
179180
[
180181
'pg_dumpall', '-f',
181182
$dump2, '--no-sync',
183+
'--restrict-key=test',
182184
'--no-unlogged-table-data', '-d',
183185
$pitr2->connstr('postgres'),
184186
],

src/bin/pg_dump/dumputils.c

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

22+
static const char restrict_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
2223

2324
static bool parseAclItem(const char *item, const char *type,
2425
const char *name, const char *subname, int remoteVersion,
@@ -920,3 +921,40 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem,
920921

921922
pg_free(mine);
922923
}
924+
925+
/*
926+
* Generates a valid restrict key (i.e., an alphanumeric string) for use with
927+
* psql's \restrict and \unrestrict meta-commands. For safety, the value is
928+
* chosen at random.
929+
*/
930+
char *
931+
generate_restrict_key(void)
932+
{
933+
uint8 buf[64];
934+
char *ret = palloc(sizeof(buf));
935+
936+
if (!pg_strong_random(buf, sizeof(buf)))
937+
return NULL;
938+
939+
for (int i = 0; i < sizeof(buf) - 1; i++)
940+
{
941+
uint8 idx = buf[i] % strlen(restrict_chars);
942+
943+
ret[i] = restrict_chars[idx];
944+
}
945+
ret[sizeof(buf) - 1] = '\0';
946+
947+
return ret;
948+
}
949+
950+
/*
951+
* Checks that a given restrict key (intended for use with psql's \restrict and
952+
* \unrestrict meta-commands) contains only alphanumeric characters.
953+
*/
954+
bool
955+
valid_restrict_key(const char *restrict_key)
956+
{
957+
return restrict_key != NULL &&
958+
restrict_key[0] != '\0' &&
959+
strspn(restrict_key, restrict_chars) == strlen(restrict_key);
960+
}

src/bin/pg_dump/dumputils.h

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

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

src/bin/pg_dump/pg_backup.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ typedef struct _restoreOptions
157157
int enable_row_security;
158158
int sequence_data; /* dump sequence data even in schema-only mode */
159159
int binary_upgrade;
160+
161+
char *restrict_key;
160162
} RestoreOptions;
161163

162164
typedef struct _dumpOptions
@@ -203,6 +205,8 @@ typedef struct _dumpOptions
203205

204206
int sequence_data; /* dump sequence data even in schema-only mode */
205207
int do_nothing;
208+
209+
char *restrict_key;
206210
} DumpOptions;
207211

208212
/*

src/bin/pg_dump/pg_backup_archiver.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt)
187187
dopt->include_everything = ropt->include_everything;
188188
dopt->enable_row_security = ropt->enable_row_security;
189189
dopt->sequence_data = ropt->sequence_data;
190+
dopt->restrict_key = ropt->restrict_key ? pg_strdup(ropt->restrict_key) : NULL;
190191

191192
return dopt;
192193
}
@@ -451,6 +452,17 @@ RestoreArchive(Archive *AHX)
451452

452453
ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
453454

455+
/*
456+
* If generating plain-text output, enter restricted mode to block any
457+
* unexpected psql meta-commands. A malicious source might try to inject
458+
* a variety of things via bogus responses to queries. While we cannot
459+
* prevent such sources from affecting the destination at restore time, we
460+
* can block psql meta-commands so that the client machine that runs psql
461+
* with the dump output remains unaffected.
462+
*/
463+
if (ropt->restrict_key)
464+
ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
465+
454466
if (AH->archiveRemoteVersion)
455467
ahprintf(AH, "-- Dumped from database version %s\n",
456468
AH->archiveRemoteVersion);
@@ -791,6 +803,14 @@ RestoreArchive(Archive *AHX)
791803

792804
ahprintf(AH, "--\n-- PostgreSQL database dump complete\n--\n\n");
793805

806+
/*
807+
* If generating plain-text output, exit restricted mode at the very end
808+
* of the script. This is not pro forma; in particular, pg_dumpall
809+
* requires this when transitioning from one database to another.
810+
*/
811+
if (ropt->restrict_key)
812+
ahprintf(AH, "\\unrestrict %s\n\n", ropt->restrict_key);
813+
794814
/*
795815
* Clean up & we're done.
796816
*/
@@ -3349,11 +3369,21 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname)
33493369
else
33503370
{
33513371
PQExpBufferData connectbuf;
3372+
RestoreOptions *ropt = AH->public.ropt;
3373+
3374+
/*
3375+
* We must temporarily exit restricted mode for \connect, etc.
3376+
* Anything added between this line and the following \restrict must
3377+
* be careful to avoid any possible meta-command injection vectors.
3378+
*/
3379+
ahprintf(AH, "\\unrestrict %s\n", ropt->restrict_key);
33523380

33533381
initPQExpBuffer(&connectbuf);
33543382
appendPsqlMetaConnect(&connectbuf, dbname);
3355-
ahprintf(AH, "%s\n", connectbuf.data);
3383+
ahprintf(AH, "%s", connectbuf.data);
33563384
termPQExpBuffer(&connectbuf);
3385+
3386+
ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
33573387
}
33583388

33593389
/*

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy