From b5c574399ee7306bee3650f0907a07e42be82b33 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Mon, 17 Feb 2014 09:33:31 -0500 Subject: [PATCH] Avoid repeated name lookups during table and index DDL. If the name lookups come to different conclusions due to concurrent activity, we might perform some parts of the DDL on a different table than other parts. At least in the case of CREATE INDEX, this can be used to cause the permissions checks to be performed against a different table than the index creation, allowing for a privilege escalation attack. This changes the calling convention for DefineIndex, CreateTrigger, transformIndexStmt, transformAlterTableStmt, CheckIndexCompatible (in 9.2 and newer), and AlterTable (in 9.1 and older). In addition, CheckRelationOwnership is removed in 9.2 and newer and the calling convention is changed in older branches. A field has also been added to the Constraint node (FkConstraint in 8.4). Third-party code calling these functions or using the Constraint node will require updating. Report by Andres Freund. Patch by Robert Haas and Andres Freund, reviewed by Tom Lane. Security: CVE-2014-0062 --- src/backend/bootstrap/bootparse.y | 15 +++- src/backend/catalog/index.c | 13 +-- src/backend/catalog/pg_constraint.c | 19 ++++ src/backend/commands/alter.c | 4 +- src/backend/commands/indexcmds.c | 25 +++--- src/backend/commands/tablecmds.c | 134 ++++++++++++++++++---------- src/backend/commands/trigger.c | 25 ++++-- src/backend/nodes/copyfuncs.c | 1 + src/backend/nodes/equalfuncs.c | 1 + src/backend/nodes/outfuncs.c | 1 + src/backend/parser/parse_utilcmd.c | 56 +++++------- src/backend/tcop/utility.c | 52 ++++++++--- src/include/catalog/index.h | 1 + src/include/catalog/pg_constraint.h | 1 + src/include/commands/defrem.h | 2 +- src/include/commands/tablecmds.h | 2 +- src/include/commands/trigger.h | 2 +- src/include/nodes/parsenodes.h | 2 + src/include/parser/parse_utilcmd.h | 5 +- src/include/tcop/utility.h | 2 +- 20 files changed, 233 insertions(+), 130 deletions(-) diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index f3e85aa31b..c6b755628b 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -27,6 +27,7 @@ #include "bootstrap/bootstrap.h" #include "catalog/catalog.h" #include "catalog/heap.h" +#include "catalog/namespace.h" #include "catalog/pg_am.h" #include "catalog/pg_attribute.h" #include "catalog/pg_authid.h" @@ -279,9 +280,14 @@ Boot_InsertStmt: Boot_DeclareIndexStmt: XDECLARE INDEX boot_ident oidspec ON boot_ident USING boot_ident LPAREN boot_index_params RPAREN { + Oid relationId; + do_start(); - DefineIndex(makeRangeVar(NULL, $6, -1), + relationId = RangeVarGetRelid(makeRangeVar(NULL, $6, -1), + false); + + DefineIndex(relationId, $3, $4, $8, @@ -297,9 +303,14 @@ Boot_DeclareIndexStmt: Boot_DeclareUniqueIndexStmt: XDECLARE UNIQUE INDEX boot_ident oidspec ON boot_ident USING boot_ident LPAREN boot_index_params RPAREN { + Oid relationId; + do_start(); - DefineIndex(makeRangeVar(NULL, $7, -1), + relationId = RangeVarGetRelid(makeRangeVar(NULL, $7, -1), + false); + + DefineIndex(relationId, $4, $5, $9, diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 129a1ac11f..4e6222b3ba 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -115,7 +115,6 @@ static void validate_index_heapscan(Relation heapRelation, IndexInfo *indexInfo, Snapshot snapshot, v_i_state *state); -static Oid IndexGetRelation(Oid indexId); static bool ReindexIsCurrentlyProcessingIndex(Oid indexOid); static void SetReindexProcessing(Oid heapOid, Oid indexOid); static void ResetReindexProcessing(void); @@ -1202,18 +1201,13 @@ index_constraint_create(Relation heapRelation, */ if (deferrable) { - RangeVar *heapRel; CreateTrigStmt *trigger; - heapRel = makeRangeVar(get_namespace_name(namespaceId), - pstrdup(RelationGetRelationName(heapRelation)), - -1); - trigger = makeNode(CreateTrigStmt); trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ? "PK_ConstraintTrigger" : "Unique_ConstraintTrigger"; - trigger->relation = heapRel; + trigger->relation = NULL; trigger->funcname = SystemFuncName("unique_key_recheck"); trigger->args = NIL; trigger->row = true; @@ -1226,7 +1220,8 @@ index_constraint_create(Relation heapRelation, trigger->initdeferred = initdeferred; trigger->constrrel = NULL; - (void) CreateTrigger(trigger, NULL, conOid, indexRelationId, true); + (void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation), + InvalidOid, conOid, indexRelationId, true); } /* @@ -2833,7 +2828,7 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action) * IndexGetRelation: given an index's relation OID, get the OID of the * relation it is an index on. Uses the system cache. */ -static Oid +Oid IndexGetRelation(Oid indexId) { HeapTuple tuple; diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 71c48ffa92..79c5cfa345 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -742,6 +742,25 @@ AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, heap_close(conRel, RowExclusiveLock); } +/* + * get_constraint_relation_oids + * Find the IDs of the relations to which a constraint refers. + */ +void +get_constraint_relation_oids(Oid constraint_oid, Oid *conrelid, Oid *confrelid) +{ + HeapTuple tup; + Form_pg_constraint con; + + tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraint_oid)); + if (!HeapTupleIsValid(tup)) /* should not happen */ + elog(ERROR, "cache lookup failed for constraint %u", constraint_oid); + con = (Form_pg_constraint) GETSTRUCT(tup); + *conrelid = con->conrelid; + *confrelid = con->confrelid; + ReleaseSysCache(tup); +} + /* * get_constraint_oid * Find a constraint on the specified relation with the specified name. diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 0f4e0f701a..91df1a0aca 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -105,9 +105,8 @@ ExecRenameStmt(RenameStmt *stmt) { Oid relid; - CheckRelationOwnership(stmt->relation, true); - relid = RangeVarGetRelid(stmt->relation, false); + CheckRelationOwnership(relid, true); switch (stmt->renameType) { @@ -223,7 +222,6 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) case OBJECT_TABLE: case OBJECT_VIEW: case OBJECT_FOREIGN_TABLE: - CheckRelationOwnership(stmt->relation, true); AlterTableNamespace(stmt->relation, stmt->newschema, stmt->objectType, AccessExclusiveLock); break; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index ab73567ad2..1a0ee84963 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -76,7 +76,8 @@ static char *ChooseIndexNameAddition(List *colnames); * DefineIndex * Creates a new index. * - * 'heapRelation': the relation the index will apply to. + * 'relationId': the OID of the heap relation on which the index is to be + * created * 'indexRelationName': the name for the new index, or NULL to indicate * that a nonconflicting default name should be picked. * 'indexRelationId': normally InvalidOid, but during bootstrap can be @@ -105,7 +106,7 @@ static char *ChooseIndexNameAddition(List *colnames); * 'concurrent': avoid blocking writers to the table while building. */ void -DefineIndex(RangeVar *heapRelation, +DefineIndex(Oid relationId, char *indexRelationName, Oid indexRelationId, char *accessMethodName, @@ -128,7 +129,6 @@ DefineIndex(RangeVar *heapRelation, Oid *collationObjectId; Oid *classObjectId; Oid accessMethodId; - Oid relationId; Oid namespaceId; Oid tablespaceId; List *indexColNames; @@ -148,6 +148,7 @@ DefineIndex(RangeVar *heapRelation, int n_old_snapshots; LockRelId heaprelid; LOCKTAG heaplocktag; + LOCKMODE lockmode; Snapshot snapshot; int i; @@ -166,14 +167,18 @@ DefineIndex(RangeVar *heapRelation, INDEX_MAX_KEYS))); /* - * Open heap relation, acquire a suitable lock on it, remember its OID - * * Only SELECT ... FOR UPDATE/SHARE are allowed while doing a standard * index build; but for concurrent builds we allow INSERT/UPDATE/DELETE * (but not VACUUM). + * + * NB: Caller is responsible for making sure that relationId refers + * to the relation on which the index should be built; except in bootstrap + * mode, this will typically require the caller to have already locked + * the relation. To avoid lock upgrade hazards, that lock should be at + * least as strong as the one we take here. */ - rel = heap_openrv(heapRelation, - (concurrent ? ShareUpdateExclusiveLock : ShareLock)); + lockmode = concurrent ? ShareUpdateExclusiveLock : ShareLock; + rel = heap_open(relationId, lockmode); relationId = RelationGetRelid(rel); namespaceId = RelationGetNamespace(rel); @@ -191,12 +196,12 @@ DefineIndex(RangeVar *heapRelation, ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot create index on foreign table \"%s\"", - heapRelation->relname))); + RelationGetRelationName(rel)))); else ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", - heapRelation->relname))); + RelationGetRelationName(rel)))); } /* @@ -503,7 +508,7 @@ DefineIndex(RangeVar *heapRelation, */ /* Open and lock the parent heap relation */ - rel = heap_openrv(heapRelation, ShareUpdateExclusiveLock); + rel = heap_open(relationId, ShareUpdateExclusiveLock); /* And the target index relation */ indexRelation = index_open(indexRelationId, RowExclusiveLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index adf278bb1f..ae62c69849 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -73,6 +73,7 @@ #include "storage/lock.h" #include "storage/predicate.h" #include "storage/smgr.h" +#include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -272,7 +273,8 @@ static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts); static void validateForeignKeyConstraint(char *conname, Relation rel, Relation pkrel, Oid pkindOid, Oid constraintOid); -static void createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, +static void createForeignKeyTriggers(Relation rel, Oid refRelOid, + Constraint *fkconstraint, Oid constraintOid, Oid indexOid); static void ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode); static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, @@ -346,7 +348,8 @@ static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno); static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode); static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode); -static void ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode); +static void ATPostAlterTypeParse(Oid oldRelId, Oid refRelId, char *cmd, + List **wqueue, LOCKMODE lockmode); static void change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId); static void change_owner_recurse_to_sequences(Oid relationOid, @@ -2378,15 +2381,13 @@ CheckTableNotInUse(Relation rel, const char *stmt) * rather than reassess it at lower levels. */ void -AlterTable(AlterTableStmt *stmt) +AlterTable(Oid relid, AlterTableStmt *stmt) { Relation rel; LOCKMODE lockmode = AlterTableGetLockLevel(stmt->cmds); - /* - * Acquire same level of lock as already acquired during parsing. - */ - rel = relation_openrv(stmt->relation, lockmode); + /* Caller is required to provide an adequate lock. */ + rel = relation_open(relid, NoLock); CheckTableNotInUse(rel, "ALTER TABLE"); @@ -5153,7 +5154,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel, /* The IndexStmt has already been through transformIndexStmt */ - DefineIndex(stmt->relation, /* relation */ + DefineIndex(RelationGetRelid(rel), /* relation */ stmt->idxname, /* index name */ InvalidOid, /* no predefined OID */ stmt->accessMethod, /* am name */ @@ -5465,7 +5466,10 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, * table; trying to start with a lesser lock will just create a risk of * deadlock.) */ - pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock); + if (OidIsValid(fkconstraint->old_pktable_oid)) + pkrel = heap_open(fkconstraint->old_pktable_oid, AccessExclusiveLock); + else + pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock); /* * Validity checks (permission checks wait till we have the column @@ -5713,7 +5717,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, /* * Create the triggers that will enforce the constraint. */ - createForeignKeyTriggers(rel, fkconstraint, constrOid, indexOid); + createForeignKeyTriggers(rel, RelationGetRelid(pkrel), fkconstraint, + constrOid, indexOid); /* * Tell Phase 3 to check that the constraint is satisfied by existing rows. @@ -6202,14 +6207,14 @@ validateForeignKeyConstraint(char *conname, } static void -CreateFKCheckTrigger(RangeVar *myRel, Constraint *fkconstraint, +CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint, Oid constraintOid, Oid indexOid, bool on_insert) { CreateTrigStmt *fk_trigger; fk_trigger = makeNode(CreateTrigStmt); fk_trigger->trigname = "RI_ConstraintTrigger"; - fk_trigger->relation = myRel; + fk_trigger->relation = NULL; fk_trigger->row = true; fk_trigger->timing = TRIGGER_TYPE_AFTER; @@ -6230,10 +6235,11 @@ CreateFKCheckTrigger(RangeVar *myRel, Constraint *fkconstraint, fk_trigger->isconstraint = true; fk_trigger->deferrable = fkconstraint->deferrable; fk_trigger->initdeferred = fkconstraint->initdeferred; - fk_trigger->constrrel = fkconstraint->pktable; + fk_trigger->constrrel = NULL; fk_trigger->args = NIL; - (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, true); + (void) CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid, constraintOid, + indexOid, true); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -6243,18 +6249,13 @@ CreateFKCheckTrigger(RangeVar *myRel, Constraint *fkconstraint, * Create the triggers that implement an FK constraint. */ static void -createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, +createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, Oid constraintOid, Oid indexOid) { - RangeVar *myRel; + Oid myRelOid; CreateTrigStmt *fk_trigger; - /* - * Reconstruct a RangeVar for my relation (not passed in, unfortunately). - */ - myRel = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)), - pstrdup(RelationGetRelationName(rel)), - -1); + myRelOid = RelationGetRelid(rel); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -6265,14 +6266,14 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, */ fk_trigger = makeNode(CreateTrigStmt); fk_trigger->trigname = "RI_ConstraintTrigger"; - fk_trigger->relation = fkconstraint->pktable; + fk_trigger->relation = NULL; fk_trigger->row = true; fk_trigger->timing = TRIGGER_TYPE_AFTER; fk_trigger->events = TRIGGER_TYPE_DELETE; fk_trigger->columns = NIL; fk_trigger->whenClause = NULL; fk_trigger->isconstraint = true; - fk_trigger->constrrel = myRel; + fk_trigger->constrrel = NULL; switch (fkconstraint->fk_del_action) { case FKCONSTR_ACTION_NOACTION: @@ -6307,7 +6308,8 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, } fk_trigger->args = NIL; - (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, true); + (void) CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid, + indexOid, true); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -6318,14 +6320,14 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, */ fk_trigger = makeNode(CreateTrigStmt); fk_trigger->trigname = "RI_ConstraintTrigger"; - fk_trigger->relation = fkconstraint->pktable; + fk_trigger->relation = NULL; fk_trigger->row = true; fk_trigger->timing = TRIGGER_TYPE_AFTER; fk_trigger->events = TRIGGER_TYPE_UPDATE; fk_trigger->columns = NIL; fk_trigger->whenClause = NULL; fk_trigger->isconstraint = true; - fk_trigger->constrrel = myRel; + fk_trigger->constrrel = NULL; switch (fkconstraint->fk_upd_action) { case FKCONSTR_ACTION_NOACTION: @@ -6360,7 +6362,8 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, } fk_trigger->args = NIL; - (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, true); + (void) CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid, + indexOid, true); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -6380,8 +6383,10 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, * and the use of self-referential FKs is rare enough, that we live with * it for now. There will be a real fix in PG 9.2. */ - CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, indexOid, true); - CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, indexOid, false); + CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid, + indexOid, true); + CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid, + indexOid, false); } /* @@ -7179,7 +7184,8 @@ static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) { ObjectAddress obj; - ListCell *l; + ListCell *def_item; + ListCell *oid_item; /* * Re-parse the index and constraint definitions, and attach them to the @@ -7188,11 +7194,36 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) * lock on the table the constraint is attached to, and we need to get * that before dropping. It's safe because the parser won't actually look * at the catalogs to detect the existing entry. + * + * We can't rely on the output of deparsing to tell us which relation + * to operate on, because concurrent activity might have made the name + * resolve differently. Instead, we've got to use the OID of the + * constraint or index we're processing to figure out which relation + * to operate on. */ - foreach(l, tab->changedIndexDefs) - ATPostAlterTypeParse((char *) lfirst(l), wqueue, lockmode); - foreach(l, tab->changedConstraintDefs) - ATPostAlterTypeParse((char *) lfirst(l), wqueue, lockmode); + forboth(oid_item, tab->changedConstraintOids, + def_item, tab->changedConstraintDefs) + { + Oid oldId = lfirst_oid(oid_item); + Oid relid; + Oid confrelid; + + get_constraint_relation_oids(oldId, &relid, &confrelid); + ATPostAlterTypeParse(relid, confrelid, + (char *) lfirst(def_item), + wqueue, lockmode); + } + forboth(oid_item, tab->changedIndexOids, + def_item, tab->changedIndexDefs) + { + Oid oldId = lfirst_oid(oid_item); + Oid relid; + + relid = IndexGetRelation(oldId); + ATPostAlterTypeParse(relid, InvalidOid, + (char *) lfirst(def_item), + wqueue, lockmode); + } /* * Now we can drop the existing constraints and indexes --- constraints @@ -7202,18 +7233,18 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) * should be okay to use DROP_RESTRICT here, since nothing else should be * depending on these objects. */ - foreach(l, tab->changedConstraintOids) + foreach(oid_item, tab->changedConstraintOids) { obj.classId = ConstraintRelationId; - obj.objectId = lfirst_oid(l); + obj.objectId = lfirst_oid(oid_item); obj.objectSubId = 0; performDeletion(&obj, DROP_RESTRICT); } - foreach(l, tab->changedIndexOids) + foreach(oid_item, tab->changedIndexOids) { obj.classId = RelationRelationId; - obj.objectId = lfirst_oid(l); + obj.objectId = lfirst_oid(oid_item); obj.objectSubId = 0; performDeletion(&obj, DROP_RESTRICT); } @@ -7225,11 +7256,13 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) } static void -ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode) +ATPostAlterTypeParse(Oid oldRelId, Oid refRelId, char *cmd, List **wqueue, + LOCKMODE lockmode) { List *raw_parsetree_list; List *querytree_list; ListCell *list_item; + Relation rel; /* * We expect that we will get only ALTER TABLE and CREATE INDEX @@ -7245,16 +7278,21 @@ ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode) if (IsA(stmt, IndexStmt)) querytree_list = lappend(querytree_list, - transformIndexStmt((IndexStmt *) stmt, + transformIndexStmt(oldRelId, + (IndexStmt *) stmt, cmd)); else if (IsA(stmt, AlterTableStmt)) querytree_list = list_concat(querytree_list, - transformAlterTableStmt((AlterTableStmt *) stmt, + transformAlterTableStmt(oldRelId, + (AlterTableStmt *) stmt, cmd)); else querytree_list = lappend(querytree_list, stmt); } + /* Caller should already have acquired whatever lock we need. */ + rel = relation_open(oldRelId, NoLock); + /* * Attach each generated command to the proper place in the work queue. * Note this could result in creation of entirely new work-queue entries. @@ -7266,7 +7304,6 @@ ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode) foreach(list_item, querytree_list) { Node *stm = (Node *) lfirst(list_item); - Relation rel; AlteredTableInfo *tab; switch (nodeTag(stm)) @@ -7276,14 +7313,12 @@ ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode) IndexStmt *stmt = (IndexStmt *) stm; AlterTableCmd *newcmd; - rel = relation_openrv(stmt->relation, lockmode); tab = ATGetQueueEntry(wqueue, rel); newcmd = makeNode(AlterTableCmd); newcmd->subtype = AT_ReAddIndex; newcmd->def = (Node *) stmt; tab->subcmds[AT_PASS_OLD_INDEX] = lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd); - relation_close(rel, NoLock); break; } case T_AlterTableStmt: @@ -7291,7 +7326,6 @@ ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode) AlterTableStmt *stmt = (AlterTableStmt *) stm; ListCell *lcmd; - rel = relation_openrv(stmt->relation, lockmode); tab = ATGetQueueEntry(wqueue, rel); foreach(lcmd, stmt->cmds) { @@ -7314,7 +7348,6 @@ ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode) (int) cmd->subtype); } } - relation_close(rel, NoLock); break; } default: @@ -7322,8 +7355,9 @@ ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode) (int) nodeTag(stm)); } } -} + relation_close(rel, NoLock); +} /* * ALTER TABLE OWNER @@ -9002,7 +9036,8 @@ ATExecGenericOptions(Relation rel, List *options) /* * Execute ALTER TABLE SET SCHEMA * - * Note: caller must have checked ownership of the relation already + * WARNING WARNING WARNING: In previous *minor* releases the caller was + * responsible for checking ownership of the relation, but now we do it here. */ void AlterTableNamespace(RangeVar *relation, const char *newschema, @@ -9017,6 +9052,7 @@ AlterTableNamespace(RangeVar *relation, const char *newschema, rel = relation_openrv(relation, lockmode); relid = RelationGetRelid(rel); + CheckRelationOwnership(relid, true); oldNspOid = RelationGetNamespace(rel); /* Check relation type against type specified in the ALTER command */ diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index ab87562d0c..3c2447671c 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -92,6 +92,13 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, * queryString is the source text of the CREATE TRIGGER command. * This must be supplied if a whenClause is specified, else it can be NULL. * + * relOid, if nonzero, is the relation on which the trigger should be + * created. If zero, the name provided in the statement will be looked up. + * + * refRelOid, if nonzero, is the relation to which the constraint trigger + * refers. If zero, the constraint relation name provided in the statement + * will be looked up as needed. + * * constraintOid, if nonzero, says that this trigger is being created * internally to implement that constraint. A suitable pg_depend entry will * be made to link the trigger to that constraint. constraintOid is zero when @@ -114,7 +121,7 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, */ Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString, - Oid constraintOid, Oid indexOid, + Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid, bool isInternal) { int16 tgtype; @@ -143,7 +150,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, ObjectAddress myself, referenced; - rel = heap_openrv(stmt->relation, AccessExclusiveLock); + if (OidIsValid(relOid)) + rel = heap_open(relOid, AccessExclusiveLock); + else + rel = heap_openrv(stmt->relation, AccessExclusiveLock); /* * Triggers must be on tables or views, and there are additional @@ -192,8 +202,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, errmsg("permission denied: \"%s\" is a system catalog", RelationGetRelationName(rel)))); - if (stmt->isconstraint && stmt->constrrel != NULL) - constrrelid = RangeVarGetRelid(stmt->constrrel, false); + if (stmt->isconstraint) + { + if (OidIsValid(refRelOid)) + constrrelid = refRelOid; + else if (stmt->constrrel != NULL) + constrrelid = RangeVarGetRelid(stmt->constrrel, false); + } /* permission checks */ if (!isInternal) @@ -501,7 +516,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("trigger \"%s\" for relation \"%s\" already exists", - trigname, stmt->relation->relname))); + trigname, RelationGetRelationName(rel)))); } systable_endscan(tgscan); } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 6f5ebb277d..09ba0732b4 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2343,6 +2343,7 @@ _copyConstraint(Constraint *from) COPY_SCALAR_FIELD(fk_del_action); COPY_SCALAR_FIELD(skip_validation); COPY_SCALAR_FIELD(initially_valid); + COPY_SCALAR_FIELD(old_pktable_oid); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index debde12ca9..0de60d5c6c 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2271,6 +2271,7 @@ _equalConstraint(Constraint *a, Constraint *b) COMPARE_SCALAR_FIELD(fk_del_action); COMPARE_SCALAR_FIELD(skip_validation); COMPARE_SCALAR_FIELD(initially_valid); + COMPARE_SCALAR_FIELD(old_pktable_oid); return true; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 085a458085..bb342ca6d3 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2630,6 +2630,7 @@ _outConstraint(StringInfo str, Constraint *node) WRITE_CHAR_FIELD(fk_del_action); WRITE_BOOL_FIELD(skip_validation); WRITE_BOOL_FIELD(initially_valid); + WRITE_OID_FIELD(old_pktable_oid); break; case CONSTR_ATTR_DEFERRABLE: diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 9c39bacc64..387bcb8ab9 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1860,14 +1860,18 @@ transformFKConstraints(CreateStmtContext *cxt, * a predicate expression. There are several code paths that create indexes * without bothering to call this, because they know they don't have any * such expressions to deal with. + * + * To avoid race conditions, it's important that this function rely only on + * the passed-in relid (and not on stmt->relation) to determine the target + * relation. */ IndexStmt * -transformIndexStmt(IndexStmt *stmt, const char *queryString) +transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) { - Relation rel; ParseState *pstate; RangeTblEntry *rte; ListCell *l; + Relation rel; /* * We must not scribble on the passed-in IndexStmt, so copy it. (This is @@ -1875,26 +1879,17 @@ transformIndexStmt(IndexStmt *stmt, const char *queryString) */ stmt = (IndexStmt *) copyObject(stmt); - /* - * Open the parent table with appropriate locking. We must do this - * because addRangeTableEntry() would acquire only AccessShareLock, - * leaving DefineIndex() needing to do a lock upgrade with consequent risk - * of deadlock. Make sure this stays in sync with the type of lock - * DefineIndex() wants. If we are being called by ALTER TABLE, we will - * already hold a higher lock. - */ - rel = heap_openrv(stmt->relation, - (stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock)); - /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; /* * Put the parent table into the rtable so that the expressions can refer - * to its fields without qualification. + * to its fields without qualification. Caller is responsible for locking + * relation, but we still need to open it. */ - rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true); + rel = relation_open(relid, NoLock); + rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true); /* no to join list, yes to namespaces */ addRTEtoQuery(pstate, rte, false, true, true); @@ -1948,7 +1943,7 @@ transformIndexStmt(IndexStmt *stmt, const char *queryString) free_parsestate(pstate); - /* Close relation, but keep the lock */ + /* Close relation */ heap_close(rel, NoLock); return stmt; @@ -2270,9 +2265,14 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString, * Returns a List of utility commands to be done in sequence. One of these * will be the transformed AlterTableStmt, but there may be additional actions * to be done before and after the actual AlterTable() call. + * + * To avoid race conditions, it's important that this function rely only on + * the passed-in relid (and not on stmt->relation) to determine the target + * relation. */ List * -transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) +transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, + const char *queryString) { Relation rel; ParseState *pstate; @@ -2284,7 +2284,6 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) List *newcmds = NIL; bool skipValidation = true; AlterTableCmd *newcmd; - LOCKMODE lockmode; /* * We must not scribble on the passed-in AlterTableStmt, so copy it. (This @@ -2292,21 +2291,8 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) */ stmt = (AlterTableStmt *) copyObject(stmt); - /* - * Determine the appropriate lock level for this list of subcommands. - */ - lockmode = AlterTableGetLockLevel(stmt->cmds); - - /* - * Acquire appropriate lock on the target relation, which will be held - * until end of transaction. This ensures any decisions we make here - * based on the state of the relation will still be good at execution. We - * must get lock now because execution will later require it; taking a - * lower grade lock now and trying to upgrade later risks deadlock. Any - * new commands we add after this must not upgrade the lock level - * requested here. - */ - rel = relation_openrv(stmt->relation, lockmode); + /* Caller is responsible for locking the relation */ + rel = relation_open(relid, NoLock); /* Set up pstate and CreateStmtContext */ pstate = make_parsestate(NULL); @@ -2419,7 +2405,7 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) IndexStmt *idxstmt = (IndexStmt *) lfirst(l); Assert(IsA(idxstmt, IndexStmt)); - idxstmt = transformIndexStmt(idxstmt, queryString); + idxstmt = transformIndexStmt(relid, idxstmt, queryString); newcmd = makeNode(AlterTableCmd); newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex; newcmd->def = (Node *) idxstmt; @@ -2443,7 +2429,7 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) newcmds = lappend(newcmds, newcmd); } - /* Close rel but keep lock */ + /* Close rel */ relation_close(rel, NoLock); /* diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index f45d92df73..beaa08358a 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -54,11 +54,13 @@ #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteRemove.h" #include "storage/fd.h" +#include "storage/lmgr.h" #include "tcop/pquery.h" #include "tcop/utility.h" #include "utils/acl.h" #include "utils/guc.h" #include "utils/syscache.h" +#include "utils/lsyscache.h" /* Hook for plugins to get control in ProcessUtility() */ @@ -72,19 +74,17 @@ ProcessUtility_hook_type ProcessUtility_hook = NULL; * except when allowSystemTableMods is true. */ void -CheckRelationOwnership(RangeVar *rel, bool noCatalogs) +CheckRelationOwnership(Oid relOid, bool noCatalogs) { - Oid relOid; HeapTuple tuple; - relOid = RangeVarGetRelid(rel, false); tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); if (!HeapTupleIsValid(tuple)) /* should not happen */ elog(ERROR, "cache lookup failed for relation %u", relOid); if (!pg_class_ownercheck(relOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, - rel->relname); + get_rel_name(relOid)); if (noCatalogs) { @@ -93,7 +93,7 @@ CheckRelationOwnership(RangeVar *rel, bool noCatalogs) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied: \"%s\" is a system catalog", - rel->relname))); + get_rel_name(relOid)))); } ReleaseSysCache(tuple); @@ -751,9 +751,23 @@ standard_ProcessUtility(Node *parsetree, { List *stmts; ListCell *l; + AlterTableStmt *atstmt = (AlterTableStmt *) parsetree; + Oid relid; + LOCKMODE lockmode; + + /* + * Look up the relation OID just once, right here at the + * beginning, so that we don't end up repeating the name + * lookup later and latching onto a different relation + * partway through. + */ + lockmode = AlterTableGetLockLevel(atstmt->cmds); + relid = RangeVarGetRelid(atstmt->relation, false); + LockRelationOid(relid, lockmode); /* Run parse analysis ... */ - stmts = transformAlterTableStmt((AlterTableStmt *) parsetree, + stmts = transformAlterTableStmt(relid, + atstmt, queryString); /* ... and do it */ @@ -764,7 +778,7 @@ standard_ProcessUtility(Node *parsetree, if (IsA(stmt, AlterTableStmt)) { /* Do the table alteration proper */ - AlterTable((AlterTableStmt *) stmt); + AlterTable(relid, (AlterTableStmt *) stmt); } else { @@ -927,18 +941,33 @@ standard_ProcessUtility(Node *parsetree, case T_IndexStmt: /* CREATE INDEX */ { IndexStmt *stmt = (IndexStmt *) parsetree; + Oid relid; + LOCKMODE lockmode; if (stmt->concurrent) PreventTransactionChain(isTopLevel, "CREATE INDEX CONCURRENTLY"); - CheckRelationOwnership(stmt->relation, true); + /* + * Look up the relation OID just once, right here at the + * beginning, so that we don't end up repeating the name + * lookup later and latching onto a different relation + * partway through. To avoid lock upgrade hazards, it's + * important that we take the strongest lock that will + * eventually be needed here, so the lockmode calculation + * needs to match what DefineIndex() does. + */ + lockmode = stmt->concurrent ? ShareUpdateExclusiveLock + : ShareLock; + relid = RangeVarGetRelid(stmt->relation, false); + LockRelationOid(relid, lockmode); + CheckRelationOwnership(relid, true); /* Run parse analysis ... */ - stmt = transformIndexStmt(stmt, queryString); + stmt = transformIndexStmt(relid, stmt, queryString); /* ... and do it */ - DefineIndex(stmt->relation, /* relation */ + DefineIndex(relid, /* relation */ stmt->idxname, /* index name */ InvalidOid, /* no predefined OID */ stmt->accessMethod, /* am name */ @@ -1105,7 +1134,8 @@ standard_ProcessUtility(Node *parsetree, case T_CreateTrigStmt: (void) CreateTrigger((CreateTrigStmt *) parsetree, queryString, - InvalidOid, InvalidOid, false); + InvalidOid, InvalidOid, InvalidOid, + InvalidOid, false); break; case T_DropPropertyStmt: diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 9c9987e876..4c27021b8b 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -108,5 +108,6 @@ extern bool reindex_relation(Oid relid, int flags); extern bool ReindexIsProcessingHeap(Oid heapOid); extern bool ReindexIsProcessingIndex(Oid indexOid); +extern Oid IndexGetRelation(Oid indexId); #endif /* INDEX_H */ diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 56b6ca2f50..a12e901fc6 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -242,6 +242,7 @@ extern char *ChooseConstraintName(const char *name1, const char *name2, extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, Oid newNspId, bool isType, ObjectAddresses *objsMoved); +extern void get_constraint_relation_oids(Oid constraint_oid, Oid *conrelid, Oid *confrelid); extern Oid get_constraint_oid(Oid relid, const char *conname, bool missing_ok); extern bool check_functional_grouping(Oid relid, diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index d2a6352822..65121b944c 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -18,7 +18,7 @@ /* commands/indexcmds.c */ -extern void DefineIndex(RangeVar *heapRelation, +extern void DefineIndex(Oid relationId, char *indexRelationName, Oid indexRelationId, char *accessMethodName, diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 293e4e5bc9..cafcf20fbd 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -25,7 +25,7 @@ extern Oid DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId); extern void RemoveRelations(DropStmt *drop); -extern void AlterTable(AlterTableStmt *stmt); +extern void AlterTable(Oid relid, AlterTableStmt *stmt); extern LOCKMODE AlterTableGetLockLevel(List *cmds); diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index ad97871d98..9ec3f87513 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -109,7 +109,7 @@ extern PGDLLIMPORT int SessionReplicationRole; #define TRIGGER_DISABLED 'D' extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString, - Oid constraintOid, Oid indexOid, + Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid, bool isInternal); extern void DropTrigger(Oid relid, const char *trigname, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 8358723cff..1301463c62 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1553,6 +1553,8 @@ typedef struct Constraint char fk_del_action; /* ON DELETE action */ bool skip_validation; /* skip validation of existing rows? */ bool initially_valid; /* mark the new constraint as valid? */ + + Oid old_pktable_oid; /* pg_constraint.confrelid of my former self */ } Constraint; /* ---------------------- diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 3c8b9ab881..6c73a28a4c 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -18,9 +18,10 @@ extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString); -extern List *transformAlterTableStmt(AlterTableStmt *stmt, +extern List *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, const char *queryString); -extern IndexStmt *transformIndexStmt(IndexStmt *stmt, const char *queryString); +extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, + const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt); diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h index c21857af4d..ec585bc803 100644 --- a/src/include/tcop/utility.h +++ b/src/include/tcop/utility.h @@ -40,6 +40,6 @@ extern LogStmtLevel GetCommandLogLevel(Node *parsetree); extern bool CommandIsReadOnly(Node *parsetree); -extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs); +extern void CheckRelationOwnership(Oid relOid, bool noCatalogs); #endif /* UTILITY_H */ -- 2.40.0