Reject modifying a temp table of another session with ALTER TABLE.

Normally this case isn't even reachable by non-superusers, since
permissions checks prevent naming such a table.  However, it is
possible to make it happen by altering a parent table whose child
is another session's temp table.

We definitely can't support any such ALTER that requires modifying
the contents of such a table, since we lack access to the other
session's temporary-buffer pool.  But there seems no good reason
to allow it even if it'd only require changing catalog contents.
One reason not to allow it is that we'd rather not expose the
implementation-dependent behavior of whether a specific ALTER
requires touching the table contents.  Another is that there may
be (in future, even if not today) optimizations that assume that
a session's own temp tables won't be modified by other sessions.

Hence, add a RELATION_IS_OTHER_TEMP() check to all the places
where ALTER TABLE currently does CheckTableNotInUse().  (I looked
through all other callers of CheckTableNotInUse(), and they seem
OK already.)

Per bug #18492 from Alexander Lakhin.  Back-patch to all supported
branches.

Discussion: https://postgr.es/m/18492-c7a2634bf4968763@postgresql.org
This commit is contained in:
Tom Lane 2024-06-07 14:50:09 -04:00
parent 2dc1deaea0
commit 7ed8ce8a46
1 changed files with 47 additions and 13 deletions

View File

@ -399,6 +399,7 @@ static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
static void validateForeignKeyConstraint(char *conname,
Relation rel, Relation pkrel,
Oid pkindOid, Oid constraintOid);
static void CheckAlterTableIsSafe(Relation rel);
static void ATController(AlterTableStmt *parsetree,
Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
AlterTableUtilityContext *context);
@ -4269,6 +4270,37 @@ CheckTableNotInUse(Relation rel, const char *stmt)
stmt, RelationGetRelationName(rel))));
}
/*
* CheckAlterTableIsSafe
* Verify that it's safe to allow ALTER TABLE on this relation.
*
* This consists of CheckTableNotInUse() plus a check that the relation
* isn't another session's temp table. We must split out the temp-table
* check because there are callers of CheckTableNotInUse() that don't want
* that, notably DROP TABLE. (We must allow DROP or we couldn't clean out
* an orphaned temp schema.) Compare truncate_check_activity().
*/
static void
CheckAlterTableIsSafe(Relation rel)
{
/*
* Don't allow ALTER on temp tables of other backends. Their local buffer
* manager is not going to cope if we need to change the table's contents.
* Even if we don't, there may be optimizations that assume temp tables
* aren't subject to such interference.
*/
if (RELATION_IS_OTHER_TEMP(rel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter temporary tables of other sessions")));
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
*/
CheckTableNotInUse(rel, "ALTER TABLE");
}
/*
* AlterTableLookupRelation
* Look up, and lock, the OID for the relation named by an alter table
@ -4342,7 +4374,7 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
CheckTableNotInUse(rel, "ALTER TABLE");
CheckAlterTableIsSafe(rel);
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
}
@ -5748,7 +5780,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
/*
* Don't allow rewrite on temp tables of other backends ... their
* local buffer manager is not going to cope.
* local buffer manager is not going to cope. (This is redundant
* with the check in CheckAlterTableIsSafe, but for safety we'll
* check here too.)
*/
if (RELATION_IS_OTHER_TEMP(OldHeap))
ereport(ERROR,
@ -6619,7 +6653,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
continue;
/* find_all_inheritors already got lock */
childrel = relation_open(childrelid, NoLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
CheckAlterTableIsSafe(childrel);
ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
relation_close(childrel, NoLock);
}
@ -6628,7 +6662,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
/*
* Obtain list of partitions of the given table, locking them all at the given
* lockmode and ensuring that they all pass CheckTableNotInUse.
* lockmode and ensuring that they all pass CheckAlterTableIsSafe.
*
* This function is a no-op if the given relation is not a partitioned table;
* in particular, nothing is done if it's a legacy inheritance parent.
@ -6649,7 +6683,7 @@ ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
/* find_all_inheritors already got lock */
childrel = table_open(lfirst_oid(cell), NoLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
CheckAlterTableIsSafe(childrel);
table_close(childrel, NoLock);
}
list_free(inh);
@ -6682,7 +6716,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
Relation childrel;
childrel = relation_open(childrelid, lockmode);
CheckTableNotInUse(childrel, "ALTER TABLE");
CheckAlterTableIsSafe(childrel);
ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
relation_close(childrel, NoLock);
}
@ -7354,7 +7388,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* find_inheritance_children already got lock */
childrel = table_open(childrelid, NoLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
CheckAlterTableIsSafe(childrel);
/* Find or create work queue entry for this table */
childtab = ATGetQueueEntry(wqueue, childrel);
@ -9031,7 +9065,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/* find_inheritance_children already got lock */
childrel = table_open(childrelid, NoLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
CheckAlterTableIsSafe(childrel);
tuple = SearchSysCacheCopyAttName(childrelid, colName);
if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
@ -9514,7 +9548,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* find_inheritance_children already got lock */
childrel = table_open(childrelid, NoLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
CheckAlterTableIsSafe(childrel);
/* Find or create work queue entry for this table */
childtab = ATGetQueueEntry(wqueue, childrel);
@ -10343,7 +10377,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
referenced;
ListCell *cell;
CheckTableNotInUse(partition, "ALTER TABLE");
CheckAlterTableIsSafe(partition);
attmap = build_attrmap_by_name(RelationGetDescr(partition),
RelationGetDescr(rel),
@ -12460,7 +12494,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/* Must match lock taken by RemoveTriggerById: */
frel = table_open(con->confrelid, AccessExclusiveLock);
CheckTableNotInUse(frel, "ALTER TABLE");
CheckAlterTableIsSafe(frel);
table_close(frel, NoLock);
}
@ -12537,7 +12571,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/* find_inheritance_children already got lock */
childrel = table_open(childrelid, NoLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
CheckAlterTableIsSafe(childrel);
ScanKeyInit(&skey[0],
Anum_pg_constraint_conrelid,
@ -12840,7 +12874,7 @@ ATPrepAlterColumnType(List **wqueue,
/* find_all_inheritors already got lock */
childrel = relation_open(childrelid, NoLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
CheckAlterTableIsSafe(childrel);
/*
* Verify that the child doesn't have any inherited definitions of