Re: ALTER TYPE OWNER fails to recurse to multirange - Mailing list pgsql-hackers

From Tom Lane
Subject Re: ALTER TYPE OWNER fails to recurse to multirange
Date
Msg-id 2441890.1707778513@sss.pgh.pa.us
Whole thread Raw
In response to Re: ALTER TYPE OWNER fails to recurse to multirange  (Robert Haas <robertmhaas@gmail.com>)
List pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> On Tue, Jan 16, 2024 at 11:46 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Also, we already
>> treat the multirange as dependent for some things:

> But this seems like an entirely valid point.

Yeah, it's a bit of a muddle.  But there is no syntax for making
a standalone multirange type, so it seems to me that we've mostly
determined that multiranges are dependent types.  There are just
a few places that didn't get the word.

Attached is a proposed patch to enforce that ownership and permissions
of a multirange are those of the underlying range type, in ways
parallel to how we treat array types.  This is all that I found by
looking for calls to IsTrueArrayType().  It's possible that there's
some dependent-type behavior somewhere that isn't conditioned on that,
but I can't think of a good way to search.

If we don't do this, then we need some hacking in pg_dump to get it
to save and restore these properties of multiranges, so it's not like
the status quo is acceptable.

I'd initially thought that perhaps we could back-patch parts of this,
but now I'm not sure; it seems like these behaviors are a bit
intertwined.  Given the lack of field complaints I'm inclined to leave
things alone in the back branches.

            regards, tom lane

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 590affb79a..591e767cce 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -2447,11 +2447,17 @@ ExecGrant_Type_check(InternalGrant *istmt, HeapTuple tuple)

     pg_type_tuple = (Form_pg_type) GETSTRUCT(tuple);

+    /* Disallow GRANT on dependent types */
     if (IsTrueArrayType(pg_type_tuple))
         ereport(ERROR,
                 (errcode(ERRCODE_INVALID_GRANT_OPERATION),
                  errmsg("cannot set privileges of array types"),
                  errhint("Set the privileges of the element type instead.")));
+    if (pg_type_tuple->typtype == TYPTYPE_MULTIRANGE)
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+                 errmsg("cannot set privileges of multirange types"),
+                 errhint("Set the privileges of the range type instead.")));

     /* Used GRANT DOMAIN on a non-domain? */
     if (istmt->objtype == OBJECT_DOMAIN &&
@@ -3806,6 +3812,35 @@ pg_type_aclmask_ext(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how,
         typeForm = (Form_pg_type) GETSTRUCT(tuple);
     }

+    /*
+     * Likewise, multirange types don't manage their own permissions; consult
+     * the associated range type.  (Note we must do this after the array step
+     * to get the right answer for arrays of multiranges.)
+     */
+    if (typeForm->typtype == TYPTYPE_MULTIRANGE)
+    {
+        Oid            rangetype = get_multirange_range(type_oid);
+
+        ReleaseSysCache(tuple);
+
+        tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rangetype));
+        if (!HeapTupleIsValid(tuple))
+        {
+            if (is_missing != NULL)
+            {
+                /* return "no privileges" instead of throwing an error */
+                *is_missing = true;
+                return 0;
+            }
+            else
+                ereport(ERROR,
+                        (errcode(ERRCODE_UNDEFINED_OBJECT),
+                         errmsg("type with OID %u does not exist",
+                                rangetype)));
+        }
+        typeForm = (Form_pg_type) GETSTRUCT(tuple);
+    }
+
     /*
      * Now get the type's owner and ACL from the tuple
      */
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index d7167108fb..fe47be38d0 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -326,14 +326,15 @@ TypeCreate(Oid newTypeOid,
                  errmsg("fixed-size types must have storage PLAIN")));

     /*
-     * This is a dependent type if it's an implicitly-created array type, or
-     * if it's a relation rowtype that's not a composite type.  For such types
-     * we'll leave the ACL empty, and we'll skip creating some dependency
-     * records because there will be a dependency already through the
-     * depended-on type or relation.  (Caution: this is closely intertwined
-     * with some behavior in GenerateTypeDependencies.)
+     * This is a dependent type if it's an implicitly-created array type or
+     * multirange type, or if it's a relation rowtype that's not a composite
+     * type.  For such types we'll leave the ACL empty, and we'll skip
+     * creating some dependency records because there will be a dependency
+     * already through the depended-on type or relation.  (Caution: this is
+     * closely intertwined with some behavior in GenerateTypeDependencies.)
      */
     isDependentType = isImplicitArray ||
+        typeType == TYPTYPE_MULTIRANGE ||
         (OidIsValid(relationOid) && relationKind != RELKIND_COMPOSITE_TYPE);

     /*
@@ -534,8 +535,9 @@ TypeCreate(Oid newTypeOid,
  * relationKind and isImplicitArray are likewise somewhat expensive to deduce
  * from the tuple, so we make callers pass those (they're not optional).
  *
- * isDependentType is true if this is an implicit array or relation rowtype;
- * that means it doesn't need its own dependencies on owner etc.
+ * isDependentType is true if this is an implicit array, multirange, or
+ * relation rowtype; that means it doesn't need its own dependencies on owner
+ * etc.
  *
  * We make an extension-membership dependency if we're in an extension
  * script and makeExtensionDep is true (and isDependentType isn't true).
@@ -601,18 +603,23 @@ GenerateTypeDependencies(HeapTuple typeTuple,
      * Make dependencies on namespace, owner, ACL, extension.
      *
      * Skip these for a dependent type, since it will have such dependencies
-     * indirectly through its depended-on type or relation.
+     * indirectly through its depended-on type or relation.  An exception is
+     * that multiranges need their own namespace dependency, since we don't
+     * force them to be in the same schema as their range type.
      */

-    /* placeholder for all normal dependencies */
+    /* collects normal dependencies for bulk recording */
     addrs_normal = new_object_addresses();

-    if (!isDependentType)
+    if (!isDependentType || typeForm->typtype == TYPTYPE_MULTIRANGE)
     {
         ObjectAddressSet(referenced, NamespaceRelationId,
                          typeForm->typnamespace);
-        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+        add_exact_object_address(&referenced, addrs_normal);
+    }

+    if (!isDependentType)
+    {
         recordDependencyOnOwner(TypeRelationId, typeObjectId,
                                 typeForm->typowner);

@@ -727,6 +734,16 @@ GenerateTypeDependencies(HeapTuple typeTuple,
         recordDependencyOn(&myself, &referenced,
                            isImplicitArray ? DEPENDENCY_INTERNAL : DEPENDENCY_NORMAL);
     }
+
+    /*
+     * Note: you might expect that we should record an internal dependency of
+     * a multirange on its range type here, by analogy with the cases above.
+     * But instead, that is done by RangeCreate(), which also handles
+     * recording of other range-type-specific dependencies.  That's pretty
+     * bogus.  It's okay for now, because there are no cases where we need to
+     * regenerate the dependencies of a range or multirange type.  But someday
+     * we might need to move that logic here to allow such regeneration.
+     */
 }

 /*
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index a400fb39f6..e0275e5fe9 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3647,6 +3647,8 @@ RenameType(RenameStmt *stmt)
                  errhint("You can alter type %s, which will alter the array type as well.",
                          format_type_be(typTup->typelem))));

+    /* we do allow separate renaming of multirange types, though */
+
     /*
      * If type is composite we need to rename associated pg_class entry too.
      * RenameRelationInternal will call RenameTypeInternal automatically.
@@ -3730,6 +3732,21 @@ AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype)
                  errhint("You can alter type %s, which will alter the array type as well.",
                          format_type_be(typTup->typelem))));

+    /* don't allow direct alteration of multirange types, either */
+    if (typTup->typtype == TYPTYPE_MULTIRANGE)
+    {
+        Oid            rangetype = get_multirange_range(typeOid);
+
+        /* We don't expect get_multirange_range to fail, but cope if so */
+        ereport(ERROR,
+                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                 errmsg("cannot alter multirange type %s",
+                        format_type_be(typeOid)),
+                 OidIsValid(rangetype) ?
+                 errhint("You can alter type %s, which will alter the multirange type as well.",
+                         format_type_be(rangetype)) : 0));
+    }
+
     /*
      * If the new owner is the same as the existing owner, consider the
      * command to have succeeded.  This is for dump restoration purposes.
@@ -3769,13 +3786,13 @@ AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype)
 /*
  * AlterTypeOwner_oid - change type owner unconditionally
  *
- * This function recurses to handle a pg_class entry, if necessary.  It
- * invokes any necessary access object hooks.  If hasDependEntry is true, this
- * function modifies the pg_shdepend entry appropriately (this should be
- * passed as false only for table rowtypes and array types).
+ * This function recurses to handle dependent types (arrays and multiranges).
+ * It invokes any necessary access object hooks.  If hasDependEntry is true,
+ * this function modifies the pg_shdepend entry appropriately (this should be
+ * passed as false only for table rowtypes and dependent types).
  *
  * This is used by ALTER TABLE/TYPE OWNER commands, as well as by REASSIGN
- * OWNED BY.  It assumes the caller has done all needed check.
+ * OWNED BY.  It assumes the caller has done all needed checks.
  */
 void
 AlterTypeOwner_oid(Oid typeOid, Oid newOwnerId, bool hasDependEntry)
@@ -3815,7 +3832,7 @@ AlterTypeOwner_oid(Oid typeOid, Oid newOwnerId, bool hasDependEntry)
  * AlterTypeOwnerInternal - bare-bones type owner change.
  *
  * This routine simply modifies the owner of a pg_type entry, and recurses
- * to handle a possible array type.
+ * to handle any dependent types.
  */
 void
 AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId)
@@ -3865,6 +3882,19 @@ AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId)
     if (OidIsValid(typTup->typarray))
         AlterTypeOwnerInternal(typTup->typarray, newOwnerId);

+    /* If it is a range type, update the associated multirange too */
+    if (typTup->typtype == TYPTYPE_RANGE)
+    {
+        Oid            multirange_typeid = get_range_multirange(typeOid);
+
+        if (!OidIsValid(multirange_typeid))
+            ereport(ERROR,
+                    (errcode(ERRCODE_UNDEFINED_OBJECT),
+                     errmsg("could not find multirange type for data type %s",
+                            format_type_be(typeOid))));
+        AlterTypeOwnerInternal(multirange_typeid, newOwnerId);
+    }
+
     /* Clean up */
     table_close(rel, RowExclusiveLock);
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 348748bae5..f40bc759c5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1868,7 +1868,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
         return;
     }

-    /* skip auto-generated array types */
+    /* skip auto-generated array and multirange types */
     if (tyinfo->isArray || tyinfo->isMultirange)
     {
         tyinfo->dobj.objType = DO_DUMMY_TYPE;
@@ -1876,8 +1876,8 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
         /*
          * Fall through to set the dump flag; we assume that the subsequent
          * rules will do the same thing as they would for the array's base
-         * type.  (We cannot reliably look up the base type here, since
-         * getTypes may not have processed it yet.)
+         * type or multirange's range type.  (We cannot reliably look up the
+         * base type here, since getTypes may not have processed it yet.)
          */
     }

@@ -5770,7 +5770,7 @@ getTypes(Archive *fout, int *numTypes)
         else
             tyinfo[i].isArray = false;

-        if (tyinfo[i].typtype == 'm')
+        if (tyinfo[i].typtype == TYPTYPE_MULTIRANGE)
             tyinfo[i].isMultirange = true;
         else
             tyinfo[i].isMultirange = false;
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 6d9498cdd1..5a9ee5d2cd 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -144,7 +144,6 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
-owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index 9808587532..002775b9eb 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -3115,6 +3115,36 @@ select _textrange1(textrange2('a','z')) @> 'b'::text;
 drop type textrange1;
 drop type textrange2;
 --
+-- Multiranges don't have their own ownership or permissions.
+--
+create type textrange1 as range(subtype=text, multirange_type_name=multitextrange1, collation="C");
+create role regress_multirange_owner;
+alter type multitextrange1 owner to regress_multirange_owner;  -- fail
+ERROR:  cannot alter multirange type multitextrange1
+HINT:  You can alter type textrange1, which will alter the multirange type as well.
+alter type textrange1 owner to regress_multirange_owner;
+set role regress_multirange_owner;
+revoke usage on type multitextrange1 from public;  -- fail
+ERROR:  cannot set privileges of multirange types
+HINT:  Set the privileges of the range type instead.
+revoke usage on type textrange1 from public;
+\dT+ *textrange1*
+                                                                     List of data types
+ Schema |      Name       |  Internal name  | Size | Elements |          Owner           |                  Access
privileges                 | Description  

+--------+-----------------+-----------------+------+----------+--------------------------+-----------------------------------------------------+-------------
+ public | multitextrange1 | multitextrange1 | var  |          | regress_multirange_owner |
                       |  
+ public | textrange1      | textrange1      | var  |          | regress_multirange_owner |
regress_multirange_owner=U/regress_multirange_owner|  
+(2 rows)
+
+create temp table test1(f1 multitextrange1);
+revoke usage on type textrange1 from regress_multirange_owner;
+create temp table test2(f1 multitextrange1);  -- fail
+ERROR:  permission denied for type multitextrange1
+drop table test1;
+drop type textrange1;
+reset role;
+drop role regress_multirange_owner;
+--
 -- Test polymorphic type system
 --
 create function anyarray_anymultirange_func(a anyarray, r anymultirange)
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index cadf312031..197e3af29c 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -700,6 +700,27 @@ select _textrange1(textrange2('a','z')) @> 'b'::text;
 drop type textrange1;
 drop type textrange2;

+--
+-- Multiranges don't have their own ownership or permissions.
+--
+create type textrange1 as range(subtype=text, multirange_type_name=multitextrange1, collation="C");
+create role regress_multirange_owner;
+
+alter type multitextrange1 owner to regress_multirange_owner;  -- fail
+alter type textrange1 owner to regress_multirange_owner;
+set role regress_multirange_owner;
+revoke usage on type multitextrange1 from public;  -- fail
+revoke usage on type textrange1 from public;
+\dT+ *textrange1*
+create temp table test1(f1 multitextrange1);
+revoke usage on type textrange1 from regress_multirange_owner;
+create temp table test2(f1 multitextrange1);  -- fail
+
+drop table test1;
+drop type textrange1;
+reset role;
+drop role regress_multirange_owner;
+
 --
 -- Test polymorphic type system
 --

pgsql-hackers by date:

Previous
From: Andres Freund
Date:
Subject: Re: backend *.c #include cleanup (IWYU)
Next
From: Nathan Bossart
Date:
Subject: Re: glibc qsort() vulnerability