aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/catalog/pg_collation.c46
-rw-r--r--src/backend/catalog/pg_depend.c74
-rw-r--r--src/backend/catalog/pg_operator.c2
-rw-r--r--src/backend/catalog/pg_type.c7
-rw-r--r--src/backend/commands/createas.c18
-rw-r--r--src/backend/commands/foreigncmds.c19
-rw-r--r--src/backend/commands/schemacmds.c25
-rw-r--r--src/backend/commands/sequence.c8
-rw-r--r--src/backend/commands/statscmds.c4
-rw-r--r--src/backend/commands/view.c16
-rw-r--r--src/backend/parser/parse_utilcmd.c10
11 files changed, 191 insertions, 38 deletions
diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c
index ca62896ecbb..42c12c1be28 100644
--- a/src/backend/catalog/pg_collation.c
+++ b/src/backend/catalog/pg_collation.c
@@ -78,15 +78,24 @@ CollationCreate(const char *collname, Oid collnamespace,
* friendlier error message. The unique index provides a backstop against
* race conditions.
*/
- if (SearchSysCacheExists3(COLLNAMEENCNSP,
- PointerGetDatum(collname),
- Int32GetDatum(collencoding),
- ObjectIdGetDatum(collnamespace)))
+ oid = GetSysCacheOid3(COLLNAMEENCNSP,
+ PointerGetDatum(collname),
+ Int32GetDatum(collencoding),
+ ObjectIdGetDatum(collnamespace));
+ if (OidIsValid(oid))
{
if (quiet)
return InvalidOid;
else if (if_not_exists)
{
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(myself, CollationRelationId, oid);
+ checkMembershipInCurrentExtension(&myself);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
collencoding == -1
@@ -116,16 +125,17 @@ CollationCreate(const char *collname, Oid collnamespace,
* so we take a ShareRowExclusiveLock earlier, to protect against
* concurrent changes fooling this check.
*/
- if ((collencoding == -1 &&
- SearchSysCacheExists3(COLLNAMEENCNSP,
- PointerGetDatum(collname),
- Int32GetDatum(GetDatabaseEncoding()),
- ObjectIdGetDatum(collnamespace))) ||
- (collencoding != -1 &&
- SearchSysCacheExists3(COLLNAMEENCNSP,
- PointerGetDatum(collname),
- Int32GetDatum(-1),
- ObjectIdGetDatum(collnamespace))))
+ if (collencoding == -1)
+ oid = GetSysCacheOid3(COLLNAMEENCNSP,
+ PointerGetDatum(collname),
+ Int32GetDatum(GetDatabaseEncoding()),
+ ObjectIdGetDatum(collnamespace));
+ else
+ oid = GetSysCacheOid3(COLLNAMEENCNSP,
+ PointerGetDatum(collname),
+ Int32GetDatum(-1),
+ ObjectIdGetDatum(collnamespace));
+ if (OidIsValid(oid))
{
if (quiet)
{
@@ -134,6 +144,14 @@ CollationCreate(const char *collname, Oid collnamespace,
}
else if (if_not_exists)
{
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(myself, CollationRelationId, oid);
+ checkMembershipInCurrentExtension(&myself);
+
+ /* OK to skip */
heap_close(rel, NoLock);
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 63741b31c42..a0b21807e2c 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -125,15 +125,23 @@ recordMultipleDependencies(const ObjectAddress *depender,
/*
* If we are executing a CREATE EXTENSION operation, mark the given object
- * as being a member of the extension. Otherwise, do nothing.
+ * as being a member of the extension, or check that it already is one.
+ * Otherwise, do nothing.
*
* This must be called during creation of any user-definable object type
* that could be a member of an extension.
*
- * If isReplace is true, the object already existed (or might have already
- * existed), so we must check for a pre-existing extension membership entry.
- * Passing false is a guarantee that the object is newly created, and so
- * could not already be a member of any extension.
+ * isReplace must be true if the object already existed, and false if it is
+ * newly created. In the former case we insist that it already be a member
+ * of the current extension. In the latter case we can skip checking whether
+ * it is already a member of any extension.
+ *
+ * Note: isReplace = true is typically used when updating a object in
+ * CREATE OR REPLACE and similar commands. We used to allow the target
+ * object to not already be an extension member, instead silently absorbing
+ * it into the current extension. However, this was both error-prone
+ * (extensions might accidentally overwrite free-standing objects) and
+ * a security hazard (since the object would retain its previous ownership).
*/
void
recordDependencyOnCurrentExtension(const ObjectAddress *object,
@@ -151,6 +159,12 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
{
Oid oldext;
+ /*
+ * Side note: these catalog lookups are safe only because the
+ * object is a pre-existing one. In the not-isReplace case, the
+ * caller has most likely not yet done a CommandCounterIncrement
+ * that would make the new object visible.
+ */
oldext = getExtensionOfObject(object->classId, object->objectId);
if (OidIsValid(oldext))
{
@@ -164,6 +178,13 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
getObjectDescription(object),
get_extension_name(oldext))));
}
+ /* It's a free-standing object, so reject */
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("%s is not a member of extension \"%s\"",
+ getObjectDescription(object),
+ get_extension_name(CurrentExtensionObject)),
+ errdetail("An extension is not allowed to replace an object that it does not own.")));
}
/* OK, record it as a member of CurrentExtensionObject */
@@ -176,6 +197,49 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
}
/*
+ * If we are executing a CREATE EXTENSION operation, check that the given
+ * object is a member of the extension, and throw an error if it isn't.
+ * Otherwise, do nothing.
+ *
+ * This must be called whenever a CREATE IF NOT EXISTS operation (for an
+ * object type that can be an extension member) has found that an object of
+ * the desired name already exists. It is insecure for an extension to use
+ * IF NOT EXISTS except when the conflicting object is already an extension
+ * member; otherwise a hostile user could substitute an object with arbitrary
+ * properties.
+ */
+void
+checkMembershipInCurrentExtension(const ObjectAddress *object)
+{
+ /*
+ * This is actually the same condition tested in
+ * recordDependencyOnCurrentExtension; but we want to issue a
+ * differently-worded error, and anyway it would be pretty confusing to
+ * call recordDependencyOnCurrentExtension in these circumstances.
+ */
+
+ /* Only whole objects can be extension members */
+ Assert(object->objectSubId == 0);
+
+ if (creating_extension)
+ {
+ Oid oldext;
+
+ oldext = getExtensionOfObject(object->classId, object->objectId);
+ /* If already a member of this extension, OK */
+ if (oldext == CurrentExtensionObject)
+ return;
+ /* Else complain */
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("%s is not a member of extension \"%s\"",
+ getObjectDescription(object),
+ get_extension_name(CurrentExtensionObject)),
+ errdetail("An extension may only use CREATE ... IF NOT EXISTS to skip object creation if the conflicting object is one that it already owns.")));
+ }
+}
+
+/*
* deleteDependencyRecordsFor -- delete all records with given depender
* classId/objectId. Returns the number of records deleted.
*
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index ef811021504..88dfe97cb6a 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -858,7 +858,7 @@ makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
oper->oprowner);
/* Dependency on extension */
- recordDependencyOnCurrentExtension(&myself, true);
+ recordDependencyOnCurrentExtension(&myself, isUpdate);
return myself;
}
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 7c0040bc83f..e7815beb98e 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -512,10 +512,9 @@ TypeCreate(Oid newTypeOid,
* If rebuild is true, we remove existing dependencies and rebuild them
* from scratch. This is needed for ALTER TYPE, and also when replacing
* a shell type. We don't remove an existing extension dependency, though.
- * (That means an extension can't absorb a shell type created in another
- * extension, nor ALTER a type created by another extension. Also, if it
- * replaces a free-standing shell type or ALTERs a free-standing type,
- * that type will become a member of the extension.)
+ * That means an extension can't absorb a shell type that is free-standing
+ * or belongs to another extension, nor ALTER a type that is free-standing or
+ * belongs to another extension.
*/
void
GenerateTypeDependencies(Oid typeObjectId,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 97f9c55d6e7..7b4fee8e8ad 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -240,15 +240,27 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
if (stmt->if_not_exists)
{
Oid nspid;
+ Oid oldrelid;
- nspid = RangeVarGetCreationNamespace(stmt->into->rel);
+ nspid = RangeVarGetCreationNamespace(into->rel);
- if (get_relname_relid(stmt->into->rel->relname, nspid))
+ oldrelid = get_relname_relid(into->rel->relname, nspid);
+ if (OidIsValid(oldrelid))
{
+ /*
+ * The relation exists and IF NOT EXISTS has been specified.
+ *
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(address, RelationRelationId, oldrelid);
+ checkMembershipInCurrentExtension(&address);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists, skipping",
- stmt->into->rel->relname)));
+ into->rel->relname)));
return InvalidObjectAddress;
}
}
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index dd43b073711..fbe32a1b7c3 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -878,13 +878,22 @@ CreateForeignServer(CreateForeignServerStmt *stmt)
ownerId = GetUserId();
/*
- * Check that there is no other foreign server by this name. Do nothing if
- * IF NOT EXISTS was enforced.
+ * Check that there is no other foreign server by this name. If there is
+ * one, do nothing if IF NOT EXISTS was specified.
*/
- if (GetForeignServerByName(stmt->servername, true) != NULL)
+ srvId = get_foreign_server_oid(stmt->servername, true);
+ if (OidIsValid(srvId))
{
if (stmt->if_not_exists)
{
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(myself, ForeignServerRelationId, srvId);
+ checkMembershipInCurrentExtension(&myself);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("server \"%s\" already exists, skipping",
@@ -1170,6 +1179,10 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
{
if (stmt->if_not_exists)
{
+ /*
+ * Since user mappings aren't members of extensions (see comments
+ * below), no need for checkMembershipInCurrentExtension here.
+ */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("user mapping for \"%s\" already exists for server %s, skipping",
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index f9ea73f9233..288cc3262be 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -113,14 +113,25 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
* the permissions checks, but since CREATE TABLE IF NOT EXISTS makes its
* creation-permission check first, we do likewise.
*/
- if (stmt->if_not_exists &&
- SearchSysCacheExists1(NAMESPACENAME, PointerGetDatum(schemaName)))
+ if (stmt->if_not_exists)
{
- ereport(NOTICE,
- (errcode(ERRCODE_DUPLICATE_SCHEMA),
- errmsg("schema \"%s\" already exists, skipping",
- schemaName)));
- return InvalidOid;
+ namespaceId = get_namespace_oid(schemaName, true);
+ if (OidIsValid(namespaceId))
+ {
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(address, NamespaceRelationId, namespaceId);
+ checkMembershipInCurrentExtension(&address);
+
+ /* OK to skip */
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_SCHEMA),
+ errmsg("schema \"%s\" already exists, skipping",
+ schemaName)));
+ return InvalidOid;
+ }
}
/*
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 74cb2e61bcd..88525c5501c 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -147,6 +147,14 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
RangeVarGetAndCheckCreationNamespace(seq->sequence, NoLock, &seqoid);
if (OidIsValid(seqoid))
{
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddressSet(address, RelationRelationId, seqoid);
+ checkMembershipInCurrentExtension(&address);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists, skipping",
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index d30d1faac60..4473ace3e50 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -164,6 +164,10 @@ CreateStatistics(CreateStatsStmt *stmt)
{
if (stmt->if_not_exists)
{
+ /*
+ * Since stats objects aren't members of extensions (see comments
+ * below), no need for checkMembershipInCurrentExtension here.
+ */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("statistics object \"%s\" already exists, skipping",
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 615e191f4e7..24836bbdc6c 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -205,7 +205,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
CommandCounterIncrement();
/*
- * Finally update the view options.
+ * Update the view's options.
*
* The new options list replaces the existing options list, even if
* it's empty.
@@ -218,8 +218,22 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
/* EventTriggerAlterTableStart called by ProcessUtilitySlow */
AlterTableInternal(viewOid, atcmds, true);
+ /*
+ * There is very little to do here to update the view's dependencies.
+ * Most view-level dependency relationships, such as those on the
+ * owner, schema, and associated composite type, aren't changing.
+ * Because we don't allow changing type or collation of an existing
+ * view column, those dependencies of the existing columns don't
+ * change either, while the AT_AddColumnToView machinery took care of
+ * adding such dependencies for new view columns. The dependencies of
+ * the view's query could have changed arbitrarily, but that was dealt
+ * with inside StoreViewQuery. What remains is only to check that
+ * view replacement is allowed when we're creating an extension.
+ */
ObjectAddressSet(address, RelationRelationId, viewOid);
+ recordDependencyOnCurrentExtension(&address, true);
+
/*
* Seems okay, so return the OID of the pre-existing view.
*/
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 616de81570f..5587eb5c76f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -206,6 +206,16 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
if (stmt->if_not_exists && OidIsValid(existing_relid))
{
+ /*
+ * If we are in an extension script, insist that the pre-existing
+ * object be a member of the extension, to avoid security risks.
+ */
+ ObjectAddress address;
+
+ ObjectAddressSet(address, RelationRelationId, existing_relid);
+ checkMembershipInCurrentExtension(&address);
+
+ /* OK to skip */
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists, skipping",