Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ SET SESSION AUTHORIZATION regress_unpriv_user;
SET search_path TO temp_func_test, public;
ALTER FUNCTION functest_E_1(int) NOT LEAKPROOF;
ALTER FUNCTION functest_E_2(int) LEAKPROOF;
ERROR: only superuser can define a leakproof function
ERROR: only superuser or mdb_admin can define a leakproof function
CREATE FUNCTION functest_E_3(int) RETURNS bool LANGUAGE 'sql'
LEAKPROOF AS 'SELECT $1 < 200'; -- fail
ERROR: only superuser can define a leakproof function
ERROR: only superuser or mdb_admin can define a leakproof function
RESET SESSION AUTHORIZATION;
--
-- CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ SET SESSION AUTHORIZATION regress_unpriv_user;
SET search_path TO temp_func_test, public;
ALTER FUNCTION functest_E_1(int) NOT LEAKPROOF;
ALTER FUNCTION functest_E_2(int) LEAKPROOF;
ERROR: only superuser can define a leakproof function
ERROR: only superuser or mdb_admin can define a leakproof function
CREATE FUNCTION functest_E_3(int) RETURNS bool LANGUAGE 'sql'
LEAKPROOF AS 'SELECT $1 < 200'; -- fail
ERROR: only superuser can define a leakproof function
ERROR: only superuser or mdb_admin can define a leakproof function
RESET SESSION AUTHORIZATION;
--
-- CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
Expand Down
20 changes: 18 additions & 2 deletions src/backend/catalog/namespace.c
Original file line number Diff line number Diff line change
Expand Up @@ -2971,7 +2971,6 @@ LookupExplicitNamespace(const char *nspname, bool missing_ok)
{
Oid namespaceId;
AclResult aclresult;

/* check for pg_temp alias */
if (strcmp(nspname, "pg_temp") == 0)
{
Expand All @@ -2989,7 +2988,24 @@ LookupExplicitNamespace(const char *nspname, bool missing_ok)
if (missing_ok && !OidIsValid(namespaceId))
return InvalidOid;

aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE);
HeapTuple tuple;
Oid ownerId;

tuple = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(namespaceId));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("schema with OID %u does not exist", namespaceId)));

ownerId = ((Form_pg_namespace) GETSTRUCT(tuple))->nspowner;

ReleaseSysCache(tuple);

if (!mdb_admin_allow_bypass_owner_checks(GetUserId(), ownerId)) {
aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE);
} else {
aclresult = ACLCHECK_OK;
}
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_SCHEMA,
nspname);
Expand Down
8 changes: 4 additions & 4 deletions src/backend/commands/alter.c
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,8 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId)
if (!superuser())
{
/* must be owner */
if (!has_privs_of_role(GetUserId(), old_ownerId))
if (!has_privs_of_role(GetUserId(), old_ownerId)
&& !mdb_admin_allow_bypass_owner_checks(GetUserId(), old_ownerId))
{
char *objname;
char namebuf[NAMEDATALEN];
Expand All @@ -1105,14 +1106,13 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId)
aclcheck_error(ACLCHECK_NOT_OWNER, get_object_type(classId, objectId),
objname);
}
/* Must be able to become new owner */
check_is_member_of_role(GetUserId(), new_ownerId);

check_mdb_admin_is_member_of_role(GetUserId(), new_ownerId);

/* New owner must have CREATE privilege on namespace */
if (OidIsValid(namespaceId))
{
AclResult aclresult;

aclresult = pg_namespace_aclcheck(namespaceId, new_ownerId,
ACL_CREATE);
if (aclresult != ACLCHECK_OK)
Expand Down
20 changes: 14 additions & 6 deletions src/backend/commands/functioncmds.c
Original file line number Diff line number Diff line change
Expand Up @@ -1525,9 +1525,13 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
* by security barrier views or row-level security policies.
*/
if (isLeakProof && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser can define a leakproof function")));
{
Oid role = get_role_oid("mdb_admin", true);
if (!is_member_of_role(GetUserId(), role))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser or mdb_admin can define a leakproof function")));
}

if (transformDefElem)
{
Expand Down Expand Up @@ -1852,9 +1856,13 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
{
procForm->proleakproof = intVal(leakproof_item->arg);
if (procForm->proleakproof && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser can define a leakproof function")));
{
Oid role = get_role_oid("mdb_admin", true);
if (!is_member_of_role(GetUserId(), role))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser or mdb_admin can define a leakproof function")));
}
}
if (cost_item)
{
Expand Down
13 changes: 9 additions & 4 deletions src/backend/commands/schemacmds.c
Original file line number Diff line number Diff line change
Expand Up @@ -598,12 +598,12 @@ AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId)
AclResult aclresult;

/* Otherwise, must be owner of the existing object */
if (!pg_namespace_ownercheck(nspForm->oid, GetUserId()))
if (!mdb_admin_allow_bypass_owner_checks(GetUserId(), nspForm->nspowner)
&& !pg_namespace_ownercheck(nspForm->oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
NameStr(nspForm->nspname));

/* Must be able to become new owner */
check_is_member_of_role(GetUserId(), newOwnerId);
check_mdb_admin_is_member_of_role(GetUserId(), newOwnerId);

/*
* must have create-schema rights
Expand All @@ -614,8 +614,13 @@ AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId)
* schemas. Because superusers will always have this right, we need
* no special case for them.
*/
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(),
if (mdb_admin_allow_bypass_owner_checks(GetUserId(), nspForm->nspowner)) {
aclresult = ACLCHECK_OK;
} else {
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(),
ACL_CREATE);
}

if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
Expand Down
12 changes: 7 additions & 5 deletions src/backend/commands/tablecmds.c
Original file line number Diff line number Diff line change
Expand Up @@ -15708,13 +15708,14 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
AclResult aclresult;

/* Otherwise, must be owner of the existing object */
if (!pg_class_ownercheck(relationOid, GetUserId()))
if (!mdb_admin_allow_bypass_owner_checks(GetUserId(), tuple_class->relowner)
&& !pg_class_ownercheck(relationOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relationOid)),
RelationGetRelationName(target_rel));

/* Must be able to become new owner */
check_is_member_of_role(GetUserId(), newOwnerId);

check_mdb_admin_is_member_of_role(GetUserId(), newOwnerId);

/* New owner must have CREATE privilege on namespace */
aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId,
ACL_CREATE);
Expand Down Expand Up @@ -20795,15 +20796,16 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
Form_pg_class classform;
AclResult aclresult;
char relkind;

tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped */
classform = (Form_pg_class) GETSTRUCT(tuple);
relkind = classform->relkind;

/* Must own relation. */
if (!pg_class_ownercheck(relid, GetUserId()))
if (!mdb_admin_allow_bypass_owner_checks(GetUserId(), classform->relowner)
&& !pg_class_ownercheck(relid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);

/* No system table modifications unless explicitly allowed. */
Expand Down
28 changes: 27 additions & 1 deletion src/backend/storage/ipc/signalfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ static int
pg_signal_backend(int pid, int sig, char *msg)
{
PGPROC *proc = BackendPidGetProc(pid);
LocalPgBackendStatus *local_beentry;

/*
* BackendPidGetProc returns NULL if the pid isn't valid; but by the time
Expand All @@ -72,9 +73,34 @@ pg_signal_backend(int pid, int sig, char *msg)
return SIGNAL_BACKEND_ERROR;
}

local_beentry = pgstat_fetch_stat_local_beentry_by_pid(pid);

/* Only allow superusers to signal superuser-owned backends. */
if (superuser_arg(proc->roleId) && !superuser())
return SIGNAL_BACKEND_NOSUPERUSER;
{
Oid role;
char * appname;

if (local_beentry == NULL) {
return SIGNAL_BACKEND_NOSUPERUSER;
}

role = get_role_oid("mdb_admin", true /*if nodoby created mdb_admin role in this database*/);
appname = local_beentry->backendStatus.st_appname;

// only allow mdb_admin to kill su queries
if (!is_member_of_role(GetUserId(), role)) {
return SIGNAL_BACKEND_NOSUPERUSER;
}

if (local_beentry->backendStatus.st_backendType == B_AUTOVAC_WORKER) {
// ok
} else if (appname != NULL && strcmp(appname, "MDB") == 0) {
// ok
} else {
return SIGNAL_BACKEND_NOSUPERUSER;
}
}

/* Users can signal backends they have role membership in. */
if (!has_privs_of_role(GetUserId(), proc->roleId) &&
Expand Down
16 changes: 16 additions & 0 deletions src/backend/utils/activity/backend_status.c
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,22 @@ pgstat_fetch_stat_local_beentry(int beid)
return &localBackendStatusTable[beid - 1];
}

/* -- mdb admin patch -- */
LocalPgBackendStatus *
pgstat_fetch_stat_local_beentry_by_pid(int pid)
{
pgstat_read_current_status();

for (int i = 1; i <= localNumBackends; ++i) {
if (localBackendStatusTable[i - 1].backendStatus.st_procpid == pid) {
return &localBackendStatusTable[i - 1];
}
}

return NULL;
}

/* -- mdb admin patch end -- */

/* ----------
* pgstat_fetch_stat_numbackends() -
Expand Down
112 changes: 112 additions & 0 deletions src/backend/utils/adt/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -5012,6 +5012,60 @@ has_privs_of_role(Oid member, Oid role)
}


// -- non-upstream patch begin
/*
* Is userId allowed to bypass ownership check
* and tranfer onwership to ownerId role?
*/
bool
mdb_admin_allow_bypass_owner_checks(Oid userId, Oid ownerId)
{
Oid mdb_admin_roleoid;
/*
* Never allow nobody to grant objects to
* superusers.
* This can result in various CVE.
* For paranoic reasons, check this even before
* membership of mdb_admin role.
*/
if (superuser_arg(ownerId)) {
return false;
}

mdb_admin_roleoid = get_role_oid("mdb_admin", true /* superuser suggested to be mdb_admin*/);
/* Is userId actually member of mdb admin? */
if (!is_member_of_role(userId, mdb_admin_roleoid)) {
/* if no, disallow. */
return false;
}

/*
* Now, we need to check if ownerId
* is some dangerous role to trasfer membership to.
*
* For now, we check that ownerId does not have
* priviledge to execute server program or/and
* read/write server files.
*/

if (has_privs_of_role(ownerId, ROLE_PG_READ_SERVER_FILES)) {
return false;
}

if (has_privs_of_role(ownerId, ROLE_PG_WRITE_SERVER_FILES)) {
return false;
}

if (has_privs_of_role(ownerId, ROLE_PG_EXECUTE_SERVER_PROGRAM)) {
return false;
}

/* All checks passed, hope will not be hacked here (again) */
return true;
}

// -- non-upstream patch end

/*
* Is member a member of role (directly or indirectly)?
*
Expand Down Expand Up @@ -5051,6 +5105,64 @@ check_is_member_of_role(Oid member, Oid role)
GetUserNameFromId(role, false))));
}

// -- mdb admin patch
/*
* check_mdb_admin_is_member_of_role
* is_member_of_role with a standard permission-violation error if not in usual case
* Is case `member` in mdb_admin we check that role is neither of superuser, pg_read/write
* server files nor pg_execute_server_program
*/
void
check_mdb_admin_is_member_of_role(Oid member, Oid role)
{
Oid mdb_admin_roleoid;
/* fast path - if we are superuser, its ok */
if (superuser_arg(member)) {
return;
}

mdb_admin_roleoid = get_role_oid("mdb_admin", true /* superuser suggested to be mdb_admin*/);
/* Is userId actually member of mdb admin? */
if (is_member_of_role(member, mdb_admin_roleoid)) {
/* role is mdb admin */
if (superuser_arg(role)) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("cannot transfer ownership to superuser \"%s\"",
GetUserNameFromId(role, false))));
}

if (has_privs_of_role(role, ROLE_PG_READ_SERVER_FILES)) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("cannot transfer ownership to pg_read_server_files role in Cloud")));
}

if (has_privs_of_role(role, ROLE_PG_WRITE_SERVER_FILES)) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("cannot transfer ownership to pg_write_server_files role in Cloud")));
}

if (has_privs_of_role(role, ROLE_PG_EXECUTE_SERVER_PROGRAM)) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("cannot transfer ownership to pg_execute_server_program role in Cloud")));
}
} else {
/* if no, check membership transfer in usual way. */

if (!is_member_of_role(member, role)) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be member of role \"%s\"",
GetUserNameFromId(role, false))));
}
}
}

// -- mdb admin patch

/*
* Is member a member of role, not considering superuserness?
*
Expand Down
2 changes: 1 addition & 1 deletion src/backend/utils/misc/guc.c
Original file line number Diff line number Diff line change
Expand Up @@ -4918,7 +4918,7 @@ static struct config_enum ConfigureNamesEnum[] =
{
{"session_replication_role", PGC_SUSET, CLIENT_CONN_STATEMENT,
gettext_noop("Sets the session's behavior for triggers and rewrite rules."),
NULL
NULL, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, 0, true,
},
&SessionReplicationRole,
SESSION_REPLICATION_ROLE_ORIGIN, session_replication_role_options,
Expand Down
Loading
Loading