!2351 回合PG漏洞:CVE-2022-2625,创建extension中带有create or replace语句可能导致越权
Merge pull request !2351 from luajk/CVE-2022-2625
This commit is contained in:
@ -393,9 +393,7 @@ Oid PackageSpecCreate(Oid pkgNamespace, const char* pkgName, const Oid ownerId,
|
||||
heap_close(pkgDesc, RowExclusiveLock);
|
||||
|
||||
/* Record dependencies */
|
||||
myself.classId = PackageRelationId;
|
||||
myself.objectId = pkgOid;
|
||||
myself.objectSubId = 0;
|
||||
ObjectAddressSet(myself, PackageRelationId, pkgOid);
|
||||
isUpgrade = u_sess->attr.attr_common.IsInplaceUpgrade && myself.objectId < FirstBootstrapObjectId && !isReplaced;
|
||||
if (isUpgrade) {
|
||||
recordPinnedDependency(&myself);
|
||||
@ -408,7 +406,7 @@ Oid PackageSpecCreate(Oid pkgNamespace, const char* pkgName, const Oid ownerId,
|
||||
|
||||
recordDependencyOnOwner(PackageRelationId, pkgOid, ownerId);
|
||||
|
||||
recordDependencyOnCurrentExtension(&myself, false);
|
||||
recordDependencyOnCurrentExtension(&myself, isReplaced);
|
||||
}
|
||||
|
||||
/* Post creation hook for new schema */
|
||||
|
||||
@ -161,6 +161,14 @@ void recordDependencyOnCurrentExtension(const ObjectAddress* object, bool isRepl
|
||||
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(u_sess->cmd_cxt.CurrentExtensionObject)),
|
||||
errdetail("An extension is not allowed to replace an object that it does not own.")));
|
||||
}
|
||||
|
||||
/* OK, record it as a member of CurrentExtensionObject */
|
||||
@ -172,6 +180,49 @@ void recordDependencyOnCurrentExtension(const ObjectAddress* object, bool isRepl
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 == u_sess->cmd_cxt.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(u_sess->cmd_cxt.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.")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Record pinned dependency for fabricated system tables during in-place upgrade.
|
||||
*/
|
||||
|
||||
@ -47,7 +47,7 @@ static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
|
||||
static Oid get_other_operator(List* otherOp, Oid otherLeftTypeId, Oid otherRightTypeId, const char* operatorName,
|
||||
Oid operatorNamespace, Oid leftTypeId, Oid rightTypeId, bool isCommutator);
|
||||
|
||||
static void makeOperatorDependencies(HeapTuple tuple);
|
||||
static void makeOperatorDependencies(HeapTuple tuple, bool isUpdate);
|
||||
|
||||
/*
|
||||
* Check whether a proposed operator name is legal
|
||||
@ -234,7 +234,7 @@ static Oid OperatorShellMake(const char* operatorName, Oid operatorNamespace, Oi
|
||||
CatalogUpdateIndexes(pg_operator_desc, tup);
|
||||
|
||||
/* Add dependencies for the entry */
|
||||
makeOperatorDependencies(tup);
|
||||
makeOperatorDependencies(tup, false);
|
||||
|
||||
heap_freetuple_ext(tup);
|
||||
|
||||
@ -306,6 +306,7 @@ void OperatorCreate(const char* operatorName, Oid operatorNamespace, Oid leftTyp
|
||||
TupleDesc tupDesc;
|
||||
int i;
|
||||
Oid oprowner = InvalidOid;
|
||||
bool isUpdate = false;
|
||||
/*
|
||||
* isalter is true, change the owner of the objects as the owner of the
|
||||
* namespace, if the owner of the namespce has the same name as the namescpe
|
||||
@ -454,6 +455,7 @@ void OperatorCreate(const char* operatorName, Oid operatorNamespace, Oid leftTyp
|
||||
* If we are replacing an operator shell, update; else insert
|
||||
*/
|
||||
if (operatorObjectId) {
|
||||
isUpdate = true;
|
||||
tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(operatorObjectId));
|
||||
if (!HeapTupleIsValid(tup))
|
||||
ereport(ERROR,
|
||||
@ -474,7 +476,7 @@ void OperatorCreate(const char* operatorName, Oid operatorNamespace, Oid leftTyp
|
||||
CatalogUpdateIndexes(pg_operator_desc, tup);
|
||||
|
||||
/* Add dependencies for the entry */
|
||||
makeOperatorDependencies(tup);
|
||||
makeOperatorDependencies(tup, isUpdate);
|
||||
|
||||
/* Post creation hook for new operator */
|
||||
InvokeObjectAccessHook(OAT_POST_CREATE, OperatorRelationId, operatorObjectId, 0, NULL);
|
||||
@ -680,7 +682,7 @@ static void OperatorUpd(Oid baseId, Oid commId, Oid negId)
|
||||
* NB: the OidIsValid tests in this routine are necessary, in case
|
||||
* the given operator is a shell.
|
||||
*/
|
||||
static void makeOperatorDependencies(HeapTuple tuple)
|
||||
static void makeOperatorDependencies(HeapTuple tuple, bool isUpdate)
|
||||
{
|
||||
Form_pg_operator oper = (Form_pg_operator)GETSTRUCT(tuple);
|
||||
ObjectAddress myself, referenced;
|
||||
@ -693,8 +695,10 @@ static void makeOperatorDependencies(HeapTuple tuple)
|
||||
* In case we are updating a shell, delete any existing entries, except
|
||||
* for extension membership which should remain the same.
|
||||
*/
|
||||
(void)deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
|
||||
deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
|
||||
if (isUpdate) {
|
||||
(void)deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
|
||||
deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
|
||||
}
|
||||
|
||||
/* Dependency on namespace */
|
||||
if (OidIsValid(oper->oprnamespace)) {
|
||||
@ -765,5 +769,5 @@ static void makeOperatorDependencies(HeapTuple tuple)
|
||||
recordDependencyOnOwner(OperatorRelationId, HeapTupleGetOid(tuple), oper->oprowner);
|
||||
|
||||
/* Dependency on extension */
|
||||
recordDependencyOnCurrentExtension(&myself, true);
|
||||
recordDependencyOnCurrentExtension(&myself, isUpdate);
|
||||
}
|
||||
|
||||
@ -223,13 +223,11 @@ static Oid SynonymCreate(
|
||||
/* update the index if any */
|
||||
CatalogUpdateIndexes(rel, tuple);
|
||||
|
||||
ObjectAddressSet(myself, PgSynonymRelationId, HeapTupleGetOid(tuple));
|
||||
|
||||
/* record the dependencies, for the first create. */
|
||||
if (!isUpdate) {
|
||||
/* dependency on namespace of synonym object */
|
||||
myself.classId = PgSynonymRelationId;
|
||||
myself.objectId = HeapTupleGetOid(tuple);
|
||||
myself.objectSubId = 0;
|
||||
|
||||
referenced.classId = NamespaceRelationId;
|
||||
referenced.objectId = synNamespace;
|
||||
referenced.objectSubId = 0;
|
||||
@ -239,6 +237,8 @@ static Oid SynonymCreate(
|
||||
recordDependencyOnOwner(myself.classId, myself.objectId, synOwner);
|
||||
}
|
||||
|
||||
recordDependencyOnCurrentExtension(&myself, isUpdate);
|
||||
|
||||
heap_freetuple(tuple);
|
||||
heap_close(rel, RowExclusiveLock);
|
||||
|
||||
|
||||
@ -291,6 +291,16 @@ Oid *namespaceid, bool isFirstNode)
|
||||
*/
|
||||
if (stmt->if_not_exists && OidIsValid(existing_relid)) {
|
||||
bool exists_ok = true;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* Emit the right error or warning message for a "CREATE" command issued on a exist relation.
|
||||
* remote node : should have relation if recieve "IF NOT EXISTS" stmt.
|
||||
|
||||
@ -155,6 +155,7 @@ void CreatePgDirectory(CreateDirectoryStmt* stmt)
|
||||
HeapTuple oldtup = NULL;
|
||||
HeapTuple tup = NULL;
|
||||
TupleDesc tupDesc = NULL;
|
||||
bool isUpdate = false;
|
||||
errno_t rc;
|
||||
|
||||
/* Permission check. */
|
||||
@ -191,6 +192,7 @@ void CreatePgDirectory(CreateDirectoryStmt* stmt)
|
||||
if (OidIsValid(targetoid)) {
|
||||
/* existing a entry before */
|
||||
if (stmt->replace) {
|
||||
isUpdate = true;
|
||||
replaces[Anum_pg_directory_directory_name - 1] = false;
|
||||
oldtup = SearchSysCache1(DIRECTORYOID, targetoid);
|
||||
tupDesc = RelationGetDescr(rel);
|
||||
@ -224,6 +226,10 @@ void CreatePgDirectory(CreateDirectoryStmt* stmt)
|
||||
pfree(location);
|
||||
heap_close(rel, RowExclusiveLock);
|
||||
}
|
||||
|
||||
ObjectAddress myself;
|
||||
ObjectAddressSet(myself, PgDirectoryRelationId, isUpdate ? targetoid : directoryId);
|
||||
recordDependencyOnCurrentExtension(&myself, isUpdate);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -104,6 +104,7 @@ void CreateSchemaCommand(CreateSchemaStmt* stmt, const char* queryString)
|
||||
int save_sec_context;
|
||||
AclResult aclresult;
|
||||
char* queryStringwithinfo = (char*)queryString;
|
||||
ObjectAddress address;
|
||||
|
||||
GetUserIdAndSecContext(&saved_uid, &save_sec_context);
|
||||
if (IsExistPackageName(schemaName)) {
|
||||
@ -220,10 +221,19 @@ void CreateSchemaCommand(CreateSchemaStmt* stmt, const char* queryString)
|
||||
|
||||
/* make sure there is no existing namespace of same name */
|
||||
if (stmt->missing_ok) {
|
||||
if (SearchSysCacheExists1(NAMESPACENAME, PointerGetDatum(schemaName))) {
|
||||
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, (errmsg("schema \"%s\" already exists,skipping", schemaName)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the schema's namespace */
|
||||
|
||||
@ -162,6 +162,7 @@ static Oid DefineVirtualRelation(RangeVar* relation, List* tlist, bool replace,
|
||||
TupleDesc descriptor;
|
||||
List* atcmds = NIL;
|
||||
AlterTableCmd* atcmd = NULL;
|
||||
ObjectAddress address;
|
||||
|
||||
/*
|
||||
* During inplace upgrade, if we are doing rolling back, the old-versioned
|
||||
@ -259,6 +260,22 @@ static Oid DefineVirtualRelation(RangeVar* relation, List* tlist, bool replace,
|
||||
/* OK, let's do it. */
|
||||
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.
|
||||
*/
|
||||
|
||||
@ -289,6 +289,8 @@ extern void recordMultipleDependencies(const ObjectAddress *depender,
|
||||
extern void recordDependencyOnCurrentExtension(const ObjectAddress *object,
|
||||
bool isReplace);
|
||||
|
||||
extern void checkMembershipInCurrentExtension(const ObjectAddress *object);
|
||||
|
||||
extern void recordPinnedDependency(const ObjectAddress *object);
|
||||
|
||||
extern bool IsPackageDependType(Oid typOid, Oid pkgOid, bool isRefCur = false);
|
||||
|
||||
@ -41,6 +41,16 @@ typedef struct ObjectAddress
|
||||
char deptype; /* Indicates the deptype that the object is referenced by other object. */
|
||||
} ObjectAddress;
|
||||
|
||||
#define ObjectAddressSubSet(addr, class_id, object_id, object_sub_id) \
|
||||
do { \
|
||||
(addr).classId = (class_id); \
|
||||
(addr).objectId = (object_id); \
|
||||
(addr).objectSubId = (object_sub_id); \
|
||||
} while (0)
|
||||
|
||||
#define ObjectAddressSet(addr, class_id, object_id) \
|
||||
ObjectAddressSubSet(addr, class_id, object_id, 0)
|
||||
|
||||
extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
|
||||
List *objargs, Relation *relp,
|
||||
LOCKMODE lockmode, bool missing_ok);
|
||||
|
||||
Reference in New Issue
Block a user