openGauss-server/contrib/sepgsql/selinux.cpp

562 lines
20 KiB
C++

/* -------------------------------------------------------------------------
*
* contrib/sepgsql/selinux.c
*
* Interactions between userspace and selinux in kernelspace,
* using libselinux api.
*
* Copyright (c) 2010-2012, PostgreSQL Global Development Group
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "lib/stringinfo.h"
#include "sepgsql.h"
/*
* selinux_catalog
*
* This mapping table enables to translate the name of object classes and
* access vectors to/from their own codes.
* When we ask SELinux whether the required privileges are allowed or not,
* we use security_compute_av(3). It needs us to represent object classes
* and access vectors using 'external' codes defined in the security policy.
* It is determinded in the runtime, not build time. So, it needs an internal
* service to translate object class/access vectors which we want to check
* into the code which kernel want to be given.
*/
static struct {
const char* class_name;
uint16 class_code;
struct {
const char* av_name;
uint32 av_code;
} av[32];
} selinux_catalog[] =
{
{"process",
SEPG_CLASS_PROCESS,
{{"transition", SEPG_PROCESS__TRANSITION},
{"dyntransition", SEPG_PROCESS__DYNTRANSITION},
{"setcurrent", SEPG_PROCESS__SETCURRENT},
{NULL, 0UL}}},
{"file",
SEPG_CLASS_FILE,
{{"read", SEPG_FILE__READ},
{"write", SEPG_FILE__WRITE},
{"create", SEPG_FILE__CREATE},
{"getattr", SEPG_FILE__GETATTR},
{"unlink", SEPG_FILE__UNLINK},
{"rename", SEPG_FILE__RENAME},
{"append", SEPG_FILE__APPEND},
{NULL, 0UL}}},
{"dir",
SEPG_CLASS_DIR,
{{"read", SEPG_DIR__READ},
{"write", SEPG_DIR__WRITE},
{"create", SEPG_DIR__CREATE},
{"getattr", SEPG_DIR__GETATTR},
{"unlink", SEPG_DIR__UNLINK},
{"rename", SEPG_DIR__RENAME},
{"search", SEPG_DIR__SEARCH},
{"add_name", SEPG_DIR__ADD_NAME},
{"remove_name", SEPG_DIR__REMOVE_NAME},
{"rmdir", SEPG_DIR__RMDIR},
{"reparent", SEPG_DIR__REPARENT},
{NULL, 0UL}}},
{"lnk_file",
SEPG_CLASS_LNK_FILE,
{{"read", SEPG_LNK_FILE__READ},
{"write", SEPG_LNK_FILE__WRITE},
{"create", SEPG_LNK_FILE__CREATE},
{"getattr", SEPG_LNK_FILE__GETATTR},
{"unlink", SEPG_LNK_FILE__UNLINK},
{"rename", SEPG_LNK_FILE__RENAME},
{NULL, 0UL}}},
{"chr_file",
SEPG_CLASS_CHR_FILE,
{{"read", SEPG_CHR_FILE__READ},
{"write", SEPG_CHR_FILE__WRITE},
{"create", SEPG_CHR_FILE__CREATE},
{"getattr", SEPG_CHR_FILE__GETATTR},
{"unlink", SEPG_CHR_FILE__UNLINK},
{"rename", SEPG_CHR_FILE__RENAME},
{NULL, 0UL}}},
{"blk_file",
SEPG_CLASS_BLK_FILE,
{{"read", SEPG_BLK_FILE__READ},
{"write", SEPG_BLK_FILE__WRITE},
{"create", SEPG_BLK_FILE__CREATE},
{"getattr", SEPG_BLK_FILE__GETATTR},
{"unlink", SEPG_BLK_FILE__UNLINK},
{"rename", SEPG_BLK_FILE__RENAME},
{NULL, 0UL}}},
{"sock_file",
SEPG_CLASS_SOCK_FILE,
{{"read", SEPG_SOCK_FILE__READ},
{"write", SEPG_SOCK_FILE__WRITE},
{"create", SEPG_SOCK_FILE__CREATE},
{"getattr", SEPG_SOCK_FILE__GETATTR},
{"unlink", SEPG_SOCK_FILE__UNLINK},
{"rename", SEPG_SOCK_FILE__RENAME},
{NULL, 0UL}}},
{"fifo_file",
SEPG_CLASS_FIFO_FILE,
{{"read", SEPG_FIFO_FILE__READ},
{"write", SEPG_FIFO_FILE__WRITE},
{"create", SEPG_FIFO_FILE__CREATE},
{"getattr", SEPG_FIFO_FILE__GETATTR},
{"unlink", SEPG_FIFO_FILE__UNLINK},
{"rename", SEPG_FIFO_FILE__RENAME},
{NULL, 0UL}}},
{"db_database",
SEPG_CLASS_DB_DATABASE,
{
{"create", SEPG_DB_DATABASE__CREATE},
{"drop", SEPG_DB_DATABASE__DROP},
{"getattr", SEPG_DB_DATABASE__GETATTR},
{"setattr", SEPG_DB_DATABASE__SETATTR},
{"relabelfrom", SEPG_DB_DATABASE__RELABELFROM},
{"relabelto", SEPG_DB_DATABASE__RELABELTO},
{"access", SEPG_DB_DATABASE__ACCESS},
{"load_module", SEPG_DB_DATABASE__LOAD_MODULE},
{NULL, 0UL},
}},
{"db_schema",
SEPG_CLASS_DB_SCHEMA,
{
{"create", SEPG_DB_SCHEMA__CREATE},
{"drop", SEPG_DB_SCHEMA__DROP},
{"getattr", SEPG_DB_SCHEMA__GETATTR},
{"setattr", SEPG_DB_SCHEMA__SETATTR},
{"relabelfrom", SEPG_DB_SCHEMA__RELABELFROM},
{"relabelto", SEPG_DB_SCHEMA__RELABELTO},
{"search", SEPG_DB_SCHEMA__SEARCH},
{"add_name", SEPG_DB_SCHEMA__ADD_NAME},
{"remove_name", SEPG_DB_SCHEMA__REMOVE_NAME},
{NULL, 0UL},
}},
{"db_table",
SEPG_CLASS_DB_TABLE,
{
{"create", SEPG_DB_TABLE__CREATE},
{"drop", SEPG_DB_TABLE__DROP},
{"getattr", SEPG_DB_TABLE__GETATTR},
{"setattr", SEPG_DB_TABLE__SETATTR},
{"relabelfrom", SEPG_DB_TABLE__RELABELFROM},
{"relabelto", SEPG_DB_TABLE__RELABELTO},
{"select", SEPG_DB_TABLE__SELECT},
{"update", SEPG_DB_TABLE__UPDATE},
{"insert", SEPG_DB_TABLE__INSERT},
{"delete", SEPG_DB_TABLE__DELETE},
{"lock", SEPG_DB_TABLE__LOCK},
{NULL, 0UL},
}},
{"db_sequence",
SEPG_CLASS_DB_SEQUENCE,
{
{"create", SEPG_DB_SEQUENCE__CREATE},
{"drop", SEPG_DB_SEQUENCE__DROP},
{"getattr", SEPG_DB_SEQUENCE__GETATTR},
{"setattr", SEPG_DB_SEQUENCE__SETATTR},
{"relabelfrom", SEPG_DB_SEQUENCE__RELABELFROM},
{"relabelto", SEPG_DB_SEQUENCE__RELABELTO},
{"get_value", SEPG_DB_SEQUENCE__GET_VALUE},
{"next_value", SEPG_DB_SEQUENCE__NEXT_VALUE},
{"set_value", SEPG_DB_SEQUENCE__SET_VALUE},
{NULL, 0UL},
}},
{"db_procedure",
SEPG_CLASS_DB_PROCEDURE,
{
{"create", SEPG_DB_PROCEDURE__CREATE},
{"drop", SEPG_DB_PROCEDURE__DROP},
{"getattr", SEPG_DB_PROCEDURE__GETATTR},
{"setattr", SEPG_DB_PROCEDURE__SETATTR},
{"relabelfrom", SEPG_DB_PROCEDURE__RELABELFROM},
{"relabelto", SEPG_DB_PROCEDURE__RELABELTO},
{"execute", SEPG_DB_PROCEDURE__EXECUTE},
{"entrypoint", SEPG_DB_PROCEDURE__ENTRYPOINT},
{"install", SEPG_DB_PROCEDURE__INSTALL},
{NULL, 0UL},
}},
{"db_column",
SEPG_CLASS_DB_COLUMN,
{
{"create", SEPG_DB_COLUMN__CREATE},
{"drop", SEPG_DB_COLUMN__DROP},
{"getattr", SEPG_DB_COLUMN__GETATTR},
{"setattr", SEPG_DB_COLUMN__SETATTR},
{"relabelfrom", SEPG_DB_COLUMN__RELABELFROM},
{"relabelto", SEPG_DB_COLUMN__RELABELTO},
{"select", SEPG_DB_COLUMN__SELECT},
{"update", SEPG_DB_COLUMN__UPDATE},
{"insert", SEPG_DB_COLUMN__INSERT},
{NULL, 0UL},
}},
{"db_tuple",
SEPG_CLASS_DB_TUPLE,
{
{"relabelfrom", SEPG_DB_TUPLE__RELABELFROM},
{"relabelto", SEPG_DB_TUPLE__RELABELTO},
{"select", SEPG_DB_TUPLE__SELECT},
{"update", SEPG_DB_TUPLE__UPDATE},
{"insert", SEPG_DB_TUPLE__INSERT},
{"delete", SEPG_DB_TUPLE__DELETE},
{NULL, 0UL},
}},
{"db_blob",
SEPG_CLASS_DB_BLOB,
{
{"create", SEPG_DB_BLOB__CREATE},
{"drop", SEPG_DB_BLOB__DROP},
{"getattr", SEPG_DB_BLOB__GETATTR},
{"setattr", SEPG_DB_BLOB__SETATTR},
{"relabelfrom", SEPG_DB_BLOB__RELABELFROM},
{"relabelto", SEPG_DB_BLOB__RELABELTO},
{"read", SEPG_DB_BLOB__READ},
{"write", SEPG_DB_BLOB__WRITE},
{"import", SEPG_DB_BLOB__IMPORT},
{"export", SEPG_DB_BLOB__EXPORT},
{NULL, 0UL},
}},
{"db_language",
SEPG_CLASS_DB_LANGUAGE,
{
{"create", SEPG_DB_LANGUAGE__CREATE},
{"drop", SEPG_DB_LANGUAGE__DROP},
{"getattr", SEPG_DB_LANGUAGE__GETATTR},
{"setattr", SEPG_DB_LANGUAGE__SETATTR},
{"relabelfrom", SEPG_DB_LANGUAGE__RELABELFROM},
{"relabelto", SEPG_DB_LANGUAGE__RELABELTO},
{"implement", SEPG_DB_LANGUAGE__IMPLEMENT},
{"execute", SEPG_DB_LANGUAGE__EXECUTE},
{NULL, 0UL},
}},
{"db_view",
SEPG_CLASS_DB_VIEW,
{
{"create", SEPG_DB_VIEW__CREATE},
{"drop", SEPG_DB_VIEW__DROP},
{"getattr", SEPG_DB_VIEW__GETATTR},
{"setattr", SEPG_DB_VIEW__SETATTR},
{"relabelfrom", SEPG_DB_VIEW__RELABELFROM},
{"relabelto", SEPG_DB_VIEW__RELABELTO},
{"expand", SEPG_DB_VIEW__EXPAND},
{NULL, 0UL},
}},
};
/*
* sepgsql_mode
*
* SEPGSQL_MODE_DISABLED: Disabled on runtime
* SEPGSQL_MODE_DEFAULT: Same as system settings
* SEPGSQL_MODE_PERMISSIVE: Always permissive mode
* SEPGSQL_MODE_INTERNAL: Same as permissive, except for no audit logs
*/
static int sepgsql_mode = SEPGSQL_MODE_INTERNAL;
/*
* sepgsql_is_enabled
*/
bool sepgsql_is_enabled(void)
{
return (sepgsql_mode != SEPGSQL_MODE_DISABLED ? true : false);
}
/*
* sepgsql_get_mode
*/
int sepgsql_get_mode(void)
{
return sepgsql_mode;
}
/*
* sepgsql_set_mode
*/
int sepgsql_set_mode(int new_mode)
{
int old_mode = sepgsql_mode;
sepgsql_mode = new_mode;
return old_mode;
}
/*
* sepgsql_getenforce
*
* It returns whether the current working mode tries to enforce access
* control decision, or not. It shall be enforced when sepgsql_mode is
* SEPGSQL_MODE_DEFAULT and system is running in enforcing mode.
*/
bool sepgsql_getenforce(void)
{
if (sepgsql_mode == SEPGSQL_MODE_DEFAULT && selinux_status_getenforce() > 0)
return true;
return false;
}
/*
* sepgsql_audit_log
*
* It generates a security audit record. In the default, it writes out
* audit records into standard PG's logfile. It also allows to set up
* external audit log receiver, such as auditd in Linux, using the
* sepgsql_audit_hook.
*
* SELinux can control what should be audited and should not using
* "auditdeny" and "auditallow" rules in the security policy. In the
* default, all the access violations are audited, and all the access
* allowed are not audited. But we can set up the security policy, so
* we can have exceptions. So, it is necessary to follow the suggestion
* come from the security policy. (av_decision.auditallow and auditdeny)
*
* Security audit is an important feature, because it enables us to check
* what was happen if we have a security incident. In fact, ISO/IEC15408
* defines several security functionalities for audit features.
*/
void sepgsql_audit_log(
bool denied, const char* scontext, const char* tcontext, uint16 tclass, uint32 audited, const char* audit_name)
{
StringInfoData buf;
const char* class_name = NULL;
const char* av_name = NULL;
int i;
/* lookup name of the object class */
Assert(tclass < SEPG_CLASS_MAX);
class_name = selinux_catalog[tclass].class_name;
/* lookup name of the permissions */
initStringInfo(&buf);
appendStringInfo(&buf, "%s {", (denied ? "denied" : "allowed"));
for (i = 0; selinux_catalog[tclass].av[i].av_name; i++) {
if (audited & (1UL << i)) {
av_name = selinux_catalog[tclass].av[i].av_name;
appendStringInfo(&buf, " %s", av_name);
}
}
appendStringInfo(&buf, " }");
/*
* Call external audit module, if loaded
*/
appendStringInfo(&buf, " scontext=%s tcontext=%s tclass=%s", scontext, tcontext, class_name);
if (audit_name)
appendStringInfo(&buf, " name=\"%s\"", audit_name);
ereport(LOG, (errmsg("SELinux: %s", buf.data)));
}
/*
* sepgsql_compute_avd
*
* It actually asks SELinux what permissions are allowed on a pair of
* the security contexts and object class. It also returns what permissions
* should be audited on access violation or allowed.
* In most cases, subject's security context (scontext) is a client, and
* target security context (tcontext) is a database object.
*
* The access control decision shall be set on the given av_decision.
* The av_decision.allowed has a bitmask of SEPG_<class>__<perms>
* to suggest a set of allowed actions in this object class.
*/
void sepgsql_compute_avd(const char* scontext, const char* tcontext, uint16 tclass, struct av_decision* avd)
{
const char* tclass_name = NULL;
security_class_t tclass_ex;
struct av_decision avd_ex;
int i, deny_unknown = security_deny_unknown();
/* Get external code of the object class */
Assert(tclass < SEPG_CLASS_MAX);
Assert(tclass == selinux_catalog[tclass].class_code);
tclass_name = selinux_catalog[tclass].class_name;
tclass_ex = string_to_security_class(tclass_name);
if (tclass_ex == 0) {
/*
* If the current security policy does not support permissions
* corresponding to database objects, we fill up them with dummy data.
* If security_deny_unknown() returns positive value, undefined
* permissions should be denied. Otherwise, allowed
*/
avd->allowed = (security_deny_unknown() > 0 ? 0 : ~0);
avd->auditallow = 0U;
avd->auditdeny = ~0U;
avd->flags = 0;
return;
}
/*
* Ask SELinux what is allowed set of permissions on a pair of the
* security contexts and the given object class.
*/
if (security_compute_av_flags_raw(
(security_context_t)scontext, (security_context_t)tcontext, tclass_ex, 0, &avd_ex) < 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("SELinux could not compute av_decision: "
"scontext=%s tcontext=%s tclass=%s: %m",
scontext,
tcontext,
tclass_name)));
/*
* SELinux returns its access control decision as a set of permissions
* represented in external code which depends on run-time environment. So,
* we need to translate it to the internal representation before returning
* results for the caller.
*/
memset(avd, 0, sizeof(struct av_decision));
for (i = 0; selinux_catalog[tclass].av[i].av_name; i++) {
access_vector_t av_code_ex;
const char* av_name = selinux_catalog[tclass].av[i].av_name;
uint32 av_code = selinux_catalog[tclass].av[i].av_code;
av_code_ex = string_to_av_perm(tclass_ex, av_name);
if (av_code_ex == 0) {
/* fill up undefined permissions */
if (!deny_unknown)
avd->allowed |= av_code;
avd->auditdeny |= av_code;
continue;
}
if (avd_ex.allowed & av_code_ex)
avd->allowed |= av_code;
if (avd_ex.auditallow & av_code_ex)
avd->auditallow |= av_code;
if (avd_ex.auditdeny & av_code_ex)
avd->auditdeny |= av_code;
}
return;
}
/*
* sepgsql_compute_create
*
* It returns a default security context to be assigned on a new database
* object. SELinux compute it based on a combination of client, upper object
* which owns the new object and object class.
*
* For example, when a client (staff_u:staff_r:staff_t:s0) tries to create
* a new table within a schema (system_u:object_r:sepgsql_schema_t:s0),
* SELinux looks-up its security policy. If it has a special rule on the
* combination of these security contexts and object class (db_table),
* it returns the security context suggested by the special rule.
* Otherwise, it returns the security context of schema, as is.
*
* We expect the caller already applies sanity/validation checks on the
* given security context.
*
* scontext: security context of the subject (mostly, peer process).
* tcontext: security context of the upper database object.
* tclass: class code (SEPG_CLASS_*) of the new object in creation
*/
char* sepgsql_compute_create(const char* scontext, const char* tcontext, uint16 tclass)
{
security_context_t ncontext;
security_class_t tclass_ex;
const char* tclass_name = NULL;
char* result = NULL;
/* Get external code of the object class */
Assert(tclass < SEPG_CLASS_MAX);
tclass_name = selinux_catalog[tclass].class_name;
tclass_ex = string_to_security_class(tclass_name);
/*
* Ask SELinux what is the default context for the given object class on a
* pair of security contexts
*/
if (security_compute_create_raw((security_context_t)scontext, (security_context_t)tcontext, tclass_ex, &ncontext) <
0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("SELinux could not compute a new context: "
"scontext=%s tcontext=%s tclass=%s: %m",
scontext,
tcontext,
tclass_name)));
/*
* libselinux returns malloc()'ed string, so we need to copy it on the
* palloc()'ed region.
*/
PG_TRY();
{
result = pstrdup(ncontext);
}
PG_CATCH();
{
freecon(ncontext);
PG_RE_THROW();
}
PG_END_TRY();
freecon(ncontext);
return result;
}
/*
* sepgsql_check_perms
*
* It makes access control decision without userspace caching mechanism.
* If SELinux denied the required accesses on the pair of security labels,
* it raises an error or returns false.
*
* scontext: security label of the subject (mostly, peer process)
* tcontext: security label of the object being referenced
* tclass: class code (SEPG_CLASS_*) of the object being referenced
* required: a mask of required permissions (SEPG_<class>__<perm>)
* audit_name: a human readable object name for audit logs, or NULL.
* abort: true, if caller wants to raise an error on access violation
*/
bool sepgsql_check_perms(
const char* scontext, const char* tcontext, uint16 tclass, uint32 required, const char* audit_name, bool abort)
{
struct av_decision avd;
uint32 denied;
uint32 audited;
bool result = true;
sepgsql_compute_avd(scontext, tcontext, tclass, &avd);
denied = required & ~avd.allowed;
if (sepgsql_get_debug_audit())
audited = (denied ? denied : required);
else
audited = (denied ? (denied & avd.auditdeny) : (required & avd.auditallow));
if (denied && sepgsql_getenforce() > 0 && (avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE) == 0)
result = false;
/*
* It records a security audit for the request, if needed. But, when
* SE-PgSQL performs 'internal' mode, it needs to keep silent.
*/
if (audited && sepgsql_mode != SEPGSQL_MODE_INTERNAL) {
sepgsql_audit_log(denied, scontext, tcontext, tclass, audited, audit_name);
}
if (!result && abort)
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("SELinux: security policy violation")));
return result;
}