From 330b84d8c40864007833e05dc9d849c4bda77240 Mon Sep 17 00:00:00 2001 From: Stephen Frost Date: Mon, 6 Mar 2017 23:29:02 -0500 Subject: [PATCH] pg_dump: Properly handle public schema ACLs with --clean pg_dump has always handled the public schema in a special way when it comes to the "--clean" option. To wit, we do not drop or recreate the public schema in "normal" mode, but when we are run in "--clean" mode then we do drop and recreate the public schema. When running in "--clean" mode, the public schema is dropped and then recreated and it is recreated with the normal schema-default privileges of "nothing". This is unlike how the public schema starts life, which is to have CREATE and USAGE GRANT'd to the PUBLIC role, and that is what is recorded in pg_init_privs. Due to this, in "--clean" mode, pg_dump would mistakenly only dump out the set of privileges required to go from the initdb-time privileges on the public schema to whatever the current-state privileges are. If the privileges were not changed from initdb time, then no privileges would be dumped out for the public schema, but with the schema being dropped and recreated, the result was that the public schema would have no ACLs on it instead of what it should have, which is the initdb-time privileges. Practically speaking, this meant that pg_dump with --clean mode dumping a database where the ACLs on the public schema were not changed from the default would, upon restore, result in a public schema with *no* privileges GRANT'd, not matching the state of the existing database (where the initdb-time privileges would have been CREATE and USAGE to the PUBLIC role for the public schema). To fix, adjust the query in getNamespaces() to ignore the pg_init_privs entry for the public schema when running in "--clean" mode, meaning that the privileges for the public schema would be dumped, correctly, as if it was going from a newly-created schema to the current state (which is, indeed, what will happen during the restore thanks to the DROP/CREATE). Only the public schema is handled in this special way by pg_dump, no other initdb-time objects are dropped/recreated in --clean mode. Back-patch to 9.6 where the bug was introduced. Discussion: https://postgr.es/m/3534542.o3cNaKiDID%40techfox --- src/bin/pg_dump/pg_dump.c | 22 +++++++++++++++++++++- src/bin/pg_dump/t/002_pg_dump.pl | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 71461b339d..c7876fedd2 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4005,13 +4005,33 @@ getNamespaces(Archive *fout, int *numNamespaces) "LEFT JOIN pg_init_privs pip " "ON (n.oid = pip.objoid " "AND pip.classoid = 'pg_namespace'::regclass " - "AND pip.objsubid = 0) ", + "AND pip.objsubid = 0", username_subquery, acl_subquery->data, racl_subquery->data, init_acl_subquery->data, init_racl_subquery->data); + /* + * When we are doing a 'clean' run, we will be dropping and recreating + * the 'public' schema (the only object which has that kind of + * treatment in the backend and which has an entry in pg_init_privs) + * and therefore we should not consider any initial privileges in + * pg_init_privs in that case. + * + * See pg_backup_archiver.c:_printTocEntry() for the details on why + * the public schema is special in this regard. + * + * Note that if the public schema is dropped and re-created, this is + * essentially a no-op because the new public schema won't have an + * entry in pg_init_privs anyway, as the entry will be removed when + * the public schema is dropped. + */ + if (dopt->outputClean) + appendPQExpBuffer(query," AND pip.objoid <> 'public'::regnamespace"); + + appendPQExpBuffer(query,") "); + destroyPQExpBuffer(acl_subquery); destroyPQExpBuffer(racl_subquery); destroyPQExpBuffer(init_acl_subquery); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index c51088a49c..b554dcd55e 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -3081,6 +3081,34 @@ qr/^GRANT SELECT ON TABLE test_third_table TO regress_dump_test_role;/m, role => 1, test_schema_plus_blobs => 1, }, }, + 'GRANT USAGE ON SCHEMA public TO public' => { + regexp => qr/^ + \Q--\E\n\n + \QGRANT USAGE ON SCHEMA public TO PUBLIC;\E + /xm, + like => { + clean => 1, + clean_if_exists => 1, }, + unlike => { + binary_upgrade => 1, + createdb => 1, + defaults => 1, + exclude_dump_test_schema => 1, + exclude_test_table => 1, + exclude_test_table_data => 1, + no_blobs => 1, + no_owner => 1, + pg_dumpall_dbprivs => 1, + schema_only => 1, + section_pre_data => 1, + only_dump_test_schema => 1, + only_dump_test_table => 1, + pg_dumpall_globals_clean => 1, + role => 1, + section_data => 1, + section_post_data => 1, + test_schema_plus_blobs => 1, }, }, + 'GRANT commands' => { # catch-all for GRANT commands all_runs => 0, # catch-all regexp => qr/^GRANT /m, @@ -3258,8 +3286,6 @@ qr/^GRANT SELECT ON TABLE test_third_table TO regress_dump_test_role;/m, /xm, like => { binary_upgrade => 1, - clean => 1, - clean_if_exists => 1, createdb => 1, defaults => 1, exclude_dump_test_schema => 1, @@ -3271,6 +3297,8 @@ qr/^GRANT SELECT ON TABLE test_third_table TO regress_dump_test_role;/m, schema_only => 1, section_pre_data => 1, }, unlike => { + clean => 1, + clean_if_exists => 1, only_dump_test_schema => 1, only_dump_test_table => 1, pg_dumpall_globals_clean => 1,