Skip to content

Commit

Permalink
selinux: cache the SID -> context string translation
Browse files Browse the repository at this point in the history
Translating a context struct to string can be quite slow, especially if
the context has a lot of category bits set. This can cause quite
noticeable performance impact in situations where the translation needs
to be done repeatedly. A common example is a UNIX datagram socket with
the SO_PASSSEC option enabled, which is used e.g. by systemd-journald
when receiving log messages via datagram socket. This scenario can be
reproduced with:

    cat /dev/urandom | base64 | logger &
    timeout 30s perf record -p $(pidof systemd-journald) -a -g
    kill %1
    perf report -g none --pretty raw | grep security_secid_to_secctx

Before the caching introduced by this patch, computing the context
string (security_secid_to_secctx() function) takes up ~65% of
systemd-journald's CPU time (assuming a context with 1024 categories
set and Fedora x86_64 release kernel configs). After this patch
(assuming near-perfect cache hit ratio) this overhead is reduced to just
~2%.

This patch addresses the issue by caching a certain number (compile-time
configurable) of recently used context strings to speed up repeated
translations of the same context, while using only a small amount of
memory.

The cache is integrated into the existing sidtab table by adding a field
to each entry, which when not NULL contains an RCU-protected pointer to
a cache entry containing the cached string. The cache entries are kept
in a linked list sorted according to how recently they were used. On a
cache miss when the cache is full, the least recently used entry is
removed to make space for the new entry.

The patch migrates security_sid_to_context_core() to use the cache (also
a few other functions where it was possible without too much fuss, but
these mostly use the translation for logging in case of error, which is
rare).

Link: https://bugzilla.redhat.com/show_bug.cgi?id=1733259
Cc: Michal Sekletar <[email protected]>
Signed-off-by: Ondrej Mosnacek <[email protected]>
Reviewed-by: Stephen Smalley <[email protected]>
Tested-by: Stephen Smalley <[email protected]>
Reviewed-by: Paul E. McKenney <[email protected]>
[PM: lots of merge fixups due to collisions with other sidtab patches]
Signed-off-by: Paul Moore <[email protected]>
  • Loading branch information
WOnder93 authored and pcmoore committed Dec 9, 2019
1 parent 66f8e2f commit d97bd23
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 94 deletions.
11 changes: 11 additions & 0 deletions security/selinux/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,14 @@ config SECURITY_SELINUX_SIDTAB_HASH_BITS
collisions may be viewed at /sys/fs/selinux/ss/sidtab_hash_stats. If
chain lengths are high (e.g. > 20) then selecting a higher value here
will ensure that lookups times are short and stable.

config SECURITY_SELINUX_SID2STR_CACHE_SIZE
int "NSA SELinux SID to context string translation cache size"
depends on SECURITY_SELINUX
default 256
help
This option defines the size of the internal SID -> context string
cache, which improves the performance of context to string
conversion. Setting this option to 0 disables the cache completely.

If unsure, keep the default value.
138 changes: 84 additions & 54 deletions security/selinux/ss/services.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ static int context_struct_to_string(struct policydb *policydb,
char **scontext,
u32 *scontext_len);

static int sidtab_entry_to_string(struct policydb *policydb,
struct sidtab *sidtab,
struct sidtab_entry *entry,
char **scontext,
u32 *scontext_len);

static void context_struct_compute_av(struct policydb *policydb,
struct context *scontext,
struct context *tcontext,
Expand Down Expand Up @@ -716,20 +722,21 @@ static void context_struct_compute_av(struct policydb *policydb,
}

static int security_validtrans_handle_fail(struct selinux_state *state,
struct context *ocontext,
struct context *ncontext,
struct context *tcontext,
struct sidtab_entry *oentry,
struct sidtab_entry *nentry,
struct sidtab_entry *tentry,
u16 tclass)
{
struct policydb *p = &state->ss->policydb;
struct sidtab *sidtab = state->ss->sidtab;
char *o = NULL, *n = NULL, *t = NULL;
u32 olen, nlen, tlen;

if (context_struct_to_string(p, ocontext, &o, &olen))
if (sidtab_entry_to_string(p, sidtab, oentry, &o, &olen))
goto out;
if (context_struct_to_string(p, ncontext, &n, &nlen))
if (sidtab_entry_to_string(p, sidtab, nentry, &n, &nlen))
goto out;
if (context_struct_to_string(p, tcontext, &t, &tlen))
if (sidtab_entry_to_string(p, sidtab, tentry, &t, &tlen))
goto out;
audit_log(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR,
"op=security_validate_transition seresult=denied"
Expand All @@ -751,9 +758,9 @@ static int security_compute_validatetrans(struct selinux_state *state,
{
struct policydb *policydb;
struct sidtab *sidtab;
struct context *ocontext;
struct context *ncontext;
struct context *tcontext;
struct sidtab_entry *oentry;
struct sidtab_entry *nentry;
struct sidtab_entry *tentry;
struct class_datum *tclass_datum;
struct constraint_node *constraint;
u16 tclass;
Expand All @@ -779,24 +786,24 @@ static int security_compute_validatetrans(struct selinux_state *state,
}
tclass_datum = policydb->class_val_to_struct[tclass - 1];

ocontext = sidtab_search(sidtab, oldsid);
if (!ocontext) {
oentry = sidtab_search_entry(sidtab, oldsid);
if (!oentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, oldsid);
rc = -EINVAL;
goto out;
}

ncontext = sidtab_search(sidtab, newsid);
if (!ncontext) {
nentry = sidtab_search_entry(sidtab, newsid);
if (!nentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, newsid);
rc = -EINVAL;
goto out;
}

tcontext = sidtab_search(sidtab, tasksid);
if (!tcontext) {
tentry = sidtab_search_entry(sidtab, tasksid);
if (!tentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, tasksid);
rc = -EINVAL;
Expand All @@ -805,15 +812,16 @@ static int security_compute_validatetrans(struct selinux_state *state,

constraint = tclass_datum->validatetrans;
while (constraint) {
if (!constraint_expr_eval(policydb, ocontext, ncontext,
tcontext, constraint->expr)) {
if (!constraint_expr_eval(policydb, &oentry->context,
&nentry->context, &tentry->context,
constraint->expr)) {
if (user)
rc = -EPERM;
else
rc = security_validtrans_handle_fail(state,
ocontext,
ncontext,
tcontext,
oentry,
nentry,
tentry,
tclass);
goto out;
}
Expand Down Expand Up @@ -855,7 +863,7 @@ int security_bounded_transition(struct selinux_state *state,
{
struct policydb *policydb;
struct sidtab *sidtab;
struct context *old_context, *new_context;
struct sidtab_entry *old_entry, *new_entry;
struct type_datum *type;
int index;
int rc;
Expand All @@ -869,27 +877,27 @@ int security_bounded_transition(struct selinux_state *state,
sidtab = state->ss->sidtab;

rc = -EINVAL;
old_context = sidtab_search(sidtab, old_sid);
if (!old_context) {
old_entry = sidtab_search_entry(sidtab, old_sid);
if (!old_entry) {
pr_err("SELinux: %s: unrecognized SID %u\n",
__func__, old_sid);
goto out;
}

rc = -EINVAL;
new_context = sidtab_search(sidtab, new_sid);
if (!new_context) {
new_entry = sidtab_search_entry(sidtab, new_sid);
if (!new_entry) {
pr_err("SELinux: %s: unrecognized SID %u\n",
__func__, new_sid);
goto out;
}

rc = 0;
/* type/domain unchanged */
if (old_context->type == new_context->type)
if (old_entry->context.type == new_entry->context.type)
goto out;

index = new_context->type;
index = new_entry->context.type;
while (true) {
type = policydb->type_val_to_struct[index - 1];
BUG_ON(!type);
Expand All @@ -901,7 +909,7 @@ int security_bounded_transition(struct selinux_state *state,

/* @newsid is bounded by @oldsid */
rc = 0;
if (type->bounds == old_context->type)
if (type->bounds == old_entry->context.type)
break;

index = type->bounds;
Expand All @@ -912,10 +920,10 @@ int security_bounded_transition(struct selinux_state *state,
char *new_name = NULL;
u32 length;

if (!context_struct_to_string(policydb, old_context,
&old_name, &length) &&
!context_struct_to_string(policydb, new_context,
&new_name, &length)) {
if (!sidtab_entry_to_string(policydb, sidtab, old_entry,
&old_name, &length) &&
!sidtab_entry_to_string(policydb, sidtab, new_entry,
&new_name, &length)) {
audit_log(audit_context(),
GFP_ATOMIC, AUDIT_SELINUX_ERR,
"op=security_bounded_transition "
Expand Down Expand Up @@ -1255,6 +1263,23 @@ static int context_struct_to_string(struct policydb *p,
return 0;
}

static int sidtab_entry_to_string(struct policydb *p,
struct sidtab *sidtab,
struct sidtab_entry *entry,
char **scontext, u32 *scontext_len)
{
int rc = sidtab_sid2str_get(sidtab, entry, scontext, scontext_len);

if (rc != -ENOENT)
return rc;

rc = context_struct_to_string(p, &entry->context, scontext,
scontext_len);
if (!rc && scontext)
sidtab_sid2str_put(sidtab, entry, *scontext, *scontext_len);
return rc;
}

#include "initial_sid_to_string.h"

int security_sidtab_hash_stats(struct selinux_state *state, char *page)
Expand Down Expand Up @@ -1282,7 +1307,7 @@ static int security_sid_to_context_core(struct selinux_state *state,
{
struct policydb *policydb;
struct sidtab *sidtab;
struct context *context;
struct sidtab_entry *entry;
int rc = 0;

if (scontext)
Expand Down Expand Up @@ -1313,21 +1338,23 @@ static int security_sid_to_context_core(struct selinux_state *state,
read_lock(&state->ss->policy_rwlock);
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;

if (force)
context = sidtab_search_force(sidtab, sid);
entry = sidtab_search_entry_force(sidtab, sid);
else
context = sidtab_search(sidtab, sid);
if (!context) {
entry = sidtab_search_entry(sidtab, sid);
if (!entry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, sid);
rc = -EINVAL;
goto out_unlock;
}
if (only_invalid && !context->len)
rc = 0;
else
rc = context_struct_to_string(policydb, context, scontext,
scontext_len);
if (only_invalid && !entry->context.len)
goto out_unlock;

rc = sidtab_entry_to_string(policydb, sidtab, entry, scontext,
scontext_len);

out_unlock:
read_unlock(&state->ss->policy_rwlock);
out:
Expand Down Expand Up @@ -1621,19 +1648,20 @@ int security_context_to_sid_force(struct selinux_state *state,

static int compute_sid_handle_invalid_context(
struct selinux_state *state,
struct context *scontext,
struct context *tcontext,
struct sidtab_entry *sentry,
struct sidtab_entry *tentry,
u16 tclass,
struct context *newcontext)
{
struct policydb *policydb = &state->ss->policydb;
struct sidtab *sidtab = state->ss->sidtab;
char *s = NULL, *t = NULL, *n = NULL;
u32 slen, tlen, nlen;
struct audit_buffer *ab;

if (context_struct_to_string(policydb, scontext, &s, &slen))
if (sidtab_entry_to_string(policydb, sidtab, sentry, &s, &slen))
goto out;
if (context_struct_to_string(policydb, tcontext, &t, &tlen))
if (sidtab_entry_to_string(policydb, sidtab, tentry, &t, &tlen))
goto out;
if (context_struct_to_string(policydb, newcontext, &n, &nlen))
goto out;
Expand Down Expand Up @@ -1692,7 +1720,8 @@ static int security_compute_sid(struct selinux_state *state,
struct policydb *policydb;
struct sidtab *sidtab;
struct class_datum *cladatum = NULL;
struct context *scontext = NULL, *tcontext = NULL, newcontext;
struct context *scontext, *tcontext, newcontext;
struct sidtab_entry *sentry, *tentry;
struct role_trans *roletr = NULL;
struct avtab_key avkey;
struct avtab_datum *avdatum;
Expand Down Expand Up @@ -1729,21 +1758,24 @@ static int security_compute_sid(struct selinux_state *state,
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;

scontext = sidtab_search(sidtab, ssid);
if (!scontext) {
sentry = sidtab_search_entry(sidtab, ssid);
if (!sentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, ssid);
rc = -EINVAL;
goto out_unlock;
}
tcontext = sidtab_search(sidtab, tsid);
if (!tcontext) {
tentry = sidtab_search_entry(sidtab, tsid);
if (!tentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, tsid);
rc = -EINVAL;
goto out_unlock;
}

scontext = &sentry->context;
tcontext = &tentry->context;

if (tclass && tclass <= policydb->p_classes.nprim)
cladatum = policydb->class_val_to_struct[tclass - 1];

Expand Down Expand Up @@ -1844,10 +1876,8 @@ static int security_compute_sid(struct selinux_state *state,

/* Check the validity of the context. */
if (!policydb_context_isvalid(policydb, &newcontext)) {
rc = compute_sid_handle_invalid_context(state, scontext,
tcontext,
tclass,
&newcontext);
rc = compute_sid_handle_invalid_context(state, sentry, tentry,
tclass, &newcontext);
if (rc)
goto out_unlock;
}
Expand Down
Loading

0 comments on commit d97bd23

Please sign in to comment.