This is an automated email from the ASF dual-hosted git repository.

reshke pushed a commit to branch REL_2_STABLE
in repository https://gitbox.apache.org/repos/asf/cloudberry.git

commit 16201010cd1294dbe77f5c99be11e0b135c8117a
Author: Noah Misch <[email protected]>
AuthorDate: Mon Aug 11 06:18:59 2025 -0700

    Convert newlines to spaces in names written in v11+ pg_dump comments.
    
    Maliciously-crafted object names could achieve SQL injection during
    restore.  CVE-2012-0868 fixed this class of problem at the time, but
    later work reintroduced three cases.  Commit
    bc8cd50fefd369b217f80078585c486505aafb62 (back-patched to v11+ in
    2023-05 releases) introduced the pg_dump case.  Commit
    6cbdbd9e8d8f2986fde44f2431ed8d0c8fce7f5d (v12+) introduced the two
    pg_dumpall cases.  Move sanitize_line(), unchanged, to dumputils.c so
    pg_dumpall has access to it in all supported versions.  Back-patch to
    v13 (all supported versions).
    
    Reviewed-by: Robert Haas <[email protected]>
    Reviewed-by: Nathan Bossart <[email protected]>
    Backpatch-through: 13
    Security: CVE-2025-8715
---
 src/bin/pg_dump/dumputils.c                  | 37 ++++++++++++++++++++++++++++
 src/bin/pg_dump/dumputils.h                  |  1 +
 src/bin/pg_dump/pg_backup_archiver.c         | 37 ----------------------------
 src/bin/pg_dump/pg_dump.c                    |  5 +++-
 src/bin/pg_dump/pg_dumpall.c                 | 11 +++++++--
 src/bin/pg_dump/t/002_pg_dump.pl             | 21 ++++++++++++++++
 src/bin/pg_dump/t/003_pg_dump_with_server.pl | 19 ++++++++++++--
 7 files changed, 89 insertions(+), 42 deletions(-)

diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index a481687cd5b..1385d9fa8f1 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -29,6 +29,43 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
                                   const char *subname);
 
 
+/*
+ * Sanitize a string to be included in an SQL comment or TOC listing, by
+ * replacing any newlines with spaces.  This ensures each logical output line
+ * is in fact one physical output line, to prevent corruption of the dump
+ * (which could, in the worst case, present an SQL injection vulnerability
+ * if someone were to incautiously load a dump containing objects with
+ * maliciously crafted names).
+ *
+ * The result is a freshly malloc'd string.  If the input string is NULL,
+ * return a malloc'ed empty string, unless want_hyphen, in which case return a
+ * malloc'ed hyphen.
+ *
+ * Note that we currently don't bother to quote names, meaning that the name
+ * fields aren't automatically parseable.  "pg_restore -L" doesn't care because
+ * it only examines the dumpId field, but someday we might want to try harder.
+ */
+char *
+sanitize_line(const char *str, bool want_hyphen)
+{
+       char       *result;
+       char       *s;
+
+       if (!str)
+               return pg_strdup(want_hyphen ? "-" : "");
+
+       result = pg_strdup(str);
+
+       for (s = result; *s != '\0'; s++)
+       {
+               if (*s == '\n' || *s == '\r')
+                       *s = ' ';
+       }
+
+       return result;
+}
+
+
 /*
  * Build GRANT/REVOKE command(s) for an object.
  *
diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h
index 7b5e282f6cf..8e7728150cc 100644
--- a/src/bin/pg_dump/dumputils.h
+++ b/src/bin/pg_dump/dumputils.h
@@ -35,6 +35,7 @@
 #define PGDUMP_STRFTIME_FMT  "%Y-%m-%d %H:%M:%S"
 #endif
 
+extern char *sanitize_line(const char *str, bool want_hyphen);
 extern bool buildACLCommands(const char *name, const char *subname, const char 
*nspname,
                                                         const char *type, 
const char *acls, const char *racls,
                                                         const char *owner, 
const char *prefix, int remoteVersion,
diff --git a/src/bin/pg_dump/pg_backup_archiver.c 
b/src/bin/pg_dump/pg_backup_archiver.c
index 70131b7af63..591264869f1 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -76,7 +76,6 @@ static ArchiveHandle *_allocAH(const char *FileSpec, const 
ArchiveFormat fmt,
                                                           SetupWorkerPtrType 
setupWorkerPtr);
 static void _getObjectDescription(PQExpBuffer buf, TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
-static char *sanitize_line(const char *str, bool want_hyphen);
 static void _doSetFixedOutputState(ArchiveHandle *AH);
 static void _doSetSessionAuth(ArchiveHandle *AH, const char *user);
 static void _reconnectToDB(ArchiveHandle *AH, const char *dbname);
@@ -3772,42 +3771,6 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool 
isData)
        }
 }
 
-/*
- * Sanitize a string to be included in an SQL comment or TOC listing, by
- * replacing any newlines with spaces.  This ensures each logical output line
- * is in fact one physical output line, to prevent corruption of the dump
- * (which could, in the worst case, present an SQL injection vulnerability
- * if someone were to incautiously load a dump containing objects with
- * maliciously crafted names).
- *
- * The result is a freshly malloc'd string.  If the input string is NULL,
- * return a malloc'ed empty string, unless want_hyphen, in which case return a
- * malloc'ed hyphen.
- *
- * Note that we currently don't bother to quote names, meaning that the name
- * fields aren't automatically parseable.  "pg_restore -L" doesn't care because
- * it only examines the dumpId field, but someday we might want to try harder.
- */
-static char *
-sanitize_line(const char *str, bool want_hyphen)
-{
-       char       *result;
-       char       *s;
-
-       if (!str)
-               return pg_strdup(want_hyphen ? "-" : "");
-
-       result = pg_strdup(str);
-
-       for (s = result; *s != '\0'; s++)
-       {
-               if (*s == '\n' || *s == '\r')
-                       *s = ' ';
-       }
-
-       return result;
-}
-
 /*
  * Write the file header for a custom-format archive
  */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8a2095b21a3..625cc45fc2f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2803,11 +2803,14 @@ dumpTableData(Archive *fout, const TableDataInfo 
*tdinfo)
                 forcePartitionRootLoad(tbinfo)))
        {
                TableInfo  *parentTbinfo;
+               char       *sanitized;
 
                parentTbinfo = getRootTableInfo(tbinfo);
                copyFrom = fmtQualifiedDumpable(parentTbinfo);
+               sanitized = sanitize_line(copyFrom, true);
                printfPQExpBuffer(copyBuf, "-- load via partition root %s",
-                                                 copyFrom);
+                                                 sanitized);
+               free(sanitized);
                tdDefn = pg_strdup(copyBuf->data);
        }
        else
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index b193448d2a4..7f2ff68a955 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1953,6 +1953,8 @@ dumpUserConfig(PGconn *conn, const char *username)
                if (PQntuples(res) == 1 &&
                        !PQgetisnull(res, 0, 0))
                {
+                       char       *sanitized;
+
                        /* comment at section start, only if needed */
                        if (first)
                        {
@@ -1960,7 +1962,9 @@ dumpUserConfig(PGconn *conn, const char *username)
                                first = false;
                        }
 
-                       fprintf(OPF, "--\n-- User Config \"%s\"\n--\n\n", 
username);
+                       sanitized = sanitize_line(username, true);
+                       fprintf(OPF, "--\n-- User Config \"%s\"\n--\n\n", 
sanitized);
+                       free(sanitized);
                        resetPQExpBuffer(buf);
                        makeAlterConfigCommand(conn, PQgetvalue(res, 0, 0),
                                                                   "ROLE", 
username, NULL, NULL,
@@ -2330,6 +2334,7 @@ dumpDatabases(PGconn *conn)
        for (i = 0; i < PQntuples(res); i++)
        {
                char       *dbname = PQgetvalue(res, i, 0);
+               char       *sanitized;
                const char *create_opts;
                int                     ret;
 
@@ -2346,7 +2351,9 @@ dumpDatabases(PGconn *conn)
 
                pg_log_info("dumping database \"%s\"", dbname);
 
-               fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", dbname);
+               sanitized = sanitize_line(dbname, true);
+               fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", sanitized);
+               free(sanitized);
 
                /*
                 * We assume that "template1" and "postgres" already exist in 
the
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 5b85ffa49fc..d669bd9b09f 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -1450,6 +1450,27 @@ my %tests = (
                },
        },
 
+       'newline of role or table name in comment' => {
+               create_sql => qq{CREATE ROLE regress_newline;
+                                                ALTER ROLE regress_newline SET 
enable_seqscan = off;
+                                                ALTER ROLE regress_newline
+                                                       RENAME TO 
"regress_newline\nattack";
+
+                                                -- meet getPartitioningInfo() 
"unsafe" condition
+                                                CREATE TYPE pp_colors AS
+                                                       ENUM ('green', 'blue', 
'black');
+                                                CREATE TABLE pp_enumpart (a 
pp_colors)
+                                                       PARTITION BY HASH (a);
+                                                CREATE TABLE pp_enumpart1 
PARTITION OF pp_enumpart
+                                                       FOR VALUES WITH 
(MODULUS 2, REMAINDER 0);
+                                                CREATE TABLE pp_enumpart2 
PARTITION OF pp_enumpart
+                                                       FOR VALUES WITH 
(MODULUS 2, REMAINDER 1);
+                                                ALTER TABLE pp_enumpart
+                                                       RENAME TO 
"pp_enumpart\nattack";},
+               regexp => qr/\n--[^\n]*\nattack/s,
+               like => {},
+       },
+
        'CREATE ACCESS METHOD gist2' => {
                create_order => 52,
                create_sql =>
diff --git a/src/bin/pg_dump/t/003_pg_dump_with_server.pl 
b/src/bin/pg_dump/t/003_pg_dump_with_server.pl
index f9fea9ddcfe..18177232fa9 100644
--- a/src/bin/pg_dump/t/003_pg_dump_with_server.pl
+++ b/src/bin/pg_dump/t/003_pg_dump_with_server.pl
@@ -6,7 +6,7 @@ use warnings;
 
 use PostgresNode;
 use TestLib;
-use Test::More tests => 3;
+use Test::More tests => 6;
 
 my $tempdir       = TestLib::tempdir;
 my $tempdir_short = TestLib::tempdir_short;
@@ -17,6 +17,22 @@ my $port = $node->port;
 $node->init;
 $node->start;
 
+#########################################
+# pg_dumpall: newline in database name
+
+$node->safe_psql('postgres', qq{CREATE DATABASE "regress_\nattack"});
+
+my (@cmd, $stdout, $stderr);
+@cmd = ("pg_dumpall", '--port' => $port, '--exclude-database=postgres');
+print("# Running: " . join(" ", @cmd) . "\n");
+my $result = IPC::Run::run \@cmd, '>' => \$stdout, '2>' => \$stderr;
+ok(!$result, "newline in dbname: exit code not 0");
+like(
+       $stderr,
+       qr/shell command argument contains a newline/,
+       "newline in dbname: stderr matches");
+unlike($stdout, qr/^attack/m, "newline in dbname: no comment escape");
+
 #########################################
 # Verify that dumping foreign data includes only foreign tables of
 # matching servers
@@ -27,7 +43,6 @@ $node->safe_psql('postgres', "CREATE SERVER s1 FOREIGN DATA 
WRAPPER dummy");
 $node->safe_psql('postgres', "CREATE SERVER s2 FOREIGN DATA WRAPPER dummy");
 $node->safe_psql('postgres', "CREATE FOREIGN TABLE t0 (a int) SERVER s0");
 $node->safe_psql('postgres', "CREATE FOREIGN TABLE t1 (a int) SERVER s1");
-my ($cmd, $stdout, $stderr, $result);
 
 command_fails_like(
        [ "pg_dump", '-p', $port, '--include-foreign-data=s0', 'postgres' ],


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to