Skip to content

Commit 81402e3

Browse files
nathan-bossartyjhjstz
authored andcommitted
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 <postgres@jeltef.nl> Author: Jelte Fennema-Nio <postgres@jeltef.nl> Co-authored-by: Nathan Bossart <nathandbossart@gmail.com> Reviewed-by: Noah Misch <noah@leadboat.com> Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de> Security: CVE-2025-12817 Backpatch-through: 13 ======== In CBDB backported from https://git.postgresql.org/cgit/postgresql.git/commit/?h=95cce566968 Backport-by: reshke <reshke@double.cloud>
1 parent 90f9dc3 commit 81402e3

File tree

6 files changed

+56
-4
lines changed

6 files changed

+56
-4
lines changed

src/backend/commands/statscmds.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ compare_int16(const void *a, const void *b)
6565
* CREATE STATISTICS
6666
*/
6767
ObjectAddress
68-
CreateStatistics(CreateStatsStmt *stmt)
68+
CreateStatistics(CreateStatsStmt *stmt, bool check_rights)
6969
{
7070
int16 attnums[STATS_MAX_DIMENSIONS];
7171
int nattnums = 0;
@@ -179,6 +179,20 @@ CreateStatistics(CreateStatsStmt *stmt)
179179
}
180180
namestrcpy(&stxname, namestr);
181181

182+
/*
183+
* Check we have creation rights in target namespace. Skip check if
184+
* caller doesn't want it.
185+
*/
186+
if (check_rights)
187+
{
188+
AclResult aclresult;
189+
190+
aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE);
191+
if (aclresult != ACLCHECK_OK)
192+
aclcheck_error(aclresult, OBJECT_SCHEMA,
193+
get_namespace_name(namespaceId));
194+
}
195+
182196
/*
183197
* Deal with the possibility that the statistics object already exists.
184198
*/

src/backend/commands/tablecmds.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10599,7 +10599,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
1059910599
Assert(stmt->transformed);
1060010600

1060110601
HOLD_DISPATCH();
10602-
address = CreateStatistics(stmt);
10602+
address = CreateStatistics(stmt, !is_rebuild);
1060310603
RESUME_DISPATCH();
1060410604

1060510605
return address;

src/backend/tcop/utility.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2517,7 +2517,7 @@ ProcessUtilitySlow(ParseState *pstate,
25172517
/* Run parse analysis ... */
25182518
stmt = transformStatsStmt(relid, stmt, queryString);
25192519

2520-
address = CreateStatistics(stmt);
2520+
address = CreateStatistics(stmt, true);
25212521
}
25222522
break;
25232523

src/include/commands/defrem.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ extern void RemoveOperatorById(Oid operOid);
8686
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
8787

8888
/* commands/statscmds.c */
89-
extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
89+
extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, bool check_rights);
9090
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
9191
extern void RemoveStatisticsById(Oid statsOid);
9292
extern Oid StatisticsGetRelation(Oid statId, bool missing_ok);

src/test/regress/expected/stats_ext.out

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3277,6 +3277,23 @@ SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x
32773277
s_expr | {1}
32783278
(2 rows)
32793279

3280+
-- CREATE STATISTICS checks for CREATE on the schema
3281+
RESET SESSION AUTHORIZATION;
3282+
CREATE SCHEMA sts_sch1 CREATE TABLE sts_sch1.tbl (a INT, b INT);
3283+
GRANT USAGE ON SCHEMA sts_sch1 TO regress_stats_user1;
3284+
ALTER TABLE sts_sch1.tbl OWNER TO regress_stats_user1;
3285+
SET SESSION AUTHORIZATION regress_stats_user1;
3286+
CREATE STATISTICS sts_sch1.fail ON a, b FROM sts_sch1.tbl;
3287+
ERROR: permission denied for schema sts_sch1
3288+
RESET SESSION AUTHORIZATION;
3289+
GRANT CREATE ON SCHEMA sts_sch1 TO regress_stats_user1;
3290+
SET SESSION AUTHORIZATION regress_stats_user1;
3291+
CREATE STATISTICS sts_sch1.pass ON a, b FROM sts_sch1.tbl;
3292+
-- re-creating statistics via ALTER TABLE bypasses checks for CREATE on schema
3293+
RESET SESSION AUTHORIZATION;
3294+
REVOKE CREATE ON SCHEMA sts_sch1 FROM regress_stats_user1;
3295+
SET SESSION AUTHORIZATION regress_stats_user1;
3296+
ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
32803297
-- Tidy up
32813298
DROP OPERATOR <<< (int, int);
32823299
DROP FUNCTION op_leak(int, int);
@@ -3286,6 +3303,8 @@ DROP SCHEMA tststats CASCADE;
32863303
NOTICE: drop cascades to 2 other objects
32873304
DETAIL: drop cascades to table tststats.priv_test_tbl
32883305
drop cascades to view tststats.priv_test_view
3306+
DROP SCHEMA sts_sch1 CASCADE;
3307+
NOTICE: drop cascades to table sts_sch1.tbl
32893308
DROP USER regress_stats_user1;
32903309
-- test analyze with extended statistics
32913310
CREATE TABLE tbl_issue1293 (col1 int, col2 int);

src/test/regress/sql/stats_ext.sql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,12 +1675,31 @@ SELECT statistics_name, most_common_vals FROM pg_stats_ext x
16751675
SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x
16761676
WHERE tablename = 'stats_ext_tbl' ORDER BY ROW(x.*);
16771677

1678+
-- CREATE STATISTICS checks for CREATE on the schema
1679+
RESET SESSION AUTHORIZATION;
1680+
CREATE SCHEMA sts_sch1 CREATE TABLE sts_sch1.tbl (a INT, b INT);
1681+
GRANT USAGE ON SCHEMA sts_sch1 TO regress_stats_user1;
1682+
ALTER TABLE sts_sch1.tbl OWNER TO regress_stats_user1;
1683+
SET SESSION AUTHORIZATION regress_stats_user1;
1684+
CREATE STATISTICS sts_sch1.fail ON a, b FROM sts_sch1.tbl;
1685+
RESET SESSION AUTHORIZATION;
1686+
GRANT CREATE ON SCHEMA sts_sch1 TO regress_stats_user1;
1687+
SET SESSION AUTHORIZATION regress_stats_user1;
1688+
CREATE STATISTICS sts_sch1.pass ON a, b FROM sts_sch1.tbl;
1689+
1690+
-- re-creating statistics via ALTER TABLE bypasses checks for CREATE on schema
1691+
RESET SESSION AUTHORIZATION;
1692+
REVOKE CREATE ON SCHEMA sts_sch1 FROM regress_stats_user1;
1693+
SET SESSION AUTHORIZATION regress_stats_user1;
1694+
ALTER TABLE sts_sch1.tbl ALTER COLUMN a TYPE SMALLINT;
1695+
16781696
-- Tidy up
16791697
DROP OPERATOR <<< (int, int);
16801698
DROP FUNCTION op_leak(int, int);
16811699
RESET SESSION AUTHORIZATION;
16821700
DROP TABLE stats_ext_tbl;
16831701
DROP SCHEMA tststats CASCADE;
1702+
DROP SCHEMA sts_sch1 CASCADE;
16841703
DROP USER regress_stats_user1;
16851704

16861705
-- test analyze with extended statistics

0 commit comments

Comments
 (0)