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
30 changes: 15 additions & 15 deletions contrib/babelfishpg_tsql/runtime/functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,19 @@ extern char *replace_special_chars_fts_impl(char *input_str);
char *bbf_servername = "BABELFISH";
const char *bbf_servicename = "MSSQLSERVER";
char *bbf_language = "us_english";
const char *shipped_objects_not_in_sys_db[NUM_DB_OBJECTS][2] = {
{"xp_qv","master_dbo"},
{"xp_instance_regread","master_dbo"},
{"sp_addlinkedserver", "master_dbo"},
{"sp_addlinkedsrvlogin", "master_dbo"},
{"sp_dropserver", "master_dbo"},
{"sp_droplinkedsrvlogin", "master_dbo"},
{"sp_testlinkedserver", "master_dbo"},
{"fn_syspolicy_is_automation_enabled", "msdb_dbo"},
{"syspolicy_configuration", "msdb_dbo"},
{"syspolicy_system_health_state", "msdb_dbo"},
{"sp_enum_oledb_providers", "master_dbo"}
};
#define MD5_HASH_LEN 32

#define MAX_CATNAME_LEN NAMEDATALEN
Expand Down Expand Up @@ -3830,31 +3843,18 @@ bool is_ms_shipped(char *object_name, int type, Oid schema_id)
int i = 0;
bool is_ms_shipped = false;
char *namespace_name = NULL;

/*
* This array contains information of objects that reside in a schema in one specfic database.
* For example, 'master_dbo' schema can only exist in the 'master' database.
*/
#define NUM_DB_OBJECTS 11
int shipped_objects_not_in_sys_db_type[NUM_DB_OBJECTS] = {
static int shipped_objects_not_in_sys_db_type[NUM_DB_OBJECTS] = {
OBJECT_TYPE_TSQL_STORED_PROCEDURE, OBJECT_TYPE_TSQL_STORED_PROCEDURE,
OBJECT_TYPE_TSQL_STORED_PROCEDURE, OBJECT_TYPE_TSQL_STORED_PROCEDURE,
OBJECT_TYPE_TSQL_STORED_PROCEDURE, OBJECT_TYPE_TSQL_STORED_PROCEDURE,
OBJECT_TYPE_TSQL_STORED_PROCEDURE, OBJECT_TYPE_TSQL_SCALAR_FUNCTION,
OBJECT_TYPE_VIEW, OBJECT_TYPE_VIEW, OBJECT_TYPE_TSQL_STORED_PROCEDURE
};
char *shipped_objects_not_in_sys_db[NUM_DB_OBJECTS][2] = {
{"xp_qv","master_dbo"},
{"xp_instance_regread","master_dbo"},
{"sp_addlinkedserver", "master_dbo"},
{"sp_addlinkedsrvlogin", "master_dbo"},
{"sp_dropserver", "master_dbo"},
{"sp_droplinkedsrvlogin", "master_dbo"},
{"sp_testlinkedserver", "master_dbo"},
{"fn_syspolicy_is_automation_enabled", "msdb_dbo"},
{"syspolicy_configuration", "msdb_dbo"},
{"syspolicy_system_health_state", "msdb_dbo"},
{"sp_enum_oledb_providers", "master_dbo"}
};

/*
* This array contains information of objects that reside in a schema in any number of databases.
Expand Down
105 changes: 63 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,78 @@ 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, const char *schema_name)
{
for (int idx = 0; idx < NUM_DB_OBJECTS; idx++)
{
if (pg_strcasecmp(shipped_objects_not_in_sys_db[idx][0], objname) == 0 &&
pg_strcasecmp(shipped_objects_not_in_sys_db[idx][1], schema_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 */
if (object_type == OBJECT_PROCEDURE || object_type == OBJECT_FUNCTION)
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(object_id));
else
tuple = SearchSysCache1(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 || object_type == OBJECT_FUNCTION)
{
/* 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;
}

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

ReleaseSysCache(proctup);
if (is_restricted)
/* Check if object is restricted and error out if it is */
if (is_object_restricted(objname, schema_name))
{
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" :
object_type == OBJECT_VIEW ? "view" :
"function",
objname)));
}
ReleaseSysCache(tuple);
}

/* Determine whether a variable name is a predefined T-SQL global variable */
Expand Down
13 changes: 10 additions & 3 deletions contrib/babelfishpg_tsql/src/pl_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -6979,6 +6979,13 @@ bbf_ExecDropStmt(DropStmt *stmt)
if (!relation)
continue;

if (!OidIsValid(address.objectId))
continue;

/* Restrict dropping of system views 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));
relation_close(relation, AccessShareLock);
Expand Down Expand Up @@ -7038,9 +7045,9 @@ bbf_ExecDropStmt(DropStmt *stmt)
if (!OidIsValid(address.objectId))
continue;

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

/* Get major_name */
relation = table_open(address.classId, AccessShareLock);
Expand Down
6 changes: 5 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,8 @@ 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);

#define NUM_DB_OBJECTS 11

extern const char *shipped_objects_not_in_sys_db[NUM_DB_OBJECTS][2];

#endif /* PLTSQL_H */
154 changes: 154 additions & 0 deletions test/JDBC/expected/restricted_objects-vu-cleanup.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
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 password=abc
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

REVOKE CONNECT FROM guest;
GO
Loading
Loading