]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Defend against self-referential views in relation_is_updatable().
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 21 Nov 2019 21:21:44 +0000 (16:21 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 21 Nov 2019 21:21:44 +0000 (16:21 -0500)
While a self-referential view doesn't actually work, it's possible
to create one, and it turns out that this breaks some of the
information_schema views.  Those views call relation_is_updatable(),
which neglected to consider the hazards of being recursive.  In
older PG versions you get a "stack depth limit exceeded" error,
but since v10 it'd recurse to the point of stack overrun and crash,
because commit a4c35ea1c took out the expression_returns_set() call
that was incidentally checking the stack depth.

Since this function is only used by information_schema views, it
seems like it'd be better to return "not updatable" than suffer
an error.  Hence, add tracking of what views we're examining,
in just the same way that the nearby fireRIRrules() code detects
self-referential views.  I added a check_stack_depth() call too,
just to be defensive.

Per private report from Manuel Rigger.  Back-patch to all
supported versions.

src/backend/rewrite/rewriteHandler.c
src/backend/utils/adt/misc.c
src/include/rewrite/rewriteHandler.h

index 858baf87ade74fbdcb3b49394cc112fd87e9ab06..04b79fd1d0ed7673794b1fcb4b32b2a180237f02 100644 (file)
@@ -25,6 +25,7 @@
 #include "catalog/pg_type.h"
 #include "commands/trigger.h"
 #include "foreign/fdwapi.h"
+#include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
@@ -2587,6 +2588,11 @@ view_cols_are_auto_updatable(Query *viewquery,
  * non-NULL, then only the specified columns are considered when testing for
  * updatability.
  *
+ * Unlike the preceding functions, this does recurse to look at a view's
+ * base relations, so it needs to detect recursion.  To do that, we pass
+ * a list of currently-considered outer relations.  External callers need
+ * only pass NIL.
+ *
  * This is used for the information_schema views, which have separate concepts
  * of "updatable" and "trigger updatable".  A relation is "updatable" if it
  * can be updated without the need for triggers (either because it has a
@@ -2605,6 +2611,7 @@ view_cols_are_auto_updatable(Query *viewquery,
  */
 int
 relation_is_updatable(Oid reloid,
+                                         List *outer_reloids,
                                          bool include_triggers,
                                          Bitmapset *include_cols)
 {
@@ -2614,6 +2621,9 @@ relation_is_updatable(Oid reloid,
 
 #define ALL_EVENTS ((1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE))
 
+       /* Since this function recurses, it could be driven to stack overflow */
+       check_stack_depth();
+
        rel = try_relation_open(reloid, AccessShareLock);
 
        /*
@@ -2625,6 +2635,13 @@ relation_is_updatable(Oid reloid,
        if (rel == NULL)
                return 0;
 
+       /* If we detect a recursive view, report that it is not updatable */
+       if (list_member_oid(outer_reloids, RelationGetRelid(rel)))
+       {
+               relation_close(rel, AccessShareLock);
+               return 0;
+       }
+
        /* If the relation is a table, it is always updatable */
        if (rel->rd_rel->relkind == RELKIND_RELATION ||
                rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
@@ -2745,11 +2762,15 @@ relation_is_updatable(Oid reloid,
                                base_rte->relkind != RELKIND_PARTITIONED_TABLE)
                        {
                                baseoid = base_rte->relid;
+                               outer_reloids = lcons_oid(RelationGetRelid(rel),
+                                                                                 outer_reloids);
                                include_cols = adjust_view_column_set(updatable_cols,
                                                                                                          viewquery->targetList);
                                auto_events &= relation_is_updatable(baseoid,
+                                                                                                        outer_reloids,
                                                                                                         include_triggers,
                                                                                                         include_cols);
+                               outer_reloids = list_delete_first(outer_reloids);
                        }
                        events |= auto_events;
                }
index b24dece23f8031333c8294ffb5a3efe98d5f3cd1..d850dd581b2ec897b1b72f389dc86ad48901a18a 100644 (file)
@@ -701,7 +701,7 @@ pg_relation_is_updatable(PG_FUNCTION_ARGS)
        Oid                     reloid = PG_GETARG_OID(0);
        bool            include_triggers = PG_GETARG_BOOL(1);
 
-       PG_RETURN_INT32(relation_is_updatable(reloid, include_triggers, NULL));
+       PG_RETURN_INT32(relation_is_updatable(reloid, NIL, include_triggers, NULL));
 }
 
 /*
@@ -725,7 +725,7 @@ pg_column_is_updatable(PG_FUNCTION_ARGS)
        if (attnum <= 0)
                PG_RETURN_BOOL(false);
 
-       events = relation_is_updatable(reloid, include_triggers,
+       events = relation_is_updatable(reloid, NIL, include_triggers,
                                                                   bms_make_singleton(col));
 
        /* We require both updatability and deletability of the relation */
index 8128199fc3146de3a3d867e62ce0ab1fc767f250..339f3438fdaf1e031632ecd4b42f5d339a5ae9be 100644 (file)
@@ -30,6 +30,7 @@ extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
                                                         bool check_cols);
 extern int relation_is_updatable(Oid reloid,
+                                         List *outer_reloids,
                                          bool include_triggers,
                                          Bitmapset *include_cols);