diff options
author | Robert Haas | 2011-01-24 01:44:48 +0000 |
---|---|---|
committer | Robert Haas | 2011-01-24 01:48:27 +0000 |
commit | 968bc6fac91d6aaca594488ab85c179b686cbbdd (patch) | |
tree | 3cb8fa7ee4101723733e5ed5a06803f9c299c2d7 /contrib/sepgsql/selinux.c | |
parent | e5487f65fdbd05716ade642a3ae1c5c6e85b6f22 (diff) |
sepgsql, an SE-Linux integration for PostgreSQL
This is still pretty rough - among other things, the documentation
needs work, and the messages need a visit from the style police -
but this gets the basic framework in place.
KaiGai Kohei
Diffstat (limited to 'contrib/sepgsql/selinux.c')
-rw-r--r-- | contrib/sepgsql/selinux.c | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c new file mode 100644 index 00000000000..a67bd567112 --- /dev/null +++ b/contrib/sepgsql/selinux.c @@ -0,0 +1,631 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/selinux.c + * + * Interactions between userspace and selinux in kernelspace, + * using libselinux api. + * + * Copyright (c) 2010-2011, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.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 }, + { 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 && + security_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; + const char *av_name; + 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; + 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", + 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 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; + char *result; + + /* 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", + 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; +} |