Re: pg_upgrade fails with non-standard ACL

From: Noah Misch <noah(at)leadboat(dot)com>
To: Arthur Zakirov <zaartur(at)gmail(dot)com>, Anastasia Lubennikova <a(dot)lubennikova(at)postgrespro(dot)ru>
Cc: Alvaro Herrera <alvherre(at)2ndquadrant(dot)com>, Michael Paquier <michael(at)paquier(dot)xyz>, Grigory Smolkin <g(dot)smolkin(at)postgrespro(dot)ru>, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: pg_upgrade fails with non-standard ACL
Date: 2021-01-03 11:29:55
Message-ID: 20210103112955.GA2243205@rfd.leadboat.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Thu, Jun 11, 2020 at 07:58:43PM +0300, Anastasia Lubennikova wrote:
> On 08.06.2020 19:31, Alvaro Herrera wrote:
> >I'm thinking what's a good way to have a test that's committable. Maybe
> >it would work to add a TAP test to pg_upgrade that runs initdb, does a
> >few GRANTS as per your attachment, then runs pg_upgrade? Currently the
> >pg_upgrade tests are not TAP ... we'd have to revive
> >https://postgr.es/m/20180126080026.GI17847@paquier.xyz
> >(Some progress was already made on the buildfarm front to permit this)
>
> I would be glad to add some test, but it seems to me that the infrastructure
> changes for cross-version pg_upgrade test is much more complicated task than
> this modest bugfix.

Agreed.

> --- a/src/bin/pg_upgrade/check.c
> +++ b/src/bin/pg_upgrade/check.c

> +static void
> +check_for_changed_signatures(void)
> +{

> + snprintf(output_path, sizeof(output_path), "revoke_objects.sql");

If an upgrade deleted function pg_last_wal_replay_lsn, upgrading a database in
which "REVOKE ALL ON FUNCTION pg_last_wal_replay_lsn FROM public" happened
requires a GRANT. Can you use a file name that will still make sense if
someone enhances pg_upgrade to generate such GRANT statements?

> + if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
> + pg_fatal("could not open file \"%s\": %s\n",
> + output_path, strerror(errno));

Use %m instead of passing sterror(errno) to %s.

> + }
> +
> + /* Handle columns separately */
> + if (strstr(aclinfo->obj_type, "column") != NULL)
> + {
> + char *pdot = last_dot_location(aclinfo->obj_ident);

I'd write strrchr(aclinfo->obj_ident, '.') instead of introducing
last_dot_location().

This assumes column names don't contain '.'; how difficult would it be to
remove that assumption? If it's difficult, a cheap approach would be to
ignore any entry containing too many dots. We're not likely to create such
column names at initdb time, but v13 does allow:

ALTER VIEW pg_locks RENAME COLUMN objid TO "a.b";
GRANT SELECT ("a.b") ON pg_locks TO backup;

After which revoke_objects.sql has:

psql:./revoke_objects.sql:9: ERROR: syntax error at or near "") ON pg_catalog.pg_locks.""
LINE 1: REVOKE ALL (b") ON pg_catalog.pg_locks."a FROM "backup";

While that ALTER VIEW probably should have required allow_system_table_mods,
we need to assume such databases exist.

> --- a/src/bin/pg_upgrade/info.c
> +++ b/src/bin/pg_upgrade/info.c

> +get_non_default_acl_infos(ClusterInfo *cluster)
> +{

> + res = executeQueryOrDie(conn,
> + /*
> + * Get relations, attributes, functions and procedures. Some system
> + * objects like views are not pinned, but these type of objects are
> + * created in pg_catalog schema.
> + */
> + "SELECT obj.type, obj.identity, shd.refobjid::regrole rolename, "
> + " CASE WHEN shd.classid = 'pg_class'::regclass THEN true "
> + " ELSE false "
> + " END is_relation "
> + "FROM pg_catalog.pg_shdepend shd, "
> + "LATERAL pg_catalog.pg_identify_object("
> + " shd.classid, shd.objid, shd.objsubid) obj "
> + /* 'a' is for SHARED_DEPENDENCY_ACL */
> + "WHERE shd.deptype = 'a' AND shd.dbid = %d "
> + " AND shd.classid IN ('pg_proc'::regclass, 'pg_class'::regclass) "
> + /* get only system objects */
> + " AND obj.schema = 'pg_catalog' "

Overall, this patch predicts a subset of cases where pg_dump will emit a
failing GRANT or REVOKE that targets a pg_catalog object. Can you write a
code comment stating what it does and does not detect? I think it's okay to
not predict every failure, but let's record the intended coverage. Here are a
few examples I know; there may be more. The above query only finds GRANTs to
non-pinned roles. pg_dump dumps the following, but the above query doesn't
find them:

REVOKE ALL ON FUNCTION pg_last_wal_replay_lsn FROM public;
GRANT EXECUTE ON FUNCTION pg_reload_conf() TO pg_signal_backend;

The above query should test refclassid.

This function should not run queries against servers older than 9.6, because
pg_dump emits GRANT/REVOKE for pg_catalog objects only when targeting 9.6 or
later. An upgrade from 8.4 to master is failing on the above query:

===
Checking for large objects ok
SQL command failed
SELECT obj.type, obj.identity, shd.refobjid::regrole rolename, CASE WHEN shd.classid = 'pg_class'::regclass THEN true ELSE false END is_relation FROM pg_catalog.pg_shdepend shd, LATERAL pg_catalog.pg_identify_object( shd.classid, shd.objid, shd.objsubid) obj WHERE shd.deptype = 'a' AND shd.dbid = 11564 AND shd.classid IN ('pg_proc'::regclass, 'pg_class'::regclass) AND obj.schema = 'pg_catalog' ORDER BY obj.identity COLLATE "C";
ERROR: syntax error at or near "."
LINE 1: ...ROM pg_catalog.pg_shdepend shd, LATERAL pg_catalog.pg_identi...
^
Failure, exiting
===

> + while (aclnum < PQntuples(res) &&
> + strcmp(curr->obj_ident, PQgetvalue(res, aclnum, i_obj_ident)) == 0)

The patch adds many lines wider than 78 columns, this being one example. In
general, break such lines. (Don't break string literal arguments of
ereport(), though.)

nm

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Michael Paquier 2021-01-03 12:05:09 Re: doc review for v14
Previous Message Simon Riggs 2021-01-03 10:57:14 Re: Safety/validity of resetting permissions by updating system tables