1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-21 15:54:08 +03:00

Fix pg_dumpall to cope with dangling OIDs in pg_auth_members.

There is a race condition between "GRANT role" and "DROP ROLE",
which allows GRANT to install pg_auth_members entries that refer to
dropped roles.  (Commit 6566133c5 prevented that for the grantor
field, but not for the granted or grantee roles.)  We'll soon fix
that, at least in HEAD, but pg_dumpall needs to cope with the
situation in case of pre-existing inconsistency.  As pg_dumpall
stands, it will emit invalid commands like 'GRANT foo TO ""',
which causes pg_upgrade to fail.  Fix it to emit warnings and skip
those GRANTs, instead.

There was some discussion of removing the problem by changing
dumpRoleMembership's query to use JOIN not LEFT JOIN, but that
would result in silently ignoring such entries.  It seems better
to produce a warning.

Pre-v16 branches already coped with dangling grantor OIDs by simply
omitting the GRANTED BY clause.  I left that behavior as-is, although
it's somewhat inconsistent with the behavior of later branches.

Reported-by: Virender Singla <virender.cse@gmail.com>
Discussion: https://postgr.es/m/CAM6Zo8woa62ZFHtMKox6a4jb8qQ=w87R2L0K8347iE-juQL2EA@mail.gmail.com
Backpatch-through: 13
This commit is contained in:
Tom Lane 2025-02-21 13:37:12 -05:00
parent 69c45eccea
commit 16eff4261f

View File

@ -966,6 +966,13 @@ dumpRoleMembership(PGconn *conn)
total; total;
bool dump_grantors; bool dump_grantors;
bool dump_grant_options; bool dump_grant_options;
int i_role;
int i_member;
int i_grantor;
int i_roleid;
int i_memberid;
int i_grantorid;
int i_admin_option;
int i_inherit_option; int i_inherit_option;
int i_set_option; int i_set_option;
@ -975,6 +982,10 @@ dumpRoleMembership(PGconn *conn)
* they didn't have ADMIN OPTION on the role, or a user that no longer * they didn't have ADMIN OPTION on the role, or a user that no longer
* existed. To avoid dump and restore failures, don't dump the grantor * existed. To avoid dump and restore failures, don't dump the grantor
* when talking to an old server version. * when talking to an old server version.
*
* Also, in older versions the roleid and/or member could be role OIDs
* that no longer exist. If we find such cases, print a warning and skip
* the entry.
*/ */
dump_grantors = (PQserverVersion(conn) >= 160000); dump_grantors = (PQserverVersion(conn) >= 160000);
@ -986,8 +997,10 @@ dumpRoleMembership(PGconn *conn)
/* Generate and execute query. */ /* Generate and execute query. */
printfPQExpBuffer(buf, "SELECT ur.rolname AS role, " printfPQExpBuffer(buf, "SELECT ur.rolname AS role, "
"um.rolname AS member, " "um.rolname AS member, "
"ug.oid AS grantorid, "
"ug.rolname AS grantor, " "ug.rolname AS grantor, "
"a.roleid AS roleid, "
"a.member AS memberid, "
"a.grantor AS grantorid, "
"a.admin_option"); "a.admin_option");
if (dump_grant_options) if (dump_grant_options)
appendPQExpBufferStr(buf, ", a.inherit_option, a.set_option"); appendPQExpBufferStr(buf, ", a.inherit_option, a.set_option");
@ -996,8 +1009,15 @@ dumpRoleMembership(PGconn *conn)
"LEFT JOIN %s um on um.oid = a.member " "LEFT JOIN %s um on um.oid = a.member "
"LEFT JOIN %s ug on ug.oid = a.grantor " "LEFT JOIN %s ug on ug.oid = a.grantor "
"WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')" "WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')"
"ORDER BY 1,2,4", role_catalog, role_catalog, role_catalog); "ORDER BY 1,2,3", role_catalog, role_catalog, role_catalog);
res = executeQuery(conn, buf->data); res = executeQuery(conn, buf->data);
i_role = PQfnumber(res, "role");
i_member = PQfnumber(res, "member");
i_grantor = PQfnumber(res, "grantor");
i_roleid = PQfnumber(res, "roleid");
i_memberid = PQfnumber(res, "memberid");
i_grantorid = PQfnumber(res, "grantorid");
i_admin_option = PQfnumber(res, "admin_option");
i_inherit_option = PQfnumber(res, "inherit_option"); i_inherit_option = PQfnumber(res, "inherit_option");
i_set_option = PQfnumber(res, "set_option"); i_set_option = PQfnumber(res, "set_option");
@ -1021,24 +1041,32 @@ dumpRoleMembership(PGconn *conn)
total = PQntuples(res); total = PQntuples(res);
while (start < total) while (start < total)
{ {
char *role = PQgetvalue(res, start, 0); char *role = PQgetvalue(res, start, i_role);
int i; int i;
bool *done; bool *done;
int remaining; int remaining;
int prev_remaining = 0; int prev_remaining = 0;
rolename_hash *ht; rolename_hash *ht;
/* If we hit a null roleid, we're done (nulls sort to the end). */
if (PQgetisnull(res, start, i_role))
{
/* translator: %s represents a numeric role OID */
pg_log_warning("found orphaned pg_auth_members entry for role %s",
PQgetvalue(res, start, i_roleid));
break;
}
/* All memberships for a single role should be adjacent. */ /* All memberships for a single role should be adjacent. */
for (end = start; end < total; ++end) for (end = start; end < total; ++end)
{ {
char *otherrole; char *otherrole;
otherrole = PQgetvalue(res, end, 0); otherrole = PQgetvalue(res, end, i_role);
if (strcmp(role, otherrole) != 0) if (strcmp(role, otherrole) != 0)
break; break;
} }
role = PQgetvalue(res, start, 0);
remaining = end - start; remaining = end - start;
done = pg_malloc0(remaining * sizeof(bool)); done = pg_malloc0(remaining * sizeof(bool));
ht = rolename_create(remaining, NULL); ht = rolename_create(remaining, NULL);
@ -1078,10 +1106,30 @@ dumpRoleMembership(PGconn *conn)
if (done[i - start]) if (done[i - start])
continue; continue;
member = PQgetvalue(res, i, 1); /* Complain about, then ignore, entries with orphaned OIDs. */
grantorid = PQgetvalue(res, i, 2); if (PQgetisnull(res, i, i_member))
grantor = PQgetvalue(res, i, 3); {
admin_option = PQgetvalue(res, i, 4); /* translator: %s represents a numeric role OID */
pg_log_warning("found orphaned pg_auth_members entry for role %s",
PQgetvalue(res, i, i_memberid));
done[i - start] = true;
--remaining;
continue;
}
if (PQgetisnull(res, i, i_grantor))
{
/* translator: %s represents a numeric role OID */
pg_log_warning("found orphaned pg_auth_members entry for role %s",
PQgetvalue(res, i, i_grantorid));
done[i - start] = true;
--remaining;
continue;
}
member = PQgetvalue(res, i, i_member);
grantor = PQgetvalue(res, i, i_grantor);
grantorid = PQgetvalue(res, i, i_grantorid);
admin_option = PQgetvalue(res, i, i_admin_option);
if (dump_grant_options) if (dump_grant_options)
set_option = PQgetvalue(res, i, i_set_option); set_option = PQgetvalue(res, i, i_set_option);