This is an automated email from the ASF dual-hosted git repository.

my-ship-it pushed a commit to branch REL_2_STABLE
in repository https://gitbox.apache.org/repos/asf/cloudberry.git

commit b3f374585faafafa7cc241e5ecdcf95b3f904a80
Author: Nathan Bossart <[email protected]>
AuthorDate: Mon Nov 10 09:00:00 2025 -0600

    Check for CREATE privilege on the schema in CREATE STATISTICS.
    
    This omission allowed table owners to create statistics in any
    schema, potentially leading to unexpected naming conflicts.  For
    ALTER TABLE commands that require re-creating statistics objects,
    skip this check in case the user has since lost CREATE on the
    schema.  The addition of a second parameter to CreateStatistics()
    breaks ABI compatibility, but we are unaware of any impacted
    third-party code.
    
    Reported-by: Jelte Fennema-Nio <[email protected]>
    Author: Jelte Fennema-Nio <[email protected]>
    Co-authored-by: Nathan Bossart <[email protected]>
    Reviewed-by: Noah Misch <[email protected]>
    Reviewed-by: Álvaro Herrera <[email protected]>
    Security: CVE-2025-12817
    Backpatch-through: 13
---
 src/backend/commands/statscmds.c        | 17 +++++++++++++++-
 src/backend/commands/tablecmds.c        |  2 +-
 src/backend/tcop/utility.c              |  2 +-
 src/include/commands/defrem.h           |  2 +-
 src/test/regress/expected/stats_ext.out | 36 +++++++++++++++++++++++++++++++++
 src/test/regress/sql/stats_ext.sql      | 33 ++++++++++++++++++++++++++++++
 6 files changed, 88 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 4cf7ae7b2e3..fbf0a580413 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -65,7 +65,7 @@ compare_int16(const void *a, const void *b)
  *             CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, bool check_rights)
 {
        int16           attnums[STATS_MAX_DIMENSIONS];
        int                     nattnums = 0;
@@ -179,6 +179,21 @@ CreateStatistics(CreateStatsStmt *stmt)
        }
        namestrcpy(&stxname, namestr);
 
+       /*
+        * Check we have creation rights in target namespace.  Skip check if
+        * caller doesn't want it.
+        */
+       if (check_rights)
+       {
+               AclResult       aclresult;
+
+               aclresult = object_aclcheck(NamespaceRelationId, namespaceId,
+                                                                       
GetUserId(), ACL_CREATE);
+               if (aclresult != ACLCHECK_OK)
+                       aclcheck_error(aclresult, OBJECT_SCHEMA,
+                                                  
get_namespace_name(namespaceId));
+       }
+
        /*
         * Deal with the possibility that the statistics object already exists.
         */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index af7bc2c1d5c..d6f9f9da638 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10640,7 +10640,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
        Assert(stmt->transformed);
 
        HOLD_DISPATCH();
-       address = CreateStatistics(stmt);
+       address = CreateStatistics(stmt, !is_rebuild);
        RESUME_DISPATCH();
 
        return address;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 72d4fc4c89b..0a26b6b74eb 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2517,7 +2517,7 @@ ProcessUtilitySlow(ParseState *pstate,
                                        /* Run parse analysis ... */
                                        stmt = transformStatsStmt(relid, stmt, 
queryString);
 
-                                       address = CreateStatistics(stmt);
+                                       address = CreateStatistics(stmt, true);
                                }
                                break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 57a49126c0b..7742a28a5d6 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -86,7 +86,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, bool 
check_rights);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern Oid     StatisticsGetRelation(Oid statId, bool missing_ok);
diff --git a/src/test/regress/expected/stats_ext.out 
b/src/test/regress/expected/stats_ext.out
index b1fde692e92..f84cd12b57b 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3353,6 +3353,40 @@ SELECT statistics_name, most_common_vals FROM 
pg_stats_ext_exprs x
  s_expr          | {1}
 (2 rows)
 
+-- CREATE STATISTICS checks for CREATE on the schema
+RESET SESSION AUTHORIZATION;
+CREATE SCHEMA sts_sch1 CREATE TABLE sts_sch1.tbl (a INT, b INT);
+CREATE SCHEMA sts_sch2;
+GRANT USAGE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1;
+ALTER TABLE sts_sch1.tbl OWNER TO regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+CREATE STATISTICS ON a, b FROM sts_sch1.tbl;
+ERROR:  permission denied for schema sts_sch1
+CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl;
+ERROR:  permission denied for schema sts_sch2
+RESET SESSION AUTHORIZATION;
+GRANT CREATE ON SCHEMA sts_sch1 TO regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+CREATE STATISTICS ON a, b FROM sts_sch1.tbl;
+CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl;
+ERROR:  permission denied for schema sts_sch2
+RESET SESSION AUTHORIZATION;
+REVOKE CREATE ON SCHEMA sts_sch1 FROM regress_stats_user1;
+GRANT CREATE ON SCHEMA sts_sch2 TO regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+CREATE STATISTICS ON a, b FROM sts_sch1.tbl;
+ERROR:  permission denied for schema sts_sch1
+CREATE STATISTICS sts_sch2.pass1 ON a, b FROM sts_sch1.tbl;
+RESET SESSION AUTHORIZATION;
+GRANT CREATE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+CREATE STATISTICS ON a, b FROM sts_sch1.tbl;
+CREATE STATISTICS sts_sch2.pass2 ON a, b FROM sts_sch1.tbl;
+-- re-creating statistics via ALTER TABLE bypasses checks for CREATE on schema
+RESET SESSION AUTHORIZATION;
+REVOKE CREATE ON SCHEMA sts_sch1, sts_sch2 FROM regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
 -- Tidy up
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
@@ -3365,6 +3399,8 @@ NOTICE:  drop cascades to 3 other objects
 DETAIL:  drop cascades to table tststats.priv_test_parent_tbl
 drop cascades to table tststats.priv_test_tbl
 drop cascades to view tststats.priv_test_view
+DROP SCHEMA sts_sch1, sts_sch2 CASCADE;
+NOTICE:  drop cascades to table sts_sch1.tbl
 DROP USER regress_stats_user1;
 -- test analyze with extended statistics 
 CREATE TABLE tbl_issue1293 (col1 int, col2 int);
diff --git a/src/test/regress/sql/stats_ext.sql 
b/src/test/regress/sql/stats_ext.sql
index 4ba8799c3b0..182dd1b00d0 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -1725,6 +1725,38 @@ SELECT statistics_name, most_common_vals FROM 
pg_stats_ext x
 SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x
     WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
 
+-- CREATE STATISTICS checks for CREATE on the schema
+RESET SESSION AUTHORIZATION;
+CREATE SCHEMA sts_sch1 CREATE TABLE sts_sch1.tbl (a INT, b INT);
+CREATE SCHEMA sts_sch2;
+GRANT USAGE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1;
+ALTER TABLE sts_sch1.tbl OWNER TO regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+CREATE STATISTICS ON a, b FROM sts_sch1.tbl;
+CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl;
+RESET SESSION AUTHORIZATION;
+GRANT CREATE ON SCHEMA sts_sch1 TO regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+CREATE STATISTICS ON a, b FROM sts_sch1.tbl;
+CREATE STATISTICS sts_sch2.fail ON a, b FROM sts_sch1.tbl;
+RESET SESSION AUTHORIZATION;
+REVOKE CREATE ON SCHEMA sts_sch1 FROM regress_stats_user1;
+GRANT CREATE ON SCHEMA sts_sch2 TO regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+CREATE STATISTICS ON a, b FROM sts_sch1.tbl;
+CREATE STATISTICS sts_sch2.pass1 ON a, b FROM sts_sch1.tbl;
+RESET SESSION AUTHORIZATION;
+GRANT CREATE ON SCHEMA sts_sch1, sts_sch2 TO regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+CREATE STATISTICS ON a, b FROM sts_sch1.tbl;
+CREATE STATISTICS sts_sch2.pass2 ON a, b FROM sts_sch1.tbl;
+
+-- re-creating statistics via ALTER TABLE bypasses checks for CREATE on schema
+RESET SESSION AUTHORIZATION;
+REVOKE CREATE ON SCHEMA sts_sch1, sts_sch2 FROM regress_stats_user1;
+SET SESSION AUTHORIZATION regress_stats_user1;
+ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
+
 -- Tidy up
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
@@ -1733,6 +1765,7 @@ DROP FUNCTION op_leak(record, record);
 RESET SESSION AUTHORIZATION;
 DROP TABLE stats_ext_tbl;
 DROP SCHEMA tststats CASCADE;
+DROP SCHEMA sts_sch1, sts_sch2 CASCADE;
 DROP USER regress_stats_user1;
 
 -- test analyze with extended statistics 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to