Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent dropping of critical system views, functions, and procedures #3480

Open
wants to merge 11 commits into
base: BABEL_5_X_DEV
Choose a base branch
from
120 changes: 78 additions & 42 deletions contrib/babelfishpg_tsql/src/pl_funcs-2.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "utils/builtins.h"
#include "utils/numeric.h"
#include "utils/syscache.h"
#include "utils/lsyscache.h"
#include "catalog/pg_proc.h"

static int
Expand Down Expand Up @@ -351,58 +352,93 @@ coalesce_typmod_hook_impl(const CoalesceExpr *cexpr)
return exprTypmod((Node *) linitial(cexpr->args));
}

/*
* Check if the given object is in the restricted list
*/
static bool
is_object_restricted(const char *objname, ObjectType type, const char *schema_name)
{
const RestrictedObject *restricted_objects;
size_t num_restricted_objects;

/* Determine which array to check based on schema */
if (pg_strcasecmp(schema_name, "master_dbo") == 0)
{
restricted_objects = master_dbo_objects;
num_restricted_objects = sizeof(master_dbo_objects)/sizeof(master_dbo_objects[0]);
}
else if (pg_strcasecmp(schema_name, "msdb_dbo") == 0)
{
restricted_objects = msdb_dbo_objects;
num_restricted_objects = sizeof(msdb_dbo_objects)/sizeof(msdb_dbo_objects[0]);
}
else
return false;

for (int idx = 0; idx < num_restricted_objects; idx++)
{
if (type == restricted_objects[idx].type &&
pg_strcasecmp(objname, restricted_objects[idx].name) == 0)
return true;
}

return false;
}

/*
* Main function to check and error out for restricted objects
*/
void
check_restricted_stored_procedure(Oid proc_id)
check_restricted_object(Oid object_id, ObjectType object_type)
{
HeapTuple proctup;
Form_pg_proc procform;
const char *procname;
Oid schema_oid = InvalidOid;
Oid dbo_oid = InvalidOid;
bool is_restricted = false;
HeapTuple tuple;
const char *objname;
const char *schema_name;
Oid schema_oid;

/*
* List of procedure names that are not allowed to be dropped. These procedures
* are considered essential or restricted due to security or operational reasons.
*/
static const char *restricted_procedures[] = {
"xp_qv",
"xp_instance_regread",
"sp_addlinkedsrvlogin",
"sp_droplinkedsrvlogin",
"sp_dropserver",
"sp_enum_oledb_providers",
"sp_testlinkedserver"
};
static const int RESTRICTED_PROCEDURES_COUNT = sizeof(restricted_procedures) / sizeof(restricted_procedures[0]);

proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc_id));

procform = (Form_pg_proc) GETSTRUCT(proctup);
procname = pstrdup(NameStr(procform->proname));
schema_oid = procform->pronamespace;
dbo_oid = get_namespace_oid("master_dbo", true);

if (OidIsValid(schema_oid) && OidIsValid(dbo_oid) && schema_oid == dbo_oid)
/* Get object information */
tuple = SearchSysCache1(object_type == OBJECT_PROCEDURE ? PROCOID : RELOID, ObjectIdGetDatum(object_id));

if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("object with OID %u does not exist", object_id)));

/* Get object name and schema */
if (object_type == OBJECT_PROCEDURE)
{
/* Check if the procedure name is in the restricted list */
for (int i = 0; i < RESTRICTED_PROCEDURES_COUNT; i++)
{
if (pg_strcasecmp(procname, restricted_procedures[i]) == 0)
{
is_restricted = true;
break;
}
}
Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(tuple);
objname = NameStr(procform->proname);
schema_oid = procform->pronamespace;
}
else
{
Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple);
objname = NameStr(classform->relname);
schema_oid = classform->relnamespace;
}

ReleaseSysCache(proctup);
if (is_restricted)
/* Get schema name */
if (OidIsValid(schema_oid))
schema_name = get_namespace_name(schema_oid);
else
{
ReleaseSysCache(tuple);
return;
}

/* Check if object is restricted and error out if it is */
if (is_object_restricted(objname, object_type, schema_name))
{
ReleaseSysCache(tuple);
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be owner of procedure %s", procname)));
errmsg("must be owner of %s %s",
object_type == OBJECT_PROCEDURE ? "procedure" : "view",
objname)));
}

ReleaseSysCache(tuple);
}

/* Determine whether a variable name is a predefined T-SQL global variable */
Expand Down
6 changes: 5 additions & 1 deletion contrib/babelfishpg_tsql/src/pl_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -6978,6 +6978,10 @@ bbf_ExecDropStmt(DropStmt *stmt)

if (!relation)
continue;

/* Restrict dropping of extended stored procedures for non-superuser roles */
if (stmt->removeType == OBJECT_VIEW && !superuser())
check_restricted_object(address.objectId, OBJECT_VIEW);

/* Get major_name */
major_name = pstrdup(RelationGetRelationName(relation));
Expand Down Expand Up @@ -7040,7 +7044,7 @@ bbf_ExecDropStmt(DropStmt *stmt)

/* Restrict dropping of extended stored procedures for non-superuser roles */
if (stmt->removeType == OBJECT_PROCEDURE && !superuser())
check_restricted_stored_procedure(address.objectId);
check_restricted_object(address.objectId, OBJECT_PROCEDURE);

/* Get major_name */
relation = table_open(address.classId, AccessShareLock);
Expand Down
27 changes: 26 additions & 1 deletion contrib/babelfishpg_tsql/src/pltsql.h
Original file line number Diff line number Diff line change
Expand Up @@ -2128,7 +2128,7 @@ extern void pltsql_free_function_memory(PLtsql_function *func);
extern void pltsql_dumptree(PLtsql_function *func);
extern void pre_function_call_hook_impl(const char *funcName);
extern int32 coalesce_typmod_hook_impl(const CoalesceExpr *cexpr);
extern void check_restricted_stored_procedure(Oid proc_id);
extern void check_restricted_object(Oid object_id, ObjectType object_type);
extern bool is_tsql_atatglobalvar(const char *varname);
extern bool is_tsql_atatuservar(const char *varname);

Expand Down Expand Up @@ -2348,4 +2348,29 @@ extern bool validate_special_function(char *proc_nsname, char *proc_name, int na
*/
extern char *tsql_format_type_extended(Oid type_oid, int32 typemod, bits16 flags);

/* Restricted objects definition */
typedef struct
{
const char *name;
ObjectType type;
} RestrictedObject;

/* Separate arrays for each schema */
static const RestrictedObject master_dbo_objects[] = {
{"xp_qv", OBJECT_PROCEDURE},
{"xp_instance_regread", OBJECT_PROCEDURE},
{"sp_addlinkedserver", OBJECT_PROCEDURE},
{"sp_addlinkedsrvlogin", OBJECT_PROCEDURE},
{"sp_droplinkedsrvlogin", OBJECT_PROCEDURE},
{"sp_dropserver", OBJECT_PROCEDURE},
{"sp_enum_oledb_providers", OBJECT_PROCEDURE},
{"sp_testlinkedserver", OBJECT_PROCEDURE}
};

static const RestrictedObject msdb_dbo_objects[] = {
{"syspolicy_configuration", OBJECT_VIEW},
{"syspolicy_system_health_state", OBJECT_VIEW},
{"fn_syspolicy_is_automation_enabled", OBJECT_FUNCTION}
};

#endif /* PLTSQL_H */
151 changes: 151 additions & 0 deletions test/JDBC/expected/restricted_objects-vu-cleanup.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
REVOKE EXECUTE ON babel_5146_prepare_p1 TO guest;
GO

DROP PROCEDURE IF EXISTS babel_5146_prepare_p1;
GO

USE msdb;
GO

REVOKE EXECUTE ON babel_5146_prepare_p2 TO guest;
GO

REVOKE EXECUTE ON babel_5146_prepare_p3 TO guest;
GO

REVOKE EXECUTE ON babel_5146_prepare_p4 TO guest;
GO

DROP PROCEDURE IF EXISTS babel_5146_prepare_p2;
GO

DROP PROCEDURE IF EXISTS babel_5146_prepare_p3;
GO

DROP PROCEDURE IF EXISTS babel_5146_prepare_p4;
GO

-- tsql user=babel_5146_user_l1 password=abc
DROP PROCEDURE IF EXISTS babel_5146_test_schema_s1.sp_addlinkedserver;
GO

-- tsql
DROP SCHEMA IF EXISTS babel_5146_test_schema_s1;
GO

DROP USER IF EXISTS babel_5146_user_u1;
GO

-- psql
-- Need to terminate active session before cleaning up the login
SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL)
WHERE sys.suser_name(usesysid) = 'babel_5146_user_l1' AND backend_type = 'client backend' AND usesysid IS NOT NULL;
GO
~~START~~
bool
t
~~END~~


-- Wait to sync with another session
SELECT pg_sleep(1);
GO
~~START~~
void

~~END~~


-- Need to terminate active session before cleaning up the login
SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL)
WHERE sys.suser_name(usesysid) = 'babel_5146_user_l2' AND backend_type = 'client backend' AND usesysid IS NOT NULL;
GO
~~START~~
bool
t
~~END~~


-- Wait to sync with another session
SELECT pg_sleep(1);
GO
~~START~~
void

~~END~~


-- tsql
DROP LOGIN babel_5146_user_l1;
GO

DROP LOGIN babel_5146_user_l2;
GO

-- tsql user=babel_5146_user_l3 WITH PASSWORD = '12345678'
USE msdb;
GO

DROP FUNCTION IF EXISTS babel_5146_test_schema_s2.fn_syspolicy_is_automation_enabled();
GO

DROP VIEW IF EXISTS babel_5146_test_schema_s2.syspolicy_configuration;
GO

DROP VIEW IF EXISTS babel_5146_test_schema_s2.syspolicy_system_health_state;
GO

-- tsql
USE msdb;
GO

DROP SCHEMA IF EXISTS babel_5146_test_schema_s2;
GO

DROP USER IF EXISTS babel_5146_user_u3;
GO

-- psql
-- Need to terminate active session before cleaning up the login
SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL)
WHERE sys.suser_name(usesysid) = 'babel_5146_user_l3' AND backend_type = 'client backend' AND usesysid IS NOT NULL;
GO
~~START~~
bool
t
~~END~~


-- Wait to sync with another session
SELECT pg_sleep(1);
GO
~~START~~
void

~~END~~


-- Need to terminate active session before cleaning up the login
SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL)
WHERE sys.suser_name(usesysid) = 'babel_5146_user_l4' AND backend_type = 'client backend' AND usesysid IS NOT NULL;
GO
~~START~~
bool
~~END~~


-- Wait to sync with another session
SELECT pg_sleep(1);
GO
~~START~~
void

~~END~~


-- tsql
DROP LOGIN babel_5146_user_l3;
GO

DROP LOGIN babel_5146_user_l4;
GO
Loading
Loading