Restrict psql meta-commands in plain-text dumps.
authorNathan Bossart <nathan@postgresql.org>
Mon, 11 Aug 2025 14:00:00 +0000 (09:00 -0500)
committerNathan Bossart <nathan@postgresql.org>
Mon, 11 Aug 2025 14:00:00 +0000 (09:00 -0500)
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

19 files changed:
doc/src/sgml/ref/pg_dump.sgml
doc/src/sgml/ref/pg_dumpall.sgml
doc/src/sgml/ref/pg_restore.sgml
doc/src/sgml/ref/pgupgrade.sgml
doc/src/sgml/ref/psql-ref.sgml
src/bin/pg_dump/dumputils.c
src/bin/pg_dump/dumputils.h
src/bin/pg_dump/pg_backup.h
src/bin/pg_dump/pg_backup_archiver.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dumpall.c
src/bin/pg_dump/pg_restore.c
src/bin/pg_dump/t/002_pg_dump.pl
src/bin/pg_upgrade/test.sh
src/bin/psql/command.c
src/bin/psql/help.c
src/bin/psql/tab-complete.c
src/test/regress/expected/psql.out
src/test/regress/sql/psql.sql

index 0ad307592fd37503c22a842d8df9b31e83d2fc09..19c7006b73302c17cb70b1dd751827613477c2ca 100644 (file)
@@ -92,6 +92,18 @@ PostgreSQL documentation
    light of the limitations listed below.
   </para>
 
+  <warning>
+   <para>
+    Restoring a dump causes the destination to execute arbitrary code of the
+    source superusers' choice.  Partial dumps and partial restores do not limit
+    that.  If the source superusers are not trusted, the dumped SQL statements
+    must be inspected before restoring.  Non-plain-text dumps can be inspected
+    by using <application>pg_restore</application>'s <option>--file</option>
+    option.  Note that the client running the dump and restore need not trust
+    the source or destination superusers.
+   </para>
+  </warning>
+
  </refsect1>
 
  <refsect1 id="pg-dump-options">
@@ -984,6 +996,29 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
+      <listitem>
+       <para>
+        Use the provided string as the <application>psql</application>
+        <command>\restrict</command> key in the dump output.  This can only be
+        specified for plain-text dumps, i.e., when <option>--format</option> is
+        set to <literal>plain</literal> or the <option>--format</option> option
+        is omitted.  If no restrict key is specified,
+        <application>pg_dump</application> will generate a random one as
+        needed.  Keys may contain only alphanumeric characters.
+       </para>
+       <para>
+        This option is primarily intended for testing purposes and other
+        scenarios that require repeatable output (e.g., comparing dump files).
+        It is not recommended for general use, as a malicious server with
+        advance knowledge of the key may be able to inject arbitrary code that
+        will be executed on the machine that runs
+        <application>psql</application> with the dump output.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--rows-per-insert=<replaceable class="parameter">nrows</replaceable></option></term>
       <listitem>
index e629c1d0469a700bdd00983d650d17ded611f433..70d94c29cf94d6396c2cb98c3b1d80a1a7812724 100644 (file)
@@ -65,6 +65,16 @@ PostgreSQL documentation
   linkend="libpq-pgpass"/> for more information.
   </para>
 
+  <warning>
+   <para>
+    Restoring a dump causes the destination to execute arbitrary code of the
+    source superusers' choice.  Partial dumps and partial restores do not limit
+    that.  If the source superusers are not trusted, the dumped SQL statements
+    must be inspected before restoring.  Note that the client running the dump
+    and restore need not trust the source or destination superusers.
+   </para>
+  </warning>
+
  </refsect1>
 
  <refsect1>
@@ -501,6 +511,26 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
+      <listitem>
+       <para>
+        Use the provided string as the <application>psql</application>
+        <command>\restrict</command> key in the dump output.  If no restrict
+        key is specified, <application>pg_dumpall</application> will generate a
+        random one as needed.  Keys may contain only alphanumeric characters.
+       </para>
+       <para>
+        This option is primarily intended for testing purposes and other
+        scenarios that require repeatable output (e.g., comparing dump files).
+        It is not recommended for general use, as a malicious server with
+        advance knowledge of the key may be able to inject arbitrary code that
+        will be executed on the machine that runs
+        <application>psql</application> with the dump output.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--rows-per-insert=<replaceable class="parameter">nrows</replaceable></option></term>
       <listitem>
index f8e7351b82b7c15c4535b92092a05174a6c36e41..9e54a2f545a636fbd47cefb347a8c1b0b868c4fb 100644 (file)
@@ -68,6 +68,18 @@ PostgreSQL documentation
    <application>pg_restore</application> will not be able to load the data
    using <command>COPY</command> statements.
   </para>
+
+  <warning>
+   <para>
+    Restoring a dump causes the destination to execute arbitrary code of the
+    source superusers' choice.  Partial dumps and partial restores do not limit
+    that.  If the source superusers are not trusted, the dumped SQL statements
+    must be inspected before restoring.  Non-plain-text dumps can be inspected
+    by using <application>pg_restore</application>'s <option>--file</option>
+    option.  Note that the client running the dump and restore need not trust
+    the source or destination superusers.
+   </para>
+  </warning>
  </refsect1>
 
  <refsect1 id="app-pgrestore-options">
@@ -659,6 +671,28 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
+      <listitem>
+       <para>
+        Use the provided string as the <application>psql</application>
+        <command>\restrict</command> key in the dump output.  This can only be
+        specified for SQL script output, i.e., when the <option>--file</option>
+        option is used.  If no restrict key is specified,
+        <application>pg_restore</application> will generate a random one as
+        needed.  Keys may contain only alphanumeric characters.
+       </para>
+       <para>
+        This option is primarily intended for testing purposes and other
+        scenarios that require repeatable output (e.g., comparing dump files).
+        It is not recommended for general use, as a malicious server with
+        advance knowledge of the key may be able to inject arbitrary code that
+        will be executed on the machine that runs
+        <application>psql</application> with the dump output.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
        <term><option>--section=<replaceable class="parameter">sectionname</replaceable></option></term>
        <listitem>
index dfa61e957485c2540d67e71e69d757e23ed48b50..640176f4d3e4502cc84380fddf9b349d86612fa7 100644 (file)
@@ -70,6 +70,14 @@ PostgreSQL documentation
    pg_upgrade supports upgrades from 9.0.X and later to the current
    major release of <productname>PostgreSQL</productname>, including snapshot and beta releases.
   </para>
+
+  <warning>
+   <para>
+    Upgrading a cluster causes the destination to execute arbitrary code of the
+    source superusers' choice.  Ensure that the source superusers are trusted
+    before upgrading.
+   </para>
+  </warning>
  </refsect1>
 
  <refsect1>
index 97a18a5196fc096868c38f7da8bf4016da4ebac4..50b879292bfc8cde50badf4a1c5705517e7e3f63 100644 (file)
@@ -3147,6 +3147,24 @@ lo_import 152801
       </varlistentry>
 
 
+      <varlistentry>
+        <term><literal>\restrict <replaceable class="parameter">restrict_key</replaceable></literal></term>
+        <listitem>
+        <para>
+        Enter "restricted" mode with the provided key.  In this mode, the only
+        allowed meta-command is <command>\unrestrict</command>, to exit
+        restricted mode.  The key may contain only alphanumeric characters.
+        </para>
+        <para>
+        This command is primarily intended for use in plain-text dumps
+        generated by <application>pg_dump</application>,
+        <application>pg_dumpall</application>, and
+        <application>pg_restore</application>, but it may be useful elsewhere.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
       <varlistentry>
         <term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
         <listitem>
@@ -3321,6 +3339,24 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
       </varlistentry>
 
 
+      <varlistentry>
+        <term><literal>\unrestrict <replaceable class="parameter">restrict_key</replaceable></literal></term>
+        <listitem>
+        <para>
+        Exit "restricted" mode (i.e., where all other meta-commands are
+        blocked), provided the specified key matches the one given to
+        <command>\restrict</command> when restricted mode was entered.
+        </para>
+        <para>
+        This command is primarily intended for use in plain-text dumps
+        generated by <application>pg_dump</application>,
+        <application>pg_dumpall</application>, and
+        <application>pg_restore</application>, but it may be useful elsewhere.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
       <varlistentry>
         <term><literal>\unset <replaceable class="parameter">name</replaceable></literal></term>
 
index 19ed4842f18f8a032f0508170dac2200273559bc..5106769068548c8dbc5c748e96ced5b2a4ebf0be 100644 (file)
@@ -19,6 +19,7 @@
 #include "dumputils.h"
 #include "fe_utils/string_utils.h"
 
+static const char restrict_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
 
 static bool parseAclItem(const char *item, const char *type,
                         const char *name, const char *subname, int remoteVersion,
@@ -1079,3 +1080,40 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem,
 
    pg_free(mine);
 }
+
+/*
+ * Generates a valid restrict key (i.e., an alphanumeric string) for use with
+ * psql's \restrict and \unrestrict meta-commands.  For safety, the value is
+ * chosen at random.
+ */
+char *
+generate_restrict_key(void)
+{
+   uint8       buf[64];
+   char       *ret = palloc(sizeof(buf));
+
+   if (!pg_strong_random(buf, sizeof(buf)))
+       return NULL;
+
+   for (int i = 0; i < sizeof(buf) - 1; i++)
+   {
+       uint8       idx = buf[i] % strlen(restrict_chars);
+
+       ret[i] = restrict_chars[idx];
+   }
+   ret[sizeof(buf) - 1] = '\0';
+
+   return ret;
+}
+
+/*
+ * Checks that a given restrict key (intended for use with psql's \restrict and
+ * \unrestrict meta-commands) contains only alphanumeric characters.
+ */
+bool
+valid_restrict_key(const char *restrict_key)
+{
+   return restrict_key != NULL &&
+       restrict_key[0] != '\0' &&
+       strspn(restrict_key, restrict_chars) == strlen(restrict_key);
+}
index 950cdb5f57989e706bf8b7c9d6f5acc452eb4100..ddc28c2f3aa6ee19950758b7d2791109809e4d9a 100644 (file)
@@ -67,4 +67,7 @@ extern void makeAlterConfigCommand(PGconn *conn, const char *configitem,
                                   const char *type2, const char *name2,
                                   PQExpBuffer buf);
 
+extern char *generate_restrict_key(void);
+extern bool valid_restrict_key(const char *restrict_key);
+
 #endif                         /* DUMPUTILS_H */
index 4c4e968a2fd9c77da07c16569647d5053a0c9869..05aecd29ecba8740bb44e8252a550befd6c0bd3d 100644 (file)
@@ -134,6 +134,8 @@ typedef struct _restoreOptions
    int         enable_row_security;
    int         sequence_data;  /* dump sequence data even in schema-only mode */
    int         binary_upgrade;
+
+   char       *restrict_key;
 } RestoreOptions;
 
 typedef struct _dumpOptions
@@ -179,6 +181,8 @@ typedef struct _dumpOptions
 
    int         sequence_data;  /* dump sequence data even in schema-only mode */
    int         do_nothing;
+
+   char       *restrict_key;
 } DumpOptions;
 
 /*
index 3bbd3cb888047707ddf5a8f41868255955444d58..3f62166055ba047769c385125c0e0353e38478c3 100644 (file)
@@ -207,6 +207,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt)
    dopt->include_everything = ropt->include_everything;
    dopt->enable_row_security = ropt->enable_row_security;
    dopt->sequence_data = ropt->sequence_data;
+   dopt->restrict_key = ropt->restrict_key ? pg_strdup(ropt->restrict_key) : NULL;
 
    return dopt;
 }
@@ -463,6 +464,17 @@ RestoreArchive(Archive *AHX)
 
    ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
 
+   /*
+    * If generating plain-text output, enter restricted mode to block any
+    * unexpected psql meta-commands.  A malicious source might try to inject
+    * a variety of things via bogus responses to queries.  While we cannot
+    * prevent such sources from affecting the destination at restore time, we
+    * can block psql meta-commands so that the client machine that runs psql
+    * with the dump output remains unaffected.
+    */
+   if (ropt->restrict_key)
+       ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
+
    if (AH->archiveRemoteVersion)
        ahprintf(AH, "-- Dumped from database version %s\n",
                 AH->archiveRemoteVersion);
@@ -734,6 +746,14 @@ RestoreArchive(Archive *AHX)
 
    ahprintf(AH, "--\n-- PostgreSQL database dump complete\n--\n\n");
 
+   /*
+    * If generating plain-text output, exit restricted mode at the very end
+    * of the script. This is not pro forma; in particular, pg_dumpall
+    * requires this when transitioning from one database to another.
+    */
+   if (ropt->restrict_key)
+       ahprintf(AH, "\\unrestrict %s\n\n", ropt->restrict_key);
+
    /*
     * Clean up & we're done.
     */
@@ -3239,11 +3259,21 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname)
    else
    {
        PQExpBufferData connectbuf;
+       RestoreOptions *ropt = AH->public.ropt;
+
+       /*
+        * We must temporarily exit restricted mode for \connect, etc.
+        * Anything added between this line and the following \restrict must
+        * be careful to avoid any possible meta-command injection vectors.
+        */
+       ahprintf(AH, "\\unrestrict %s\n", ropt->restrict_key);
 
        initPQExpBuffer(&connectbuf);
        appendPsqlMetaConnect(&connectbuf, dbname);
-       ahprintf(AH, "%s\n", connectbuf.data);
+       ahprintf(AH, "%s", connectbuf.data);
        termPQExpBuffer(&connectbuf);
+
+       ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
    }
 
    /*
index 96aac99a944d228c66ca1c198716d734cfddeee1..161dd5ca1ea48b6c573078e10f1cacd96f6b3b17 100644 (file)
@@ -388,6 +388,7 @@ main(int argc, char **argv)
        {"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
        {"rows-per-insert", required_argument, NULL, 10},
        {"include-foreign-data", required_argument, NULL, 11},
+       {"restrict-key", required_argument, NULL, 25},
 
        {NULL, 0, NULL, 0}
    };
@@ -605,6 +606,10 @@ main(int argc, char **argv)
                                          optarg);
                break;
 
+           case 25:
+               dopt.restrict_key = pg_strdup(optarg);
+               break;
+
            default:
                fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
                exit_nicely(1);
@@ -673,8 +678,22 @@ main(int argc, char **argv)
 
    /* archiveFormat specific setup */
    if (archiveFormat == archNull)
+   {
        plainText = 1;
 
+       /*
+        * If you don't provide a restrict key, one will be appointed for you.
+        */
+       if (!dopt.restrict_key)
+           dopt.restrict_key = generate_restrict_key();
+       if (!dopt.restrict_key)
+           fatal("could not generate restrict key");
+       if (!valid_restrict_key(dopt.restrict_key))
+           fatal("invalid restrict key");
+   }
+   else if (dopt.restrict_key)
+       fatal("option --restrict-key can only be used with --format=plain");
+
    /* Custom and directory formats are compressed by default, others not */
    if (compressLevel == -1)
    {
@@ -949,6 +968,7 @@ main(int argc, char **argv)
    ropt->enable_row_security = dopt.enable_row_security;
    ropt->sequence_data = dopt.sequence_data;
    ropt->binary_upgrade = dopt.binary_upgrade;
+   ropt->restrict_key = dopt.restrict_key ? pg_strdup(dopt.restrict_key) : NULL;
 
    if (compressLevel == -1)
        ropt->compression = 0;
@@ -1044,6 +1064,7 @@ help(const char *progname)
    printf(_("  --no-unlogged-table-data     do not dump unlogged table data\n"));
    printf(_("  --on-conflict-do-nothing     add ON CONFLICT DO NOTHING to INSERT commands\n"));
    printf(_("  --quote-all-identifiers      quote all identifiers, even if not key words\n"));
+   printf(_("  --restrict-key=RESTRICT_KEY  use provided string as psql \\restrict key\n"));
    printf(_("  --rows-per-insert=NROWS      number of rows per INSERT; implies --inserts\n"));
    printf(_("  --section=SECTION            dump named section (pre-data, data, or post-data)\n"));
    printf(_("  --serializable-deferrable    wait until the dump can run without anomalies\n"));
index 7537922a8f30559fba7612333b8025d4c74e9f37..94824c57e83a78c63690fe3d4c15cc1a893c77cb 100644 (file)
@@ -92,6 +92,8 @@ static char *filename = NULL;
 static SimpleStringList database_exclude_patterns = {NULL, NULL};
 static SimpleStringList database_exclude_names = {NULL, NULL};
 
+static char *restrict_key;
+
 #define exit_nicely(code) exit(code)
 
 int
@@ -146,6 +148,7 @@ main(int argc, char *argv[])
        {"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
        {"on-conflict-do-nothing", no_argument, &on_conflict_do_nothing, 1},
        {"rows-per-insert", required_argument, NULL, 7},
+       {"restrict-key", required_argument, NULL, 9},
 
        {NULL, 0, NULL, 0}
    };
@@ -334,6 +337,12 @@ main(int argc, char *argv[])
                appendShellString(pgdumpopts, optarg);
                break;
 
+           case 9:
+               restrict_key = pg_strdup(optarg);
+               appendPQExpBufferStr(pgdumpopts, " --restrict-key ");
+               appendShellString(pgdumpopts, optarg);
+               break;
+
            default:
                fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
                exit_nicely(1);
@@ -432,6 +441,22 @@ main(int argc, char *argv[])
    if (on_conflict_do_nothing)
        appendPQExpBufferStr(pgdumpopts, " --on-conflict-do-nothing");
 
+   /*
+    * If you don't provide a restrict key, one will be appointed for you.
+    */
+   if (!restrict_key)
+       restrict_key = generate_restrict_key();
+   if (!restrict_key)
+   {
+       pg_log_error("could not generate restrict key");
+       exit_nicely(1);
+   }
+   if (!valid_restrict_key(restrict_key))
+   {
+       pg_log_error("invalid restrict key");
+       exit_nicely(1);
+   }
+
    /*
     * If there was a database specified on the command line, use that,
     * otherwise try to connect to database "postgres", and failing that
@@ -530,6 +555,16 @@ main(int argc, char *argv[])
    if (verbose)
        dumpTimestamp("Started on");
 
+   /*
+    * Enter restricted mode to block any unexpected psql meta-commands.  A
+    * malicious source might try to inject a variety of things via bogus
+    * responses to queries.  While we cannot prevent such sources from
+    * affecting the destination at restore time, we can block psql
+    * meta-commands so that the client machine that runs psql with the dump
+    * output remains unaffected.
+    */
+   fprintf(OPF, "\\restrict %s\n\n", restrict_key);
+
    /*
     * We used to emit \connect postgres here, but that served no purpose
     * other than to break things for installations without a postgres
@@ -589,6 +624,12 @@ main(int argc, char *argv[])
            dumpTablespaces(conn);
    }
 
+   /*
+    * Exit restricted mode just before dumping the databases.  pg_dump will
+    * handle entering restricted mode again as appropriate.
+    */
+   fprintf(OPF, "\\unrestrict %s\n\n", restrict_key);
+
    if (!globals_only && !roles_only && !tablespaces_only)
        dumpDatabases(conn);
 
@@ -654,6 +695,7 @@ help(void)
    printf(_("  --no-unlogged-table-data     do not dump unlogged table data\n"));
    printf(_("  --on-conflict-do-nothing     add ON CONFLICT DO NOTHING to INSERT commands\n"));
    printf(_("  --quote-all-identifiers      quote all identifiers, even if not key words\n"));
+   printf(_("  --restrict-key=RESTRICT_KEY  use provided string as psql \\restrict key\n"));
    printf(_("  --rows-per-insert=NROWS      number of rows per INSERT; implies --inserts\n"));
    printf(_("  --use-set-session-authorization\n"
             "                               use SET SESSION AUTHORIZATION commands instead of\n"
index ebdc50d921c9676092d1fec5489f93327380a085..f883839bda8e98ee91ed522a4905509895d82117 100644 (file)
@@ -120,6 +120,7 @@ main(int argc, char **argv)
        {"no-publications", no_argument, &no_publications, 1},
        {"no-security-labels", no_argument, &no_security_labels, 1},
        {"no-subscriptions", no_argument, &no_subscriptions, 1},
+       {"restrict-key", required_argument, NULL, 6},
 
        {NULL, 0, NULL, 0}
    };
@@ -280,6 +281,10 @@ main(int argc, char **argv)
                set_dump_section(optarg, &(opts->dumpSections));
                break;
 
+           case 6:
+               opts->restrict_key = pg_strdup(optarg);
+               break;
+
            default:
                fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
                exit_nicely(1);
@@ -319,8 +324,33 @@ main(int argc, char **argv)
                    progname);
            exit_nicely(1);
        }
+
+       if (opts->restrict_key)
+       {
+           pg_log_error("options -d/--dbname and --restrict-key cannot be used together");
+           exit_nicely(1);
+       }
+
        opts->useDB = 1;
    }
+   else
+   {
+       /*
+        * If you don't provide a restrict key, one will be appointed for you.
+        */
+       if (!opts->restrict_key)
+           opts->restrict_key = generate_restrict_key();
+       if (!opts->restrict_key)
+       {
+           pg_log_error("could not generate restrict key");
+           exit_nicely(1);
+       }
+       if (!valid_restrict_key(opts->restrict_key))
+       {
+           pg_log_error("invalid restrict key");
+           exit_nicely(1);
+       }
+   }
 
    if (opts->dataOnly && opts->schemaOnly)
    {
@@ -500,6 +530,7 @@ usage(const char *progname)
    printf(_("  --no-security-labels         do not restore security labels\n"));
    printf(_("  --no-subscriptions           do not restore subscriptions\n"));
    printf(_("  --no-tablespaces             do not restore tablespace assignments\n"));
+   printf(_("  --restrict-key=RESTRICT_KEY  use provided string as psql \\restrict key\n"));
    printf(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
    printf(_("  --strict-names               require table and/or schema include patterns to\n"
             "                               match at least one entity each\n"));
index cb83714b017eecc6ddd332ba627f88bf7baad579..bc28c03448aba63cf9d7e4e51a52d3f9c41f8d83 100644 (file)
@@ -411,6 +411,16 @@ my %full_runs = (
 
 # This is where the actual tests are defined.
 my %tests = (
+   'restrict' => {
+       all_runs => 1,
+       regexp => qr/^\\restrict [a-zA-Z0-9]+$/m,
+   },
+
+   'unrestrict' => {
+       all_runs => 1,
+       regexp => qr/^\\unrestrict [a-zA-Z0-9]+$/m,
+   },
+
    'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT' => {
        create_order => 14,
        create_sql   => 'ALTER DEFAULT PRIVILEGES
@@ -2860,7 +2870,6 @@ my %tests = (
    },
 
    'ALTER TABLE measurement PRIMARY KEY' => {
-       all_runs     => 1,
        catch_all    => 'CREATE ... commands',
        create_order => 93,
        create_sql =>
@@ -2897,7 +2906,6 @@ my %tests = (
    },
 
    'ALTER INDEX ... ATTACH PARTITION (primary key)' => {
-       all_runs  => 1,
        catch_all => 'CREATE ... commands',
        regexp    => qr/^
        \QALTER INDEX dump_test.measurement_pkey ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_pkey\E
@@ -3775,9 +3783,10 @@ foreach my $run (sort keys %pgdump_runs)
            next;
        }
 
-       # Run the test listed as a like, unless it is specifically noted
-       # as an unlike (generally due to an explicit exclusion or similar).
-       if ($tests{$test}->{like}->{$test_key}
+       # Run the test if all_runs is set or if listed as a like, unless it is
+       # specifically noted as an unlike (generally due to an explicit
+       # exclusion or similar).
+       if (($tests{$test}->{like}->{$test_key} || $tests{$test}->{all_runs})
            && !defined($tests{$test}->{unlike}->{$test_key}))
        {
            if (!ok($output_file =~ $tests{$test}->{regexp},
index 9f6fb3e01857f46592606a2b24d40f2d7fbdd235..0dbb92d0d3230b202bd5d15f9fec5d068ee42cb8 100644 (file)
@@ -191,6 +191,7 @@ if "$MAKE" -C "$oldsrc" installcheck-parallel; then
    fi
 
    pg_dumpall $extra_dump_options --no-sync \
+       --restrict-key=test \
        -f "$temp_root"/dump1.sql || pg_dumpall1_status=$?
 
    if [ "$newsrc" != "$oldsrc" ]; then
@@ -262,6 +263,7 @@ case $testhost in
 esac
 
 pg_dumpall $extra_dump_options --no-sync \
+   --restrict-key=test \
    -f "$temp_root"/dump2.sql || pg_dumpall2_status=$?
 pg_ctl -m fast stop
 
index a00d1b86785dd6ad8dc8543643025937aca78775..7eda42a44d2a42d092f4175b0b475a261f624019 100644 (file)
@@ -114,6 +114,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_set(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch,
@@ -123,6 +125,8 @@ static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_
 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,
@@ -172,6 +176,8 @@ static char *pset_value_string(const char *param, printQueryOpt *popt);
 static void checkWin32Codepage(void);
 #endif
 
+static bool restricted;
+static char *restrict_key;
 
 
 /*----------
@@ -217,8 +223,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)
    {
@@ -375,6 +392,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, "set") == 0)
@@ -391,6 +410,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)
@@ -2190,6 +2211,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
  */
@@ -2508,6 +2558,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
  */
index 3cca4e937668cb9303d7b8201ea9ddd53ac8e3f5..732e449e7beb4897665e46267c759d374f00cbde 100644 (file)
@@ -182,6 +182,10 @@ slashUsage(unsigned short int pager)
    fprintf(output, _("  \\gset [PREFIX]         execute query and store results in psql variables\n"));
    fprintf(output, _("  \\gx [(OPTIONS)] [FILE] as \\g, but forces expanded output mode\n"));
    fprintf(output, _("  \\q                     quit psql\n"));
+   fprintf(output, _("  \\restrict RESTRICT_KEY\n"
+                     "                         enter restricted mode with provided key\n"));
+   fprintf(output, _("  \\unrestrict RESTRICT_KEY\n"
+                     "                         exit restricted mode if key matches\n"));
    fprintf(output, _("  \\watch [SEC]           execute query every SEC seconds\n"));
    fprintf(output, "\n");
 
index afd9669296d043768353d000b56eac555826a0a6..53797a8f92a4f2b809577c9448f31f18b19dfed7 100644 (file)
@@ -1527,10 +1527,10 @@ psql_completion(const char *text, int start, int end)
        "\\o",
        "\\p", "\\password", "\\prompt", "\\pset",
        "\\q", "\\qecho",
-       "\\r",
+       "\\r", "\\restrict",
        "\\s", "\\set", "\\setenv", "\\sf", "\\sv",
        "\\t", "\\T", "\\timing",
-       "\\unset",
+       "\\unrestrict", "\\unset",
        "\\x",
        "\\w", "\\warn", "\\watch",
        "\\z",
index 9a7d43397aea3b752f5d436f691f3da19c3758ef..8c893b14c91d14e35e65502e9b2a506251c672db 100644 (file)
@@ -4433,6 +4433,7 @@ invalid command \lo
    \pset arg1 arg2
    \q
    \reset
+   \restrict test
    \s arg1
    \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
    \setenv arg1 arg2
@@ -4441,6 +4442,7 @@ invalid command \lo
    \t arg1
    \T arg1
    \timing arg1
+   \unrestrict not_valid
    \unset arg1
    \w arg1
    \watch arg1
index e6edf1ca0d11a8e32f49e48cab43bb2ff0c1435f..3eae271ef8937995002be1ea5ac3d726116b0ee2 100644 (file)
@@ -976,6 +976,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
    \pset arg1 arg2
    \q
    \reset
+   \restrict test
    \s arg1
    \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
    \setenv arg1 arg2
@@ -984,6 +985,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
    \t arg1
    \T arg1
    \timing arg1
+   \unrestrict not_valid
    \unset arg1
    \w arg1
    \watch arg1