Skip to content

Commit 9751f93

Browse files
committed
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 <robertmhaas@gmail.com> Reviewed-by: Nathan Bossart <nathandbossart@gmail.com> Backpatch-through: 13 Security: CVE-2025-8715
1 parent a4f513b commit 9751f93

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
@@ -74,7 +74,6 @@ static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
7474
SetupWorkerPtrType setupWorkerPtr);
7575
static void _getObjectDescription(PQExpBuffer buf, TocEntry *te);
7676
static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
77-
static char *sanitize_line(const char *str, bool want_hyphen);
7877
static void _doSetFixedOutputState(ArchiveHandle *AH);
7978
static void _doSetSessionAuth(ArchiveHandle *AH, const char *user);
8079
static void _reconnectToDB(ArchiveHandle *AH, const char *dbname);
@@ -3747,42 +3746,6 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
37473746
}
37483747
}
37493748

3750-
/*
3751-
* Sanitize a string to be included in an SQL comment or TOC listing, by
3752-
* replacing any newlines with spaces. This ensures each logical output line
3753-
* is in fact one physical output line, to prevent corruption of the dump
3754-
* (which could, in the worst case, present an SQL injection vulnerability
3755-
* if someone were to incautiously load a dump containing objects with
3756-
* maliciously crafted names).
3757-
*
3758-
* The result is a freshly malloc'd string. If the input string is NULL,
3759-
* return a malloc'ed empty string, unless want_hyphen, in which case return a
3760-
* malloc'ed hyphen.
3761-
*
3762-
* Note that we currently don't bother to quote names, meaning that the name
3763-
* fields aren't automatically parseable. "pg_restore -L" doesn't care because
3764-
* it only examines the dumpId field, but someday we might want to try harder.
3765-
*/
3766-
static char *
3767-
sanitize_line(const char *str, bool want_hyphen)
3768-
{
3769-
char *result;
3770-
char *s;
3771-
3772-
if (!str)
3773-
return pg_strdup(want_hyphen ? "-" : "");
3774-
3775-
result = pg_strdup(str);
3776-
3777-
for (s = result; *s != '\0'; s++)
3778-
{
3779-
if (*s == '\n' || *s == '\r')
3780-
*s = ' ';
3781-
}
3782-
3783-
return result;
3784-
}
3785-
37863749
/*
37873750
* Write the file header for a custom-format archive
37883751
*/

src/bin/pg_dump/pg_dump.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2513,11 +2513,14 @@ dumpTableData(Archive *fout, const TableDataInfo *tdinfo)
25132513
forcePartitionRootLoad(tbinfo)))
25142514
{
25152515
TableInfo *parentTbinfo;
2516+
char *sanitized;
25162517

25172518
parentTbinfo = getRootTableInfo(tbinfo);
25182519
copyFrom = fmtQualifiedDumpable(parentTbinfo);
2520+
sanitized = sanitize_line(copyFrom, true);
25192521
printfPQExpBuffer(copyBuf, "-- load via partition root %s",
2520-
copyFrom);
2522+
sanitized);
2523+
free(sanitized);
25212524
tdDefn = pg_strdup(copyBuf->data);
25222525
}
25232526
else

src/bin/pg_dump/pg_dumpall.c

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

12561256
if (PQntuples(res) > 0)
1257-
fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", username);
1257+
{
1258+
char *sanitized;
1259+
1260+
sanitized = sanitize_line(username, true);
1261+
fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", sanitized);
1262+
free(sanitized);
1263+
}
12581264

12591265
for (int i = 0; i < PQntuples(res); i++)
12601266
{
@@ -1356,6 +1362,7 @@ dumpDatabases(PGconn *conn)
13561362
for (i = 0; i < PQntuples(res); i++)
13571363
{
13581364
char *dbname = PQgetvalue(res, i, 0);
1365+
char *sanitized;
13591366
const char *create_opts;
13601367
int ret;
13611368

@@ -1372,7 +1379,9 @@ dumpDatabases(PGconn *conn)
13721379

13731380
pg_log_info("dumping database \"%s\"", dbname);
13741381

1375-
fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", dbname);
1382+
sanitized = sanitize_line(dbname, true);
1383+
fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", sanitized);
1384+
free(sanitized);
13761385

13771386
/*
13781387
* 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
@@ -1575,6 +1575,27 @@
15751575
},
15761576
},
15771577

1578+
'newline of role or table name in comment' => {
1579+
create_sql => qq{CREATE ROLE regress_newline;
1580+
ALTER ROLE regress_newline SET enable_seqscan = off;
1581+
ALTER ROLE regress_newline
1582+
RENAME TO "regress_newline\nattack";
1583+
1584+
-- meet getPartitioningInfo() "unsafe" condition
1585+
CREATE TYPE pp_colors AS
1586+
ENUM ('green', 'blue', 'black');
1587+
CREATE TABLE pp_enumpart (a pp_colors)
1588+
PARTITION BY HASH (a);
1589+
CREATE TABLE pp_enumpart1 PARTITION OF pp_enumpart
1590+
FOR VALUES WITH (MODULUS 2, REMAINDER 0);
1591+
CREATE TABLE pp_enumpart2 PARTITION OF pp_enumpart
1592+
FOR VALUES WITH (MODULUS 2, REMAINDER 1);
1593+
ALTER TABLE pp_enumpart
1594+
RENAME TO "pp_enumpart\nattack";},
1595+
regexp => qr/\n--[^\n]*\nattack/s,
1596+
like => {},
1597+
},
1598+
15781599
'CREATE DATABASE regression_invalid...' => {
15791600
create_order => 1,
15801601
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)
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