diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:17:33 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:17:33 +0000 |
commit | 5e45211a64149b3c659b90ff2de6fa982a5a93ed (patch) | |
tree | 739caf8c461053357daa9f162bef34516c7bf452 /contrib/sepgsql/uavc.c | |
parent | Initial commit. (diff) | |
download | postgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.tar.xz postgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.zip |
Adding upstream version 15.5.upstream/15.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'contrib/sepgsql/uavc.c')
-rw-r--r-- | contrib/sepgsql/uavc.c | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/contrib/sepgsql/uavc.c b/contrib/sepgsql/uavc.c new file mode 100644 index 0000000..7659798 --- /dev/null +++ b/contrib/sepgsql/uavc.c @@ -0,0 +1,521 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/uavc.c + * + * Implementation of userspace access vector cache; that enables to cache + * access control decisions recently used, and reduce number of kernel + * invocations to avoid unnecessary performance hit. + * + * Copyright (c) 2011-2022, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_proc.h" +#include "commands/seclabel.h" +#include "common/hashfn.h" +#include "sepgsql.h" +#include "storage/ipc.h" +#include "utils/guc.h" +#include "utils/memutils.h" + +/* + * avc_cache + * + * It enables to cache access control decision (and behavior on execution of + * trusted procedure, db_procedure class only) for a particular pair of + * security labels and object class in userspace. + */ +typedef struct +{ + uint32 hash; /* hash value of this cache entry */ + char *scontext; /* security context of the subject */ + char *tcontext; /* security context of the target */ + uint16 tclass; /* object class of the target */ + + uint32 allowed; /* permissions to be allowed */ + uint32 auditallow; /* permissions to be audited on allowed */ + uint32 auditdeny; /* permissions to be audited on denied */ + + bool permissive; /* true, if permissive rule */ + bool hot_cache; /* true, if recently referenced */ + bool tcontext_is_valid; + /* true, if tcontext is valid */ + char *ncontext; /* temporary scontext on execution of trusted + * procedure, or NULL elsewhere */ +} avc_cache; + +/* + * Declaration of static variables + */ +#define AVC_NUM_SLOTS 512 +#define AVC_NUM_RECLAIM 16 +#define AVC_DEF_THRESHOLD 384 + +static MemoryContext avc_mem_cxt; +static List *avc_slots[AVC_NUM_SLOTS]; /* avc's hash buckets */ +static int avc_num_caches; /* number of caches currently used */ +static int avc_lru_hint; /* index of the buckets to be reclaimed next */ +static int avc_threshold; /* threshold to launch cache-reclaiming */ +static char *avc_unlabeled; /* system 'unlabeled' label */ + +/* + * Hash function + */ +static uint32 +sepgsql_avc_hash(const char *scontext, const char *tcontext, uint16 tclass) +{ + return hash_any((const unsigned char *) scontext, strlen(scontext)) + ^ hash_any((const unsigned char *) tcontext, strlen(tcontext)) + ^ tclass; +} + +/* + * Reset all the avc caches + */ +static void +sepgsql_avc_reset(void) +{ + MemoryContextReset(avc_mem_cxt); + + memset(avc_slots, 0, sizeof(List *) * AVC_NUM_SLOTS); + avc_num_caches = 0; + avc_lru_hint = 0; + avc_unlabeled = NULL; +} + +/* + * Reclaim caches recently unreferenced + */ +static void +sepgsql_avc_reclaim(void) +{ + ListCell *cell; + int index; + + while (avc_num_caches >= avc_threshold - AVC_NUM_RECLAIM) + { + index = avc_lru_hint; + + foreach(cell, avc_slots[index]) + { + avc_cache *cache = lfirst(cell); + + if (!cache->hot_cache) + { + avc_slots[index] + = foreach_delete_current(avc_slots[index], cell); + + pfree(cache->scontext); + pfree(cache->tcontext); + if (cache->ncontext) + pfree(cache->ncontext); + pfree(cache); + + avc_num_caches--; + } + else + { + cache->hot_cache = false; + } + } + avc_lru_hint = (avc_lru_hint + 1) % AVC_NUM_SLOTS; + } +} + +/* ------------------------------------------------------------------------- + * + * sepgsql_avc_check_valid + * + * This function checks whether the cached entries are still valid. If + * the security policy has been reloaded (or any other events that requires + * resetting userspace caches has occurred) since the last reference to + * the access vector cache, we must flush the cache. + * + * Access control decisions must be atomic, but multiple system calls may + * be required to make a decision; thus, when referencing the access vector + * cache, we must loop until we complete without an intervening cache flush + * event. In practice, looping even once should be very rare. Callers should + * do something like this: + * + * sepgsql_avc_check_valid(); + * do { + * : + * <reference to uavc> + * : + * } while (!sepgsql_avc_check_valid()) + * + * ------------------------------------------------------------------------- + */ +static bool +sepgsql_avc_check_valid(void) +{ + if (selinux_status_updated() > 0) + { + sepgsql_avc_reset(); + + return false; + } + return true; +} + +/* + * sepgsql_avc_unlabeled + * + * Returns an alternative label to be applied when no label or an invalid + * label would otherwise be assigned. + */ +static char * +sepgsql_avc_unlabeled(void) +{ + if (!avc_unlabeled) + { + char *unlabeled; + + if (security_get_initial_context_raw("unlabeled", &unlabeled) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: failed to get initial security label: %m"))); + PG_TRY(); + { + avc_unlabeled = MemoryContextStrdup(avc_mem_cxt, unlabeled); + } + PG_FINALLY(); + { + freecon(unlabeled); + } + PG_END_TRY(); + } + return avc_unlabeled; +} + +/* + * sepgsql_avc_compute + * + * A fallback path, when cache mishit. It asks SELinux its access control + * decision for the supplied pair of security context and object class. + */ +static avc_cache * +sepgsql_avc_compute(const char *scontext, const char *tcontext, uint16 tclass) +{ + char *ucontext = NULL; + char *ncontext = NULL; + MemoryContext oldctx; + avc_cache *cache; + uint32 hash; + int index; + struct av_decision avd; + + hash = sepgsql_avc_hash(scontext, tcontext, tclass); + index = hash % AVC_NUM_SLOTS; + + /* + * Validation check of the supplied security context. Because it always + * invoke system-call, frequent check should be avoided. Unless security + * policy is reloaded, validation status shall be kept, so we also cache + * whether the supplied security context was valid, or not. + */ + if (security_check_context_raw(tcontext) != 0) + ucontext = sepgsql_avc_unlabeled(); + + /* + * Ask SELinux its access control decision + */ + if (!ucontext) + sepgsql_compute_avd(scontext, tcontext, tclass, &avd); + else + sepgsql_compute_avd(scontext, ucontext, tclass, &avd); + + /* + * It also caches a security label to be switched when a client labeled as + * 'scontext' executes a procedure labeled as 'tcontext', not only access + * control decision on the procedure. The security label to be switched + * shall be computed uniquely on a pair of 'scontext' and 'tcontext', + * thus, it is reasonable to cache the new label on avc, and enables to + * reduce unnecessary system calls. It shall be referenced at + * sepgsql_needs_fmgr_hook to check whether the supplied function is a + * trusted procedure, or not. + */ + if (tclass == SEPG_CLASS_DB_PROCEDURE) + { + if (!ucontext) + ncontext = sepgsql_compute_create(scontext, tcontext, + SEPG_CLASS_PROCESS, NULL); + else + ncontext = sepgsql_compute_create(scontext, ucontext, + SEPG_CLASS_PROCESS, NULL); + if (strcmp(scontext, ncontext) == 0) + { + pfree(ncontext); + ncontext = NULL; + } + } + + /* + * Set up an avc_cache object + */ + oldctx = MemoryContextSwitchTo(avc_mem_cxt); + + cache = palloc0(sizeof(avc_cache)); + + cache->hash = hash; + cache->scontext = pstrdup(scontext); + cache->tcontext = pstrdup(tcontext); + cache->tclass = tclass; + + cache->allowed = avd.allowed; + cache->auditallow = avd.auditallow; + cache->auditdeny = avd.auditdeny; + cache->hot_cache = true; + if (avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE) + cache->permissive = true; + if (!ucontext) + cache->tcontext_is_valid = true; + if (ncontext) + cache->ncontext = pstrdup(ncontext); + + avc_num_caches++; + + if (avc_num_caches > avc_threshold) + sepgsql_avc_reclaim(); + + avc_slots[index] = lcons(cache, avc_slots[index]); + + MemoryContextSwitchTo(oldctx); + + return cache; +} + +/* + * sepgsql_avc_lookup + * + * Look up a cache entry that matches the supplied security contexts and + * object class. If not found, create a new cache entry. + */ +static avc_cache * +sepgsql_avc_lookup(const char *scontext, const char *tcontext, uint16 tclass) +{ + avc_cache *cache; + ListCell *cell; + uint32 hash; + int index; + + hash = sepgsql_avc_hash(scontext, tcontext, tclass); + index = hash % AVC_NUM_SLOTS; + + foreach(cell, avc_slots[index]) + { + cache = lfirst(cell); + + if (cache->hash == hash && + cache->tclass == tclass && + strcmp(cache->tcontext, tcontext) == 0 && + strcmp(cache->scontext, scontext) == 0) + { + cache->hot_cache = true; + return cache; + } + } + /* not found, so insert a new cache */ + return sepgsql_avc_compute(scontext, tcontext, tclass); +} + +/* + * sepgsql_avc_check_perms(_label) + * + * It returns 'true', if the security policy suggested to allow the required + * permissions. Otherwise, it returns 'false' or raises an error according + * to the 'abort_on_violation' argument. + * The 'tobject' and 'tclass' identify the target object being referenced, + * and 'required' is a bitmask of permissions (SEPG_*__*) defined for each + * object classes. + * The 'audit_name' is the object name (optional). If SEPGSQL_AVC_NOAUDIT + * was supplied, it means to skip all the audit messages. + */ +bool +sepgsql_avc_check_perms_label(const char *tcontext, + uint16 tclass, uint32 required, + const char *audit_name, + bool abort_on_violation) +{ + char *scontext = sepgsql_get_client_label(); + avc_cache *cache; + uint32 denied; + uint32 audited; + bool result; + + sepgsql_avc_check_valid(); + do + { + result = true; + + /* + * If the target object is unlabeled, we perform the check using the + * label supplied by sepgsql_avc_unlabeled(). + */ + if (tcontext) + cache = sepgsql_avc_lookup(scontext, tcontext, tclass); + else + cache = sepgsql_avc_lookup(scontext, + sepgsql_avc_unlabeled(), tclass); + + denied = required & ~cache->allowed; + + /* + * Compute permissions to be audited + */ + if (sepgsql_get_debug_audit()) + audited = (denied ? (denied & ~0) : (required & ~0)); + else + audited = denied ? (denied & cache->auditdeny) + : (required & cache->auditallow); + + if (denied) + { + /* + * In permissive mode or permissive domain, violated permissions + * shall be audited to the log files at once, and then implicitly + * allowed to avoid a flood of access denied logs, because the + * purpose of permissive mode/domain is to collect a violation log + * that will make it possible to fix up the security policy. + */ + if (!sepgsql_getenforce() || cache->permissive) + cache->allowed |= required; + else + result = false; + } + } while (!sepgsql_avc_check_valid()); + + /* + * In the case when we have something auditable actions here, + * sepgsql_audit_log shall be called with text representation of security + * labels for both of subject and object. It records this access + * violation, so DBA will be able to find out unexpected security problems + * later. + */ + if (audited != 0 && + audit_name != SEPGSQL_AVC_NOAUDIT && + sepgsql_get_mode() != SEPGSQL_MODE_INTERNAL) + { + sepgsql_audit_log(denied != 0, + (sepgsql_getenforce() && !cache->permissive), + cache->scontext, + cache->tcontext_is_valid ? + cache->tcontext : sepgsql_avc_unlabeled(), + cache->tclass, + audited, + audit_name); + } + + if (abort_on_violation && !result) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: security policy violation"))); + + return result; +} + +bool +sepgsql_avc_check_perms(const ObjectAddress *tobject, + uint16 tclass, uint32 required, + const char *audit_name, + bool abort_on_violation) +{ + char *tcontext = GetSecurityLabel(tobject, SEPGSQL_LABEL_TAG); + bool rc; + + rc = sepgsql_avc_check_perms_label(tcontext, + tclass, required, + audit_name, abort_on_violation); + if (tcontext) + pfree(tcontext); + + return rc; +} + +/* + * sepgsql_avc_trusted_proc + * + * If the supplied function OID is configured as a trusted procedure, this + * function will return a security label to be used during the execution of + * that function. Otherwise, it returns NULL. + */ +char * +sepgsql_avc_trusted_proc(Oid functionId) +{ + char *scontext = sepgsql_get_client_label(); + char *tcontext; + ObjectAddress tobject; + avc_cache *cache; + + tobject.classId = ProcedureRelationId; + tobject.objectId = functionId; + tobject.objectSubId = 0; + tcontext = GetSecurityLabel(&tobject, SEPGSQL_LABEL_TAG); + + sepgsql_avc_check_valid(); + do + { + if (tcontext) + cache = sepgsql_avc_lookup(scontext, tcontext, + SEPG_CLASS_DB_PROCEDURE); + else + cache = sepgsql_avc_lookup(scontext, sepgsql_avc_unlabeled(), + SEPG_CLASS_DB_PROCEDURE); + } while (!sepgsql_avc_check_valid()); + + return cache->ncontext; +} + +/* + * sepgsql_avc_exit + * + * Clean up userspace AVC on process exit. + */ +static void +sepgsql_avc_exit(int code, Datum arg) +{ + selinux_status_close(); +} + +/* + * sepgsql_avc_init + * + * Initialize the userspace AVC. This should be called from _PG_init. + */ +void +sepgsql_avc_init(void) +{ + int rc; + + /* + * All the avc stuff shall be allocated in avc_mem_cxt + */ + avc_mem_cxt = AllocSetContextCreate(TopMemoryContext, + "userspace access vector cache", + ALLOCSET_DEFAULT_SIZES); + memset(avc_slots, 0, sizeof(avc_slots)); + avc_num_caches = 0; + avc_lru_hint = 0; + avc_threshold = AVC_DEF_THRESHOLD; + + /* + * SELinux allows to mmap(2) its kernel status page in read-only mode to + * inform userspace applications its status updating (such as policy + * reloading) without system-call invocations. This feature is only + * supported in Linux-2.6.38 or later, however, libselinux provides a + * fallback mode to know its status using netlink sockets. + */ + rc = selinux_status_open(1); + if (rc < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: could not open selinux status : %m"))); + else if (rc > 0) + ereport(LOG, + (errmsg("SELinux: kernel status page uses fallback mode"))); + + /* Arrange to close selinux status page on process exit. */ + on_proc_exit(sepgsql_avc_exit, 0); +} |