Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 86b3e2b

Browse files
nmischartemgavrilov
authored andcommitted
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 bc8cd50 (back-patched to v11+ in 2023-05 releases) introduced the pg_dump case. Commit 6cbdbd9 (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
1 parent b0c0089 commit 86b3e2b

File tree

7 files changed

+90
-41
lines changed

7 files changed

+90
-41
lines changed

src/bin/pg_dump/dumputils.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,43 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
2929
const char *subname);
3030

3131

32+
/*
33+
* Sanitize a string to be included in an SQL comment or TOC listing, by
34+
* replacing any newlines with spaces. This ensures each logical output line
35+
* is in fact one physical output line, to prevent corruption of the dump
36+
* (which could, in the worst case, present an SQL injection vulnerability
37+
* if someone were to incautiously load a dump containing objects with
38+
* maliciously crafted names).
39+
*
40+
* The result is a freshly malloc'd string. If the input string is NULL,
41+
* return a malloc'ed empty string, unless want_hyphen, in which case return a
42+
* malloc'ed hyphen.
43+
*
44+
* Note that we currently don't bother to quote names, meaning that the name
45+
* fields aren't automatically parseable. "pg_restore -L" doesn't care because
46+
* it only examines the dumpId field, but someday we might want to try harder.
47+
*/
48+
char *
49+
sanitize_line(const char *str, bool want_hyphen)
50+
{
51+
char *result;
52+
char *s;
53+
54+
if (!str)
55+
return pg_strdup(want_hyphen ? "-" : "");
56+
57+
result = pg_strdup(str);
58+
59+
for (s = result; *s != '\0'; s++)
60+
{
61+
if (*s == '\n' || *s == '\r')
62+
*s = ' ';
63+
}
64+
65+
return result;
66+
}
67+
68+
3269
/*
3370
* Build GRANT/REVOKE command(s) for an object.
3471
*

src/bin/pg_dump/dumputils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#endif
3737

3838

39+
extern char *sanitize_line(const char *str, bool want_hyphen);
3940
extern bool buildACLCommands(const char *name, const char *subname, const char *nspname,
4041
const char *type, const char *acls, const char *baseacls,
4142
const char *owner, const char *prefix, int remoteVersion,

src/bin/pg_dump/pg_backup_archiver.c

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
5454
DataDirSyncMethod sync_method);
5555
static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te);
5656
static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
57-
static char *sanitize_line(const char *str, bool want_hyphen);
5857
static void _doSetFixedOutputState(ArchiveHandle *AH);
5958
static void _doSetSessionAuth(ArchiveHandle *AH, const char *user);
6059
static void _reconnectToDB(ArchiveHandle *AH, const char *dbname);
@@ -3915,42 +3914,6 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
39153914
}
39163915
}
39173916

3918-
/*
3919-
* Sanitize a string to be included in an SQL comment or TOC listing, by
3920-
* replacing any newlines with spaces. This ensures each logical output line
3921-
* is in fact one physical output line, to prevent corruption of the dump
3922-
* (which could, in the worst case, present an SQL injection vulnerability
3923-
* if someone were to incautiously load a dump containing objects with
3924-
* maliciously crafted names).
3925-
*
3926-
* The result is a freshly malloc'd string. If the input string is NULL,
3927-
* return a malloc'ed empty string, unless want_hyphen, in which case return a
3928-
* malloc'ed hyphen.
3929-
*
3930-
* Note that we currently don't bother to quote names, meaning that the name
3931-
* fields aren't automatically parseable. "pg_restore -L" doesn't care because
3932-
* it only examines the dumpId field, but someday we might want to try harder.
3933-
*/
3934-
static char *
3935-
sanitize_line(const char *str, bool want_hyphen)
3936-
{
3937-
char *result;
3938-
char *s;
3939-
3940-
if (!str)
3941-
return pg_strdup(want_hyphen ? "-" : "");
3942-
3943-
result = pg_strdup(str);
3944-
3945-
for (s = result; *s != '\0'; s++)
3946-
{
3947-
if (*s == '\n' || *s == '\r')
3948-
*s = ' ';
3949-
}
3950-
3951-
return result;
3952-
}
3953-
39543917
/*
39553918
* Write the file header for a custom-format archive
39563919
*/

src/bin/pg_dump/pg_dump.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2657,11 +2657,14 @@ dumpTableData(Archive *fout, const TableDataInfo *tdinfo)
26572657
forcePartitionRootLoad(tbinfo)))
26582658
{
26592659
TableInfo *parentTbinfo;
2660+
char *sanitized;
26602661

26612662
parentTbinfo = getRootTableInfo(tbinfo);
26622663
copyFrom = fmtQualifiedDumpable(parentTbinfo);
2664+
sanitized = sanitize_line(copyFrom, true);
26632665
printfPQExpBuffer(copyBuf, "-- load via partition root %s",
2664-
copyFrom);
2666+
sanitized);
2667+
free(sanitized);
26652668
tdDefn = pg_strdup(copyBuf->data);
26662669
}
26672670
else

src/bin/pg_dump/pg_dumpall.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,7 +1462,13 @@ dumpUserConfig(PGconn *conn, const char *username)
14621462
res = executeQuery(conn, buf->data);
14631463

14641464
if (PQntuples(res) > 0)
1465-
fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", username);
1465+
{
1466+
char *sanitized;
1467+
1468+
sanitized = sanitize_line(username, true);
1469+
fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", sanitized);
1470+
free(sanitized);
1471+
}
14661472

14671473
for (int i = 0; i < PQntuples(res); i++)
14681474
{
@@ -1564,6 +1570,7 @@ dumpDatabases(PGconn *conn)
15641570
for (i = 0; i < PQntuples(res); i++)
15651571
{
15661572
char *dbname = PQgetvalue(res, i, 0);
1573+
char *sanitized;
15671574
const char *create_opts;
15681575
int ret;
15691576

@@ -1580,7 +1587,9 @@ dumpDatabases(PGconn *conn)
15801587

15811588
pg_log_info("dumping database \"%s\"", dbname);
15821589

1583-
fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", dbname);
1590+
sanitized = sanitize_line(dbname, true);
1591+
fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", sanitized);
1592+
free(sanitized);
15841593

15851594
/*
15861595
* We assume that "template1" and "postgres" already exist in the

src/bin/pg_dump/t/002_pg_dump.pl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,6 +1893,27 @@
18931893
},
18941894
},
18951895

1896+
'newline of role or table name in comment' => {
1897+
create_sql => qq{CREATE ROLE regress_newline;
1898+
ALTER ROLE regress_newline SET enable_seqscan = off;
1899+
ALTER ROLE regress_newline
1900+
RENAME TO "regress_newline\nattack";
1901+
1902+
-- meet getPartitioningInfo() "unsafe" condition
1903+
CREATE TYPE pp_colors AS
1904+
ENUM ('green', 'blue', 'black');
1905+
CREATE TABLE pp_enumpart (a pp_colors)
1906+
PARTITION BY HASH (a);
1907+
CREATE TABLE pp_enumpart1 PARTITION OF pp_enumpart
1908+
FOR VALUES WITH (MODULUS 2, REMAINDER 0);
1909+
CREATE TABLE pp_enumpart2 PARTITION OF pp_enumpart
1910+
FOR VALUES WITH (MODULUS 2, REMAINDER 1);
1911+
ALTER TABLE pp_enumpart
1912+
RENAME TO "pp_enumpart\nattack";},
1913+
regexp => qr/\n--[^\n]*\nattack/s,
1914+
like => {},
1915+
},
1916+
18961917
'CREATE TABLESPACE regress_dump_tablespace' => {
18971918
create_order => 2,
18981919
create_sql => q(

src/bin/pg_dump/t/003_pg_dump_with_server.pl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@
1616
$node->init;
1717
$node->start;
1818

19+
#########################################
20+
# pg_dumpall: newline in database name
21+
22+
$node->safe_psql('postgres', qq{CREATE DATABASE "regress_\nattack"});
23+
24+
my (@cmd, $stdout, $stderr);
25+
@cmd = ("pg_dumpall", '--port' => $port, '--exclude-database=postgres');
26+
print("# Running: " . join(" ", @cmd) . "\n");
27+
my $result = IPC::Run::run \@cmd, '>' => \$stdout, '2>' => \$stderr;
28+
ok(!$result, "newline in dbname: exit code not 0");
29+
like(
30+
$stderr,
31+
qr/shell command argument contains a newline/,
32+
"newline in dbname: stderr matches");
33+
unlike($stdout, qr/^attack/m, "newline in dbname: no comment escape");
34+
1935
#########################################
2036
# Verify that dumping foreign data includes only foreign tables of
2137
# matching servers
@@ -26,7 +42,6 @@
2642
$node->safe_psql('postgres', "CREATE SERVER s2 FOREIGN DATA WRAPPER dummy");
2743
$node->safe_psql('postgres', "CREATE FOREIGN TABLE t0 (a int) SERVER s0");
2844
$node->safe_psql('postgres', "CREATE FOREIGN TABLE t1 (a int) SERVER s1");
29-
my ($cmd, $stdout, $stderr, $result);
3045

3146
command_fails_like(
3247
[ "pg_dump", '-p', $port, '--include-foreign-data=s0', 'postgres' ],

0 commit comments

Comments
 (0)