On Sun, Apr 07, 2024 at 01:22:51AM +0300, Alexander Korotkov wrote:
> I've pushed 0001 and 0002

The partition MERGE (1adf16b8f) and SPLIT (87c21bb94) v17 patches introduced
createPartitionTable() with this code:

        createStmt->relation = newPartName;
...
        wrapper->utilityStmt = (Node *) createStmt;
...
        ProcessUtility(wrapper,
...
        newRel = table_openrv(newPartName, NoLock);

This breaks from the CVE-2014-0062 (commit 5f17304) principle of not repeating
name lookups.  The attached demo uses this defect to make one partition have
two parents.
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ae2efdc..654b502 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -3495,7 +3495,11 @@ StorePartitionBound(Relation rel, Relation parent, 
PartitionBoundSpec *bound)
                elog(ERROR, "cache lookup failed for relation %u",
                         RelationGetRelid(rel));
 
-#ifdef USE_ASSERT_CHECKING
+       /*
+        * Assertion fails during partition getting multiple parents.  Disable 
the
+        * assertion, to see what non-assert builds experience.
+        */
+#if 0
        {
                Form_pg_class classForm;
                bool            isnull;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8fcb188..48207f9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -95,6 +95,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
+#include "utils/injection_point.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -15825,7 +15826,11 @@ MergeAttributesIntoExisting(Relation child_rel, 
Relation parent_rel, bool ispart
                         */
                        if (parent_rel->rd_rel->relkind == 
RELKIND_PARTITIONED_TABLE)
                        {
-                               Assert(child_att->attinhcount == 1);
+                               /*
+                                * Assertion fails during partition getting 
multiple parents.
+                                * Disable, to see what non-assert builds 
experience.
+                                */
+                               /* Assert(child_att->attinhcount == 1); */
                                child_att->attislocal = false;
                        }
 
@@ -20388,7 +20393,14 @@ createPartitionTable(RangeVar *newPartName, Relation 
modelRel,
         * Open the new partition with no lock, because we already have
         * AccessExclusiveLock placed there after creation.
         */
-       newRel = table_openrv(newPartName, NoLock);
+       INJECTION_POINT("merge-after-create");
+       /*
+        * For testing, switch to taking a lock.  This solves two problems.
+        * First, it gets an AcceptInvalidationMessages(), so we actually
+        * invalidate the search path.  Second, it avoids an assertion failure
+        * from our lack of lock, so we see what non-assert builds experience.
+        */
+       newRel = table_openrv(newPartName, AccessExclusiveLock);
 
        /*
         * We intended to create the partition with the same persistence as the
diff --git a/src/test/modules/injection_points/Makefile 
b/src/test/modules/injection_points/Makefile
index 2ffd2f7..9af45bf 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -9,7 +9,7 @@ PGFILEDESC = "injection_points - facility for injection points"
 REGRESS = injection_points
 REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
 
-ISOLATION = inplace
+ISOLATION = inplace merge
 
 # The injection points are cluster-wide, so disable installcheck
 NO_INSTALLCHECK = 1
diff --git a/src/test/modules/injection_points/expected/merge.out 
b/src/test/modules/injection_points/expected/merge.out
new file mode 100644
index 0000000..04920e7
--- /dev/null
+++ b/src/test/modules/injection_points/expected/merge.out
@@ -0,0 +1,60 @@
+Parsed test spec with 3 sessions
+
+starting permutation: merge1 ct2 detach3
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step merge1: 
+       SET search_path = target;
+       ALTER TABLE parted MERGE PARTITIONS (part1, part3) INTO part_all;
+       -- only one of *parted should have rows, but both do
+       SELECT a AS new_target_parted FROM target.parted ORDER BY 1;
+       SELECT a AS old_target_parted FROM old_target.parted ORDER BY 1;
+       SELECT a AS new_target_part_all FROM target.part_all ORDER BY 1;
+       SELECT a AS old_target_part_all FROM old_target.part_all ORDER BY 1;
+ <waiting ...>
+step ct2: 
+       ALTER SCHEMA target RENAME TO old_target;
+       CREATE SCHEMA target
+               CREATE TABLE parted (a int) partition by list (a)
+               CREATE TABLE part_all PARTITION OF parted FOR VALUES IN (1, 2, 
3, 4)
+
+step detach3: 
+       SELECT injection_points_detach('merge-after-create');
+       SELECT injection_points_wakeup('merge-after-create');
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step merge1: <... completed>
+new_target_parted
+-----------------
+                1
+                3
+(2 rows)
+
+old_target_parted
+-----------------
+                1
+                3
+(2 rows)
+
+new_target_part_all
+-------------------
+                  1
+                  3
+(2 rows)
+
+old_target_part_all
+-------------------
+(0 rows)
+
diff --git a/src/test/modules/injection_points/specs/merge.spec 
b/src/test/modules/injection_points/specs/merge.spec
new file mode 100644
index 0000000..63bcd2b
--- /dev/null
+++ b/src/test/modules/injection_points/specs/merge.spec
@@ -0,0 +1,51 @@
+setup
+{
+       CREATE EXTENSION injection_points;
+       CREATE SCHEMA target
+               CREATE TABLE parted (a int) partition by list (a)
+               CREATE TABLE part1 PARTITION OF parted FOR VALUES IN (1, 2)
+               CREATE TABLE part3 PARTITION OF parted FOR VALUES IN (3, 4);
+       INSERT INTO target.parted VALUES (1),(3);
+}
+teardown
+{
+       DROP SCHEMA target, old_target CASCADE;
+       DROP EXTENSION injection_points;
+}
+
+# MERGE PARTITIONS
+session s1
+setup  {
+       SELECT injection_points_set_local();
+       SELECT injection_points_attach('merge-after-create', 'wait');
+}
+step merge1    {
+       SET search_path = target;
+       ALTER TABLE parted MERGE PARTITIONS (part1, part3) INTO part_all;
+       -- only one of *parted should have rows, but both do
+       SELECT a AS new_target_parted FROM target.parted ORDER BY 1;
+       SELECT a AS old_target_parted FROM old_target.parted ORDER BY 1;
+       SELECT a AS new_target_part_all FROM target.part_all ORDER BY 1;
+       SELECT a AS old_target_part_all FROM old_target.part_all ORDER BY 1;
+}
+
+
+# inject another table via ALTER SCHEMA RENAME
+session s2
+step ct2       {
+       ALTER SCHEMA target RENAME TO old_target;
+       CREATE SCHEMA target
+               CREATE TABLE parted (a int) partition by list (a)
+               CREATE TABLE part_all PARTITION OF parted FOR VALUES IN (1, 2, 
3, 4)
+}
+
+
+# injection release
+session s3
+step detach3   {
+       SELECT injection_points_detach('merge-after-create');
+       SELECT injection_points_wakeup('merge-after-create');
+}
+
+
+permutation merge1(detach3) ct2 detach3

Reply via email to