diff options
Diffstat (limited to 'security/selinux/ss')
-rw-r--r-- | security/selinux/ss/context.h | 43 | ||||
-rw-r--r-- | security/selinux/ss/ebitmap.c | 18 | ||||
-rw-r--r-- | security/selinux/ss/ebitmap.h | 1 | ||||
-rw-r--r-- | security/selinux/ss/mls.c | 3 | ||||
-rw-r--r-- | security/selinux/ss/policydb.c | 416 | ||||
-rw-r--r-- | security/selinux/ss/policydb.h | 5 | ||||
-rw-r--r-- | security/selinux/ss/services.c | 327 | ||||
-rw-r--r-- | security/selinux/ss/services.h | 6 | ||||
-rw-r--r-- | security/selinux/ss/sidtab.c | 416 | ||||
-rw-r--r-- | security/selinux/ss/sidtab.h | 85 |
10 files changed, 813 insertions, 507 deletions
diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h index 2260c44a568c..3ba044fe02ed 100644 --- a/security/selinux/ss/context.h +++ b/security/selinux/ss/context.h @@ -31,6 +31,7 @@ struct context { u32 len; /* length of string in bytes */ struct mls_range range; char *str; /* string representation if context cannot be mapped. */ + u32 hash; /* a hash of the string representation */ }; static inline void mls_context_init(struct context *c) @@ -95,6 +96,38 @@ out: return rc; } + +static inline int mls_context_glblub(struct context *dst, + struct context *c1, struct context *c2) +{ + struct mls_range *dr = &dst->range, *r1 = &c1->range, *r2 = &c2->range; + int rc = 0; + + if (r1->level[1].sens < r2->level[0].sens || + r2->level[1].sens < r1->level[0].sens) + /* These ranges have no common sensitivities */ + return -EINVAL; + + /* Take the greatest of the low */ + dr->level[0].sens = max(r1->level[0].sens, r2->level[0].sens); + + /* Take the least of the high */ + dr->level[1].sens = min(r1->level[1].sens, r2->level[1].sens); + + rc = ebitmap_and(&dr->level[0].cat, + &r1->level[0].cat, &r2->level[0].cat); + if (rc) + goto out; + + rc = ebitmap_and(&dr->level[1].cat, + &r1->level[1].cat, &r2->level[1].cat); + if (rc) + goto out; + +out: + return rc; +} + static inline int mls_context_cmp(struct context *c1, struct context *c2) { return ((c1->range.level[0].sens == c2->range.level[0].sens) && @@ -136,12 +169,13 @@ static inline int context_cpy(struct context *dst, struct context *src) kfree(dst->str); return rc; } + dst->hash = src->hash; return 0; } static inline void context_destroy(struct context *c) { - c->user = c->role = c->type = 0; + c->user = c->role = c->type = c->hash = 0; kfree(c->str); c->str = NULL; c->len = 0; @@ -150,6 +184,8 @@ static inline void context_destroy(struct context *c) static inline int context_cmp(struct context *c1, struct context *c2) { + if (c1->hash && c2->hash && (c1->hash != c2->hash)) + return 0; if (c1->len && c2->len) return (c1->len == c2->len && !strcmp(c1->str, c2->str)); if (c1->len || c2->len) @@ -160,5 +196,10 @@ static inline int context_cmp(struct context *c1, struct context *c2) mls_context_cmp(c1, c2)); } +static inline unsigned int context_compute_hash(const char *s) +{ + return full_name_hash(NULL, s, strlen(s)); +} + #endif /* _SS_CONTEXT_H_ */ diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c index 09929fc5ab47..c8c3663111e2 100644 --- a/security/selinux/ss/ebitmap.c +++ b/security/selinux/ss/ebitmap.c @@ -77,6 +77,24 @@ int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src) return 0; } +int ebitmap_and(struct ebitmap *dst, struct ebitmap *e1, struct ebitmap *e2) +{ + struct ebitmap_node *n; + int bit, rc; + + ebitmap_init(dst); + + ebitmap_for_each_positive_bit(e1, n, bit) { + if (ebitmap_get_bit(e2, bit)) { + rc = ebitmap_set_bit(dst, bit, 1); + if (rc < 0) + return rc; + } + } + return 0; +} + + #ifdef CONFIG_NETLABEL /** * ebitmap_netlbl_export - Export an ebitmap into a NetLabel category bitmap diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h index 6aa7cf6a2197..9a23b81b8832 100644 --- a/security/selinux/ss/ebitmap.h +++ b/security/selinux/ss/ebitmap.h @@ -124,6 +124,7 @@ static inline void ebitmap_node_clr_bit(struct ebitmap_node *n, int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2); int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src); +int ebitmap_and(struct ebitmap *dst, struct ebitmap *e1, struct ebitmap *e2); int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2, u32 last_e2bit); int ebitmap_get_bit(struct ebitmap *e, unsigned long bit); int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value); diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index 5e05f5b902d7..ec5e3d1da9ac 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -529,6 +529,9 @@ int mls_compute_sid(struct policydb *p, return mls_context_cpy_high(newcontext, tcontext); case DEFAULT_TARGET_LOW_HIGH: return mls_context_cpy(newcontext, tcontext); + case DEFAULT_GLBLUB: + return mls_context_glblub(newcontext, + scontext, tcontext); } /* Fallthrough */ diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index f8efaa9f647c..2aa7f2e1a8e7 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -160,6 +160,11 @@ static struct policydb_compat_info policydb_compat[] = { .sym_num = SYM_NUM, .ocon_num = OCON_NUM, }, + { + .version = POLICYDB_VERSION_GLBLUB, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, }; static struct policydb_compat_info *policydb_lookup_compat(int version) @@ -177,6 +182,195 @@ static struct policydb_compat_info *policydb_lookup_compat(int version) } /* + * The following *_destroy functions are used to + * free any memory allocated for each kind of + * symbol data in the policy database. + */ + +static int perm_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int common_destroy(void *key, void *datum, void *p) +{ + struct common_datum *comdatum; + + kfree(key); + if (datum) { + comdatum = datum; + hashtab_map(comdatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(comdatum->permissions.table); + } + kfree(datum); + return 0; +} + +static void constraint_expr_destroy(struct constraint_expr *expr) +{ + if (expr) { + ebitmap_destroy(&expr->names); + if (expr->type_names) { + ebitmap_destroy(&expr->type_names->types); + ebitmap_destroy(&expr->type_names->negset); + kfree(expr->type_names); + } + kfree(expr); + } +} + +static int cls_destroy(void *key, void *datum, void *p) +{ + struct class_datum *cladatum; + struct constraint_node *constraint, *ctemp; + struct constraint_expr *e, *etmp; + + kfree(key); + if (datum) { + cladatum = datum; + hashtab_map(cladatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(cladatum->permissions.table); + constraint = cladatum->constraints; + while (constraint) { + e = constraint->expr; + while (e) { + etmp = e; + e = e->next; + constraint_expr_destroy(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + + constraint = cladatum->validatetrans; + while (constraint) { + e = constraint->expr; + while (e) { + etmp = e; + e = e->next; + constraint_expr_destroy(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + kfree(cladatum->comkey); + } + kfree(datum); + return 0; +} + +static int role_destroy(void *key, void *datum, void *p) +{ + struct role_datum *role; + + kfree(key); + if (datum) { + role = datum; + ebitmap_destroy(&role->dominates); + ebitmap_destroy(&role->types); + } + kfree(datum); + return 0; +} + +static int type_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int user_destroy(void *key, void *datum, void *p) +{ + struct user_datum *usrdatum; + + kfree(key); + if (datum) { + usrdatum = datum; + ebitmap_destroy(&usrdatum->roles); + ebitmap_destroy(&usrdatum->range.level[0].cat); + ebitmap_destroy(&usrdatum->range.level[1].cat); + ebitmap_destroy(&usrdatum->dfltlevel.cat); + } + kfree(datum); + return 0; +} + +static int sens_destroy(void *key, void *datum, void *p) +{ + struct level_datum *levdatum; + + kfree(key); + if (datum) { + levdatum = datum; + if (levdatum->level) + ebitmap_destroy(&levdatum->level->cat); + kfree(levdatum->level); + } + kfree(datum); + return 0; +} + +static int cat_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap) = +{ + common_destroy, + cls_destroy, + role_destroy, + type_destroy, + user_destroy, + cond_destroy_bool, + sens_destroy, + cat_destroy, +}; + +static int filenametr_destroy(void *key, void *datum, void *p) +{ + struct filename_trans *ft = key; + + kfree(ft->name); + kfree(key); + kfree(datum); + cond_resched(); + return 0; +} + +static int range_tr_destroy(void *key, void *datum, void *p) +{ + struct mls_range *rt = datum; + + kfree(key); + ebitmap_destroy(&rt->level[0].cat); + ebitmap_destroy(&rt->level[1].cat); + kfree(datum); + cond_resched(); + return 0; +} + +static void ocontext_destroy(struct ocontext *c, int i) +{ + if (!c) + return; + + context_destroy(&c->context[0]); + context_destroy(&c->context[1]); + if (i == OCON_ISID || i == OCON_FS || + i == OCON_NETIF || i == OCON_FSUSE) + kfree(c->u.name); + kfree(c); +} + +/* * Initialize the role table. */ static int roles_init(struct policydb *p) @@ -250,6 +444,7 @@ static int filenametr_cmp(struct hashtab *h, const void *k1, const void *k2) static u32 rangetr_hash(struct hashtab *h, const void *k) { const struct range_trans *key = k; + return (key->source_type + (key->target_type << 3) + (key->target_class << 5)) & (h->size - 1); } @@ -272,8 +467,6 @@ static int rangetr_cmp(struct hashtab *h, const void *k1, const void *k2) return v; } -static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap); - /* * Initialize a policy database structure. */ @@ -301,7 +494,8 @@ static int policydb_init(struct policydb *p) if (rc) goto out; - p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, (1 << 10)); + p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, + (1 << 10)); if (!p->filename_trans) { rc = -ENOMEM; goto out; @@ -399,7 +593,7 @@ static int type_index(void *key, void *datum, void *datap) || typdatum->bounds > p->p_types.nprim) return -EINVAL; p->sym_val_to_name[SYM_TYPES][typdatum->value - 1] = key; - p->type_val_to_struct_array[typdatum->value - 1] = typdatum; + p->type_val_to_struct[typdatum->value - 1] = typdatum; } return 0; @@ -477,9 +671,9 @@ static void hash_eval(struct hashtab *h, const char *hash_name) struct hashtab_info info; hashtab_stat(h, &info); - pr_debug("SELinux: %s: %d entries and %d/%d buckets used, " - "longest chain length %d\n", hash_name, h->nel, - info.slots_used, h->size, info.max_chain_len); + pr_debug("SELinux: %s: %d entries and %d/%d buckets used, longest chain length %d\n", + hash_name, h->nel, info.slots_used, h->size, + info.max_chain_len); } static void symtab_hash_eval(struct symtab *s) @@ -541,10 +735,10 @@ static int policydb_index(struct policydb *p) if (!p->user_val_to_struct) return -ENOMEM; - p->type_val_to_struct_array = kvcalloc(p->p_types.nprim, - sizeof(*p->type_val_to_struct_array), - GFP_KERNEL); - if (!p->type_val_to_struct_array) + p->type_val_to_struct = kvcalloc(p->p_types.nprim, + sizeof(*p->type_val_to_struct), + GFP_KERNEL); + if (!p->type_val_to_struct) return -ENOMEM; rc = cond_init_bool_indexes(p); @@ -568,193 +762,6 @@ out: } /* - * The following *_destroy functions are used to - * free any memory allocated for each kind of - * symbol data in the policy database. - */ - -static int perm_destroy(void *key, void *datum, void *p) -{ - kfree(key); - kfree(datum); - return 0; -} - -static int common_destroy(void *key, void *datum, void *p) -{ - struct common_datum *comdatum; - - kfree(key); - if (datum) { - comdatum = datum; - hashtab_map(comdatum->permissions.table, perm_destroy, NULL); - hashtab_destroy(comdatum->permissions.table); - } - kfree(datum); - return 0; -} - -static void constraint_expr_destroy(struct constraint_expr *expr) -{ - if (expr) { - ebitmap_destroy(&expr->names); - if (expr->type_names) { - ebitmap_destroy(&expr->type_names->types); - ebitmap_destroy(&expr->type_names->negset); - kfree(expr->type_names); - } - kfree(expr); - } -} - -static int cls_destroy(void *key, void *datum, void *p) -{ - struct class_datum *cladatum; - struct constraint_node *constraint, *ctemp; - struct constraint_expr *e, *etmp; - - kfree(key); - if (datum) { - cladatum = datum; - hashtab_map(cladatum->permissions.table, perm_destroy, NULL); - hashtab_destroy(cladatum->permissions.table); - constraint = cladatum->constraints; - while (constraint) { - e = constraint->expr; - while (e) { - etmp = e; - e = e->next; - constraint_expr_destroy(etmp); - } - ctemp = constraint; - constraint = constraint->next; - kfree(ctemp); - } - - constraint = cladatum->validatetrans; - while (constraint) { - e = constraint->expr; - while (e) { - etmp = e; - e = e->next; - constraint_expr_destroy(etmp); - } - ctemp = constraint; - constraint = constraint->next; - kfree(ctemp); - } - kfree(cladatum->comkey); - } - kfree(datum); - return 0; -} - -static int role_destroy(void *key, void *datum, void *p) -{ - struct role_datum *role; - - kfree(key); - if (datum) { - role = datum; - ebitmap_destroy(&role->dominates); - ebitmap_destroy(&role->types); - } - kfree(datum); - return 0; -} - -static int type_destroy(void *key, void *datum, void *p) -{ - kfree(key); - kfree(datum); - return 0; -} - -static int user_destroy(void *key, void *datum, void *p) -{ - struct user_datum *usrdatum; - - kfree(key); - if (datum) { - usrdatum = datum; - ebitmap_destroy(&usrdatum->roles); - ebitmap_destroy(&usrdatum->range.level[0].cat); - ebitmap_destroy(&usrdatum->range.level[1].cat); - ebitmap_destroy(&usrdatum->dfltlevel.cat); - } - kfree(datum); - return 0; -} - -static int sens_destroy(void *key, void *datum, void *p) -{ - struct level_datum *levdatum; - - kfree(key); - if (datum) { - levdatum = datum; - if (levdatum->level) - ebitmap_destroy(&levdatum->level->cat); - kfree(levdatum->level); - } - kfree(datum); - return 0; -} - -static int cat_destroy(void *key, void *datum, void *p) -{ - kfree(key); - kfree(datum); - return 0; -} - -static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap) = -{ - common_destroy, - cls_destroy, - role_destroy, - type_destroy, - user_destroy, - cond_destroy_bool, - sens_destroy, - cat_destroy, -}; - -static int filenametr_destroy(void *key, void *datum, void *p) -{ - struct filename_trans *ft = key; - kfree(ft->name); - kfree(key); - kfree(datum); - cond_resched(); - return 0; -} - -static int range_tr_destroy(void *key, void *datum, void *p) -{ - struct mls_range *rt = datum; - kfree(key); - ebitmap_destroy(&rt->level[0].cat); - ebitmap_destroy(&rt->level[1].cat); - kfree(datum); - cond_resched(); - return 0; -} - -static void ocontext_destroy(struct ocontext *c, int i) -{ - if (!c) - return; - - context_destroy(&c->context[0]); - context_destroy(&c->context[1]); - if (i == OCON_ISID || i == OCON_FS || - i == OCON_NETIF || i == OCON_FSUSE) - kfree(c->u.name); - kfree(c); -} - -/* * Free any memory allocated by a policy database structure. */ void policydb_destroy(struct policydb *p) @@ -777,7 +784,7 @@ void policydb_destroy(struct policydb *p) kfree(p->class_val_to_struct); kfree(p->role_val_to_struct); kfree(p->user_val_to_struct); - kvfree(p->type_val_to_struct_array); + kvfree(p->type_val_to_struct); avtab_destroy(&p->te_avtab); @@ -871,6 +878,11 @@ int policydb_load_isids(struct policydb *p, struct sidtab *s) sidtab_destroy(s); goto out; } + rc = context_add_hash(p, &c->context[0]); + if (rc) { + sidtab_destroy(s); + goto out; + } rc = sidtab_set_initial(s, c->sid[0], &c->context[0]); if (rc) { @@ -1722,7 +1734,7 @@ static int type_bounds_sanity_check(void *key, void *datum, void *datap) return -EINVAL; } - upper = p->type_val_to_struct_array[upper->bounds - 1]; + upper = p->type_val_to_struct[upper->bounds - 1]; BUG_ON(!upper); if (upper->attribute) { @@ -2647,7 +2659,7 @@ static int role_trans_write(struct policydb *p, void *fp) { struct role_trans *r = p->role_tr; struct role_trans *tr; - u32 buf[3]; + __le32 buf[3]; size_t nel; int rc; @@ -2679,7 +2691,7 @@ static int role_trans_write(struct policydb *p, void *fp) static int role_allow_write(struct role_allow *r, void *fp) { struct role_allow *ra; - u32 buf[2]; + __le32 buf[2]; size_t nel; int rc; diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index fcc6366b447f..69b24191fa38 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -69,6 +69,7 @@ struct class_datum { #define DEFAULT_TARGET_LOW 4 #define DEFAULT_TARGET_HIGH 5 #define DEFAULT_TARGET_LOW_HIGH 6 +#define DEFAULT_GLBLUB 7 char default_range; }; @@ -253,7 +254,7 @@ struct policydb { struct class_datum **class_val_to_struct; struct role_datum **role_val_to_struct; struct user_datum **user_val_to_struct; - struct type_datum **type_val_to_struct_array; + struct type_datum **type_val_to_struct; /* type enforcement access vectors and transitions */ struct avtab te_avtab; @@ -306,7 +307,7 @@ struct policydb { u16 process_class; u32 process_trans_perms; -}; +} __randomize_layout; extern void policydb_destroy(struct policydb *p); extern int policydb_load_isids(struct policydb *p, struct sidtab *s); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index d61563a3695e..216ce602a2b5 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -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, @@ -542,13 +548,13 @@ static void type_attribute_bounds_av(struct policydb *policydb, struct type_datum *target; u32 masked = 0; - source = policydb->type_val_to_struct_array[scontext->type - 1]; + source = policydb->type_val_to_struct[scontext->type - 1]; BUG_ON(!source); if (!source->bounds) return; - target = policydb->type_val_to_struct_array[tcontext->type - 1]; + target = policydb->type_val_to_struct[tcontext->type - 1]; BUG_ON(!target); memset(&lo_avd, 0, sizeof(lo_avd)); @@ -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" @@ -751,16 +758,16 @@ 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; int rc = 0; - if (!state->initialized) + if (!selinux_initialized(state)) return 0; read_lock(&state->ss->policy_rwlock); @@ -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; @@ -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; } @@ -855,12 +863,12 @@ 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; - if (!state->initialized) + if (!selinux_initialized(state)) return 0; read_lock(&state->ss->policy_rwlock); @@ -869,16 +877,16 @@ 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; @@ -886,12 +894,12 @@ int security_bounded_transition(struct selinux_state *state, 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_array[index - 1]; + type = policydb->type_val_to_struct[index - 1]; BUG_ON(!type); /* not bounded anymore */ @@ -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; @@ -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 " @@ -1019,7 +1027,7 @@ void security_compute_xperms_decision(struct selinux_state *state, memset(xpermd->dontaudit->p, 0, sizeof(xpermd->dontaudit->p)); read_lock(&state->ss->policy_rwlock); - if (!state->initialized) + if (!selinux_initialized(state)) goto allow; policydb = &state->ss->policydb; @@ -1104,7 +1112,7 @@ void security_compute_av(struct selinux_state *state, read_lock(&state->ss->policy_rwlock); avd_init(state, avd); xperms->len = 0; - if (!state->initialized) + if (!selinux_initialized(state)) goto allow; policydb = &state->ss->policydb; @@ -1158,7 +1166,7 @@ void security_compute_av_user(struct selinux_state *state, read_lock(&state->ss->policy_rwlock); avd_init(state, avd); - if (!state->initialized) + if (!selinux_initialized(state)) goto allow; policydb = &state->ss->policydb; @@ -1255,8 +1263,42 @@ 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) +{ + int rc; + + if (!selinux_initialized(state)) { + pr_err("SELinux: %s: called before initial load_policy\n", + __func__); + return -EINVAL; + } + + read_lock(&state->ss->policy_rwlock); + rc = sidtab_hash_stats(state->ss->sidtab, page); + read_unlock(&state->ss->policy_rwlock); + + return rc; +} + const char *security_get_initial_sid_context(u32 sid) { if (unlikely(sid > SECINITSID_NUM)) @@ -1271,14 +1313,14 @@ 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) *scontext = NULL; *scontext_len = 0; - if (!state->initialized) { + if (!selinux_initialized(state)) { if (sid <= SECINITSID_NUM) { char *scontextp; @@ -1302,21 +1344,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: @@ -1449,6 +1493,42 @@ out: return rc; } +int context_add_hash(struct policydb *policydb, + struct context *context) +{ + int rc; + char *str; + int len; + + if (context->str) { + context->hash = context_compute_hash(context->str); + } else { + rc = context_struct_to_string(policydb, context, + &str, &len); + if (rc) + return rc; + context->hash = context_compute_hash(str); + kfree(str); + } + return 0; +} + +static int context_struct_to_sid(struct selinux_state *state, + struct context *context, u32 *sid) +{ + int rc; + struct sidtab *sidtab = state->ss->sidtab; + struct policydb *policydb = &state->ss->policydb; + + if (!context->hash) { + rc = context_add_hash(policydb, context); + if (rc) + return rc; + } + + return sidtab_context_to_sid(sidtab, context, sid); +} + static int security_context_to_sid_core(struct selinux_state *state, const char *scontext, u32 scontext_len, u32 *sid, u32 def_sid, gfp_t gfp_flags, @@ -1469,7 +1549,7 @@ static int security_context_to_sid_core(struct selinux_state *state, if (!scontext2) return -ENOMEM; - if (!state->initialized) { + if (!selinux_initialized(state)) { int i; for (i = 1; i < SECINITSID_NUM; i++) { @@ -1501,7 +1581,7 @@ static int security_context_to_sid_core(struct selinux_state *state, str = NULL; } else if (rc) goto out_unlock; - rc = sidtab_context_to_sid(sidtab, &context, sid); + rc = context_struct_to_sid(state, &context, sid); context_destroy(&context); out_unlock: read_unlock(&state->ss->policy_rwlock); @@ -1574,19 +1654,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; @@ -1645,7 +1726,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; @@ -1654,7 +1736,7 @@ static int security_compute_sid(struct selinux_state *state, int rc = 0; bool sock; - if (!state->initialized) { + if (!selinux_initialized(state)) { switch (orig_tclass) { case SECCLASS_PROCESS: /* kernel value */ *out_sid = ssid; @@ -1682,21 +1764,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]; @@ -1797,15 +1882,13 @@ 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; } /* Obtain the sid for the context. */ - rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid); + rc = context_struct_to_sid(state, &newcontext, out_sid); out_unlock: read_unlock(&state->ss->policy_rwlock); context_destroy(&newcontext); @@ -1946,10 +2029,18 @@ static int convert_context(struct context *oldc, struct context *newc, void *p) rc = string_to_context_struct(args->newp, NULL, s, newc, SECSID_NULL); if (rc == -EINVAL) { - /* Retain string representation for later mapping. */ + /* + * Retain string representation for later mapping. + * + * IMPORTANT: We need to copy the contents of oldc->str + * back into s again because string_to_context_struct() + * may have garbled it. + */ + memcpy(s, oldc->str, oldc->len); context_init(newc); newc->str = s; newc->len = oldc->len; + newc->hash = oldc->hash; return 0; } kfree(s); @@ -2026,6 +2117,10 @@ static int convert_context(struct context *oldc, struct context *newc, void *p) goto bad; } + rc = context_add_hash(args->newp, newc); + if (rc) + goto bad; + return 0; bad: /* Map old representation to string and save it. */ @@ -2035,6 +2130,7 @@ bad: context_destroy(newc); newc->str = s; newc->len = len; + newc->hash = context_compute_hash(s); pr_info("SELinux: Context %s became invalid (unmapped).\n", newc->str); return 0; @@ -2087,26 +2183,17 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len) int rc = 0; struct policy_file file = { data, len }, *fp = &file; - oldpolicydb = kcalloc(2, sizeof(*oldpolicydb), GFP_KERNEL); - if (!oldpolicydb) { - rc = -ENOMEM; - goto out; - } - newpolicydb = oldpolicydb + 1; - policydb = &state->ss->policydb; newsidtab = kmalloc(sizeof(*newsidtab), GFP_KERNEL); - if (!newsidtab) { - rc = -ENOMEM; - goto out; - } + if (!newsidtab) + return -ENOMEM; - if (!state->initialized) { + if (!selinux_initialized(state)) { rc = policydb_read(policydb, fp); if (rc) { kfree(newsidtab); - goto out; + return rc; } policydb->len = len; @@ -2115,19 +2202,19 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len) if (rc) { kfree(newsidtab); policydb_destroy(policydb); - goto out; + return rc; } rc = policydb_load_isids(policydb, newsidtab); if (rc) { kfree(newsidtab); policydb_destroy(policydb); - goto out; + return rc; } state->ss->sidtab = newsidtab; security_load_policycaps(state); - state->initialized = 1; + selinux_mark_initialized(state); seqno = ++state->ss->latest_granting; selinux_complete_init(); avc_ss_reset(state->avc, seqno); @@ -2135,9 +2222,16 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len) selinux_status_update_policyload(state, seqno); selinux_netlbl_cache_invalidate(); selinux_xfrm_notify_policyload(); - goto out; + return 0; } + oldpolicydb = kcalloc(2, sizeof(*oldpolicydb), GFP_KERNEL); + if (!oldpolicydb) { + kfree(newsidtab); + return -ENOMEM; + } + newpolicydb = oldpolicydb + 1; + rc = policydb_read(newpolicydb, fp); if (rc) { kfree(newsidtab); @@ -2253,14 +2347,12 @@ int security_port_sid(struct selinux_state *state, u8 protocol, u16 port, u32 *out_sid) { struct policydb *policydb; - struct sidtab *sidtab; struct ocontext *c; int rc = 0; read_lock(&state->ss->policy_rwlock); policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; c = policydb->ocontexts[OCON_PORT]; while (c) { @@ -2273,8 +2365,7 @@ int security_port_sid(struct selinux_state *state, if (c) { if (!c->sid[0]) { - rc = sidtab_context_to_sid(sidtab, - &c->context[0], + rc = context_struct_to_sid(state, &c->context[0], &c->sid[0]); if (rc) goto out; @@ -2299,14 +2390,12 @@ int security_ib_pkey_sid(struct selinux_state *state, u64 subnet_prefix, u16 pkey_num, u32 *out_sid) { struct policydb *policydb; - struct sidtab *sidtab; struct ocontext *c; int rc = 0; read_lock(&state->ss->policy_rwlock); policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; c = policydb->ocontexts[OCON_IBPKEY]; while (c) { @@ -2320,7 +2409,7 @@ int security_ib_pkey_sid(struct selinux_state *state, if (c) { if (!c->sid[0]) { - rc = sidtab_context_to_sid(sidtab, + rc = context_struct_to_sid(state, &c->context[0], &c->sid[0]); if (rc) @@ -2345,14 +2434,12 @@ int security_ib_endport_sid(struct selinux_state *state, const char *dev_name, u8 port_num, u32 *out_sid) { struct policydb *policydb; - struct sidtab *sidtab; struct ocontext *c; int rc = 0; read_lock(&state->ss->policy_rwlock); policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; c = policydb->ocontexts[OCON_IBENDPORT]; while (c) { @@ -2367,8 +2454,7 @@ int security_ib_endport_sid(struct selinux_state *state, if (c) { if (!c->sid[0]) { - rc = sidtab_context_to_sid(sidtab, - &c->context[0], + rc = context_struct_to_sid(state, &c->context[0], &c->sid[0]); if (rc) goto out; @@ -2391,14 +2477,12 @@ int security_netif_sid(struct selinux_state *state, char *name, u32 *if_sid) { struct policydb *policydb; - struct sidtab *sidtab; int rc = 0; struct ocontext *c; read_lock(&state->ss->policy_rwlock); policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; c = policydb->ocontexts[OCON_NETIF]; while (c) { @@ -2409,13 +2493,11 @@ int security_netif_sid(struct selinux_state *state, if (c) { if (!c->sid[0] || !c->sid[1]) { - rc = sidtab_context_to_sid(sidtab, - &c->context[0], - &c->sid[0]); + rc = context_struct_to_sid(state, &c->context[0], + &c->sid[0]); if (rc) goto out; - rc = sidtab_context_to_sid(sidtab, - &c->context[1], + rc = context_struct_to_sid(state, &c->context[1], &c->sid[1]); if (rc) goto out; @@ -2456,14 +2538,12 @@ int security_node_sid(struct selinux_state *state, u32 *out_sid) { struct policydb *policydb; - struct sidtab *sidtab; int rc; struct ocontext *c; read_lock(&state->ss->policy_rwlock); policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; switch (domain) { case AF_INET: { @@ -2505,7 +2585,7 @@ int security_node_sid(struct selinux_state *state, if (c) { if (!c->sid[0]) { - rc = sidtab_context_to_sid(sidtab, + rc = context_struct_to_sid(state, &c->context[0], &c->sid[0]); if (rc) @@ -2557,7 +2637,7 @@ int security_get_user_sids(struct selinux_state *state, *sids = NULL; *nel = 0; - if (!state->initialized) + if (!selinux_initialized(state)) goto out; read_lock(&state->ss->policy_rwlock); @@ -2589,12 +2669,17 @@ int security_get_user_sids(struct selinux_state *state, usercon.role = i + 1; ebitmap_for_each_positive_bit(&role->types, tnode, j) { usercon.type = j + 1; + /* + * The same context struct is reused here so the hash + * must be reset. + */ + usercon.hash = 0; if (mls_setup_user_range(policydb, fromcon, user, &usercon)) continue; - rc = sidtab_context_to_sid(sidtab, &usercon, &sid); + rc = context_struct_to_sid(state, &usercon, &sid); if (rc) goto out_unlock; if (mynel < maxnel) { @@ -2665,7 +2750,6 @@ static inline int __security_genfs_sid(struct selinux_state *state, u32 *sid) { struct policydb *policydb = &state->ss->policydb; - struct sidtab *sidtab = state->ss->sidtab; int len; u16 sclass; struct genfs *genfs; @@ -2700,7 +2784,7 @@ static inline int __security_genfs_sid(struct selinux_state *state, goto out; if (!c->sid[0]) { - rc = sidtab_context_to_sid(sidtab, &c->context[0], &c->sid[0]); + rc = context_struct_to_sid(state, &c->context[0], &c->sid[0]); if (rc) goto out; } @@ -2742,7 +2826,6 @@ int security_genfs_sid(struct selinux_state *state, int security_fs_use(struct selinux_state *state, struct super_block *sb) { struct policydb *policydb; - struct sidtab *sidtab; int rc = 0; struct ocontext *c; struct superblock_security_struct *sbsec = sb->s_security; @@ -2751,7 +2834,6 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb) read_lock(&state->ss->policy_rwlock); policydb = &state->ss->policydb; - sidtab = state->ss->sidtab; c = policydb->ocontexts[OCON_FSUSE]; while (c) { @@ -2763,7 +2845,7 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb) if (c) { sbsec->behavior = c->v.behavior; if (!c->sid[0]) { - rc = sidtab_context_to_sid(sidtab, &c->context[0], + rc = context_struct_to_sid(state, &c->context[0], &c->sid[0]); if (rc) goto out; @@ -2791,7 +2873,7 @@ int security_get_bools(struct selinux_state *state, struct policydb *policydb; int i, rc; - if (!state->initialized) { + if (!selinux_initialized(state)) { *len = 0; *names = NULL; *values = NULL; @@ -2966,7 +3048,7 @@ int security_sid_mls_copy(struct selinux_state *state, int rc; rc = 0; - if (!state->initialized || !policydb->mls_enabled) { + if (!selinux_initialized(state) || !policydb->mls_enabled) { *new_sid = sid; goto out; } @@ -3019,8 +3101,7 @@ int security_sid_mls_copy(struct selinux_state *state, goto out_unlock; } } - - rc = sidtab_context_to_sid(sidtab, &newcon, new_sid); + rc = context_struct_to_sid(state, &newcon, new_sid); out_unlock: read_unlock(&state->ss->policy_rwlock); context_destroy(&newcon); @@ -3134,7 +3215,7 @@ int security_get_classes(struct selinux_state *state, struct policydb *policydb = &state->ss->policydb; int rc; - if (!state->initialized) { + if (!selinux_initialized(state)) { *nclasses = 0; *classes = NULL; return 0; @@ -3283,7 +3364,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) *rule = NULL; - if (!state->initialized) + if (!selinux_initialized(state)) return -EOPNOTSUPP; switch (field) { @@ -3582,7 +3663,7 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state, struct context *ctx; struct context ctx_new; - if (!state->initialized) { + if (!selinux_initialized(state)) { *sid = SECSID_NULL; return 0; } @@ -3613,7 +3694,7 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state, if (!mls_context_isvalid(policydb, &ctx_new)) goto out_free; - rc = sidtab_context_to_sid(sidtab, &ctx_new, sid); + rc = context_struct_to_sid(state, &ctx_new, sid); if (rc) goto out_free; @@ -3649,7 +3730,7 @@ int security_netlbl_sid_to_secattr(struct selinux_state *state, int rc; struct context *ctx; - if (!state->initialized) + if (!selinux_initialized(state)) return 0; read_lock(&state->ss->policy_rwlock); @@ -3688,7 +3769,7 @@ int security_read_policy(struct selinux_state *state, int rc; struct policy_file fp; - if (!state->initialized) + if (!selinux_initialized(state)) return -EINVAL; *len = security_policydb_len(state); diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h index 9a36de860368..c5896f39e8f6 100644 --- a/security/selinux/ss/services.h +++ b/security/selinux/ss/services.h @@ -8,7 +8,7 @@ #define _SS_SERVICES_H_ #include "policydb.h" -#include "sidtab.h" +#include "context.h" /* Mapping for a single class */ struct selinux_mapping { @@ -31,7 +31,7 @@ struct selinux_ss { struct selinux_map map; struct page *status_page; struct mutex status_lock; -}; +} __randomize_layout; void services_compute_xperms_drivers(struct extended_perms *xperms, struct avtab_node *node); @@ -39,4 +39,6 @@ void services_compute_xperms_drivers(struct extended_perms *xperms, void services_compute_xperms_decision(struct extended_perms_decision *xpermd, struct avtab_node *node); +int context_add_hash(struct policydb *policydb, struct context *context); + #endif /* _SS_SERVICES_H_ */ diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c index 1f0a6eaa2d6a..f511ffccb131 100644 --- a/security/selinux/ss/sidtab.c +++ b/security/selinux/ss/sidtab.c @@ -9,52 +9,135 @@ */ #include <linux/errno.h> #include <linux/kernel.h> +#include <linux/list.h> +#include <linux/rcupdate.h> #include <linux/slab.h> #include <linux/sched.h> #include <linux/spinlock.h> -#include <linux/atomic.h> +#include <asm/barrier.h> #include "flask.h" #include "security.h" #include "sidtab.h" +struct sidtab_str_cache { + struct rcu_head rcu_member; + struct list_head lru_member; + struct sidtab_entry *parent; + u32 len; + char str[]; +}; + +#define index_to_sid(index) (index + SECINITSID_NUM + 1) +#define sid_to_index(sid) (sid - (SECINITSID_NUM + 1)) + int sidtab_init(struct sidtab *s) { u32 i; memset(s->roots, 0, sizeof(s->roots)); - for (i = 0; i < SIDTAB_RCACHE_SIZE; i++) - atomic_set(&s->rcache[i], -1); - for (i = 0; i < SECINITSID_NUM; i++) s->isids[i].set = 0; - atomic_set(&s->count, 0); - + s->count = 0; s->convert = NULL; + hash_init(s->context_to_sid); spin_lock_init(&s->lock); + +#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 + s->cache_free_slots = CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE; + INIT_LIST_HEAD(&s->cache_lru_list); + spin_lock_init(&s->cache_lock); +#endif + return 0; } +static u32 context_to_sid(struct sidtab *s, struct context *context) +{ + struct sidtab_entry *entry; + u32 sid = 0; + + rcu_read_lock(); + hash_for_each_possible_rcu(s->context_to_sid, entry, list, + context->hash) { + if (context_cmp(&entry->context, context)) { + sid = entry->sid; + break; + } + } + rcu_read_unlock(); + return sid; +} + int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context) { - struct sidtab_isid_entry *entry; + struct sidtab_isid_entry *isid; int rc; if (sid == 0 || sid > SECINITSID_NUM) return -EINVAL; - entry = &s->isids[sid - 1]; + isid = &s->isids[sid - 1]; - rc = context_cpy(&entry->context, context); + rc = context_cpy(&isid->entry.context, context); if (rc) return rc; - entry->set = 1; +#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 + isid->entry.cache = NULL; +#endif + isid->set = 1; + + /* + * Multiple initial sids may map to the same context. Check that this + * context is not already represented in the context_to_sid hashtable + * to avoid duplicate entries and long linked lists upon hash + * collision. + */ + if (!context_to_sid(s, context)) { + isid->entry.sid = sid; + hash_add(s->context_to_sid, &isid->entry.list, context->hash); + } + return 0; } +int sidtab_hash_stats(struct sidtab *sidtab, char *page) +{ + int i; + int chain_len = 0; + int slots_used = 0; + int entries = 0; + int max_chain_len = 0; + int cur_bucket = 0; + struct sidtab_entry *entry; + + rcu_read_lock(); + hash_for_each_rcu(sidtab->context_to_sid, i, entry, list) { + entries++; + if (i == cur_bucket) { + chain_len++; + if (chain_len == 1) + slots_used++; + } else { + cur_bucket = i; + if (chain_len > max_chain_len) + max_chain_len = chain_len; + chain_len = 0; + } + } + rcu_read_unlock(); + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + + return scnprintf(page, PAGE_SIZE, "entries: %d\nbuckets used: %d/%d\n" + "longest chain: %d\n", entries, + slots_used, SIDTAB_HASH_BUCKETS, max_chain_len); +} + static u32 sidtab_level_from_count(u32 count) { u32 capacity = SIDTAB_LEAF_ENTRIES; @@ -88,7 +171,8 @@ static int sidtab_alloc_roots(struct sidtab *s, u32 level) return 0; } -static struct context *sidtab_do_lookup(struct sidtab *s, u32 index, int alloc) +static struct sidtab_entry *sidtab_do_lookup(struct sidtab *s, u32 index, + int alloc) { union sidtab_entry_inner *entry; u32 level, capacity_shift, leaf_index = index / SIDTAB_LEAF_ENTRIES; @@ -125,166 +209,76 @@ static struct context *sidtab_do_lookup(struct sidtab *s, u32 index, int alloc) if (!entry->ptr_leaf) return NULL; } - return &entry->ptr_leaf->entries[index % SIDTAB_LEAF_ENTRIES].context; + return &entry->ptr_leaf->entries[index % SIDTAB_LEAF_ENTRIES]; } -static struct context *sidtab_lookup(struct sidtab *s, u32 index) +static struct sidtab_entry *sidtab_lookup(struct sidtab *s, u32 index) { - u32 count = (u32)atomic_read(&s->count); + /* read entries only after reading count */ + u32 count = smp_load_acquire(&s->count); if (index >= count) return NULL; - /* read entries after reading count */ - smp_rmb(); - return sidtab_do_lookup(s, index, 0); } -static struct context *sidtab_lookup_initial(struct sidtab *s, u32 sid) +static struct sidtab_entry *sidtab_lookup_initial(struct sidtab *s, u32 sid) { - return s->isids[sid - 1].set ? &s->isids[sid - 1].context : NULL; + return s->isids[sid - 1].set ? &s->isids[sid - 1].entry : NULL; } -static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force) +static struct sidtab_entry *sidtab_search_core(struct sidtab *s, u32 sid, + int force) { - struct context *context; - if (sid != 0) { + struct sidtab_entry *entry; + if (sid > SECINITSID_NUM) - context = sidtab_lookup(s, sid - (SECINITSID_NUM + 1)); + entry = sidtab_lookup(s, sid_to_index(sid)); else - context = sidtab_lookup_initial(s, sid); - if (context && (!context->len || force)) - return context; + entry = sidtab_lookup_initial(s, sid); + if (entry && (!entry->context.len || force)) + return entry; } return sidtab_lookup_initial(s, SECINITSID_UNLABELED); } -struct context *sidtab_search(struct sidtab *s, u32 sid) +struct sidtab_entry *sidtab_search_entry(struct sidtab *s, u32 sid) { return sidtab_search_core(s, sid, 0); } -struct context *sidtab_search_force(struct sidtab *s, u32 sid) +struct sidtab_entry *sidtab_search_entry_force(struct sidtab *s, u32 sid) { return sidtab_search_core(s, sid, 1); } -static int sidtab_find_context(union sidtab_entry_inner entry, - u32 *pos, u32 count, u32 level, - struct context *context, u32 *index) -{ - int rc; - u32 i; - - if (level != 0) { - struct sidtab_node_inner *node = entry.ptr_inner; - - i = 0; - while (i < SIDTAB_INNER_ENTRIES && *pos < count) { - rc = sidtab_find_context(node->entries[i], - pos, count, level - 1, - context, index); - if (rc == 0) - return 0; - i++; - } - } else { - struct sidtab_node_leaf *node = entry.ptr_leaf; - - i = 0; - while (i < SIDTAB_LEAF_ENTRIES && *pos < count) { - if (context_cmp(&node->entries[i].context, context)) { - *index = *pos; - return 0; - } - (*pos)++; - i++; - } - } - return -ENOENT; -} - -static void sidtab_rcache_update(struct sidtab *s, u32 index, u32 pos) -{ - while (pos > 0) { - atomic_set(&s->rcache[pos], atomic_read(&s->rcache[pos - 1])); - --pos; - } - atomic_set(&s->rcache[0], (int)index); -} - -static void sidtab_rcache_push(struct sidtab *s, u32 index) -{ - sidtab_rcache_update(s, index, SIDTAB_RCACHE_SIZE - 1); -} - -static int sidtab_rcache_search(struct sidtab *s, struct context *context, - u32 *index) -{ - u32 i; - - for (i = 0; i < SIDTAB_RCACHE_SIZE; i++) { - int v = atomic_read(&s->rcache[i]); - - if (v < 0) - continue; - - if (context_cmp(sidtab_do_lookup(s, (u32)v, 0), context)) { - sidtab_rcache_update(s, (u32)v, i); - *index = (u32)v; - return 0; - } - } - return -ENOENT; -} - -static int sidtab_reverse_lookup(struct sidtab *s, struct context *context, - u32 *index) +int sidtab_context_to_sid(struct sidtab *s, struct context *context, + u32 *sid) { unsigned long flags; - u32 count = (u32)atomic_read(&s->count); - u32 count_locked, level, pos; + u32 count; struct sidtab_convert_params *convert; - struct context *dst, *dst_convert; + struct sidtab_entry *dst, *dst_convert; int rc; - rc = sidtab_rcache_search(s, context, index); - if (rc == 0) + *sid = context_to_sid(s, context); + if (*sid) return 0; - level = sidtab_level_from_count(count); - - /* read entries after reading count */ - smp_rmb(); - - pos = 0; - rc = sidtab_find_context(s->roots[level], &pos, count, level, - context, index); - if (rc == 0) { - sidtab_rcache_push(s, *index); - return 0; - } - /* lock-free search failed: lock, re-search, and insert if not found */ spin_lock_irqsave(&s->lock, flags); + rc = 0; + *sid = context_to_sid(s, context); + if (*sid) + goto out_unlock; + + /* read entries only after reading count */ + count = smp_load_acquire(&s->count); convert = s->convert; - count_locked = (u32)atomic_read(&s->count); - level = sidtab_level_from_count(count_locked); - - /* if count has changed before we acquired the lock, then catch up */ - while (count < count_locked) { - if (context_cmp(sidtab_do_lookup(s, count, 0), context)) { - sidtab_rcache_push(s, count); - *index = count; - rc = 0; - goto out_unlock; - } - ++count; - } /* bail out if we already reached max entries */ rc = -EOVERFLOW; @@ -297,7 +291,9 @@ static int sidtab_reverse_lookup(struct sidtab *s, struct context *context, if (!dst) goto out_unlock; - rc = context_cpy(dst, context); + dst->sid = index_to_sid(count); + + rc = context_cpy(&dst->context, context); if (rc) goto out_unlock; @@ -309,31 +305,32 @@ static int sidtab_reverse_lookup(struct sidtab *s, struct context *context, rc = -ENOMEM; dst_convert = sidtab_do_lookup(convert->target, count, 1); if (!dst_convert) { - context_destroy(dst); + context_destroy(&dst->context); goto out_unlock; } - rc = convert->func(context, dst_convert, convert->args); + rc = convert->func(context, &dst_convert->context, + convert->args); if (rc) { - context_destroy(dst); + context_destroy(&dst->context); goto out_unlock; } + dst_convert->sid = index_to_sid(count); + convert->target->count = count + 1; - /* at this point we know the insert won't fail */ - atomic_set(&convert->target->count, count + 1); + hash_add_rcu(convert->target->context_to_sid, + &dst_convert->list, dst_convert->context.hash); } if (context->len) pr_info("SELinux: Context %s is not valid (left unmapped).\n", context->str); - sidtab_rcache_push(s, count); - *index = count; - - /* write entries before writing new count */ - smp_wmb(); + *sid = index_to_sid(count); - atomic_set(&s->count, count + 1); + /* write entries before updating count */ + smp_store_release(&s->count, count + 1); + hash_add_rcu(s->context_to_sid, &dst->list, dst->context.hash); rc = 0; out_unlock: @@ -341,25 +338,19 @@ out_unlock: return rc; } -int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid) +static void sidtab_convert_hashtable(struct sidtab *s, u32 count) { - int rc; + struct sidtab_entry *entry; u32 i; - for (i = 0; i < SECINITSID_NUM; i++) { - struct sidtab_isid_entry *entry = &s->isids[i]; + for (i = 0; i < count; i++) { + entry = sidtab_do_lookup(s, i, 0); + entry->sid = index_to_sid(i); - if (entry->set && context_cmp(context, &entry->context)) { - *sid = i + 1; - return 0; - } - } + hash_add_rcu(s->context_to_sid, &entry->list, + entry->context.hash); - rc = sidtab_reverse_lookup(s, context, sid); - if (rc) - return rc; - *sid += SECINITSID_NUM + 1; - return 0; + } } static int sidtab_convert_tree(union sidtab_entry_inner *edst, @@ -423,7 +414,7 @@ int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params) return -EBUSY; } - count = (u32)atomic_read(&s->count); + count = s->count; level = sidtab_level_from_count(count); /* allocate last leaf in the new sidtab (to avoid race with @@ -436,12 +427,12 @@ int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params) } /* set count in case no new entries are added during conversion */ - atomic_set(¶ms->target->count, count); + params->target->count = count; /* enable live convert of new entries */ s->convert = params; - /* we can safely do the rest of the conversion outside the lock */ + /* we can safely convert the tree outside the lock */ spin_unlock_irqrestore(&s->lock, flags); pr_info("SELinux: Converting %u SID table entries...\n", count); @@ -455,8 +446,25 @@ int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params) spin_lock_irqsave(&s->lock, flags); s->convert = NULL; spin_unlock_irqrestore(&s->lock, flags); + return rc; } - return rc; + /* + * The hashtable can also be modified in sidtab_context_to_sid() + * so we must re-acquire the lock here. + */ + spin_lock_irqsave(&s->lock, flags); + sidtab_convert_hashtable(params->target, count); + spin_unlock_irqrestore(&s->lock, flags); + + return 0; +} + +static void sidtab_destroy_entry(struct sidtab_entry *entry) +{ + context_destroy(&entry->context); +#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 + kfree(rcu_dereference_raw(entry->cache)); +#endif } static void sidtab_destroy_tree(union sidtab_entry_inner entry, u32 level) @@ -479,7 +487,7 @@ static void sidtab_destroy_tree(union sidtab_entry_inner entry, u32 level) return; for (i = 0; i < SIDTAB_LEAF_ENTRIES; i++) - context_destroy(&node->entries[i].context); + sidtab_destroy_entry(&node->entries[i]); kfree(node); } } @@ -490,11 +498,95 @@ void sidtab_destroy(struct sidtab *s) for (i = 0; i < SECINITSID_NUM; i++) if (s->isids[i].set) - context_destroy(&s->isids[i].context); + sidtab_destroy_entry(&s->isids[i].entry); level = SIDTAB_MAX_LEVEL; while (level && !s->roots[level].ptr_inner) --level; sidtab_destroy_tree(s->roots[level], level); + /* + * The context_to_sid hashtable's objects are all shared + * with the isids array and context tree, and so don't need + * to be cleaned up here. + */ } + +#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 + +void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry, + const char *str, u32 str_len) +{ + struct sidtab_str_cache *cache, *victim = NULL; + unsigned long flags; + + /* do not cache invalid contexts */ + if (entry->context.len) + return; + + spin_lock_irqsave(&s->cache_lock, flags); + + cache = rcu_dereference_protected(entry->cache, + lockdep_is_held(&s->cache_lock)); + if (cache) { + /* entry in cache - just bump to the head of LRU list */ + list_move(&cache->lru_member, &s->cache_lru_list); + goto out_unlock; + } + + cache = kmalloc(sizeof(struct sidtab_str_cache) + str_len, GFP_ATOMIC); + if (!cache) + goto out_unlock; + + if (s->cache_free_slots == 0) { + /* pop a cache entry from the tail and free it */ + victim = container_of(s->cache_lru_list.prev, + struct sidtab_str_cache, lru_member); + list_del(&victim->lru_member); + rcu_assign_pointer(victim->parent->cache, NULL); + } else { + s->cache_free_slots--; + } + cache->parent = entry; + cache->len = str_len; + memcpy(cache->str, str, str_len); + list_add(&cache->lru_member, &s->cache_lru_list); + + rcu_assign_pointer(entry->cache, cache); + +out_unlock: + spin_unlock_irqrestore(&s->cache_lock, flags); + kfree_rcu(victim, rcu_member); +} + +int sidtab_sid2str_get(struct sidtab *s, struct sidtab_entry *entry, + char **out, u32 *out_len) +{ + struct sidtab_str_cache *cache; + int rc = 0; + + if (entry->context.len) + return -ENOENT; /* do not cache invalid contexts */ + + rcu_read_lock(); + + cache = rcu_dereference(entry->cache); + if (!cache) { + rc = -ENOENT; + } else { + *out_len = cache->len; + if (out) { + *out = kmemdup(cache->str, cache->len, GFP_ATOMIC); + if (!*out) + rc = -ENOMEM; + } + } + + rcu_read_unlock(); + + if (!rc && out) + sidtab_sid2str_put(s, entry, *out, *out_len); + return rc; +} + +#endif /* CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 */ diff --git a/security/selinux/ss/sidtab.h b/security/selinux/ss/sidtab.h index bbd5c0d1f3bd..3311d9f236c0 100644 --- a/security/selinux/ss/sidtab.h +++ b/security/selinux/ss/sidtab.h @@ -13,16 +13,19 @@ #include <linux/spinlock_types.h> #include <linux/log2.h> +#include <linux/hashtable.h> #include "context.h" -struct sidtab_entry_leaf { +struct sidtab_entry { + u32 sid; struct context context; +#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 + struct sidtab_str_cache __rcu *cache; +#endif + struct hlist_node list; }; -struct sidtab_node_inner; -struct sidtab_node_leaf; - union sidtab_entry_inner { struct sidtab_node_inner *ptr_inner; struct sidtab_node_leaf *ptr_leaf; @@ -38,17 +41,17 @@ union sidtab_entry_inner { (SIDTAB_NODE_ALLOC_SHIFT - size_to_shift(sizeof(union sidtab_entry_inner))) #define SIDTAB_INNER_ENTRIES ((size_t)1 << SIDTAB_INNER_SHIFT) #define SIDTAB_LEAF_ENTRIES \ - (SIDTAB_NODE_ALLOC_SIZE / sizeof(struct sidtab_entry_leaf)) + (SIDTAB_NODE_ALLOC_SIZE / sizeof(struct sidtab_entry)) -#define SIDTAB_MAX_BITS 31 /* limited to INT_MAX due to atomic_t range */ -#define SIDTAB_MAX (((u32)1 << SIDTAB_MAX_BITS) - 1) +#define SIDTAB_MAX_BITS 32 +#define SIDTAB_MAX U32_MAX /* ensure enough tree levels for SIDTAB_MAX entries */ #define SIDTAB_MAX_LEVEL \ DIV_ROUND_UP(SIDTAB_MAX_BITS - size_to_shift(SIDTAB_LEAF_ENTRIES), \ SIDTAB_INNER_SHIFT) struct sidtab_node_leaf { - struct sidtab_entry_leaf entries[SIDTAB_LEAF_ENTRIES]; + struct sidtab_entry entries[SIDTAB_LEAF_ENTRIES]; }; struct sidtab_node_inner { @@ -57,7 +60,7 @@ struct sidtab_node_inner { struct sidtab_isid_entry { int set; - struct context context; + struct sidtab_entry entry; }; struct sidtab_convert_params { @@ -66,25 +69,56 @@ struct sidtab_convert_params { struct sidtab *target; }; -#define SIDTAB_RCACHE_SIZE 3 +#define SIDTAB_HASH_BITS CONFIG_SECURITY_SELINUX_SIDTAB_HASH_BITS +#define SIDTAB_HASH_BUCKETS (1 << SIDTAB_HASH_BITS) struct sidtab { + /* + * lock-free read access only for as many items as a prior read of + * 'count' + */ union sidtab_entry_inner roots[SIDTAB_MAX_LEVEL + 1]; - atomic_t count; + /* + * access atomically via {READ|WRITE}_ONCE(); only increment under + * spinlock + */ + u32 count; + /* access only under spinlock */ struct sidtab_convert_params *convert; spinlock_t lock; - /* reverse lookup cache */ - atomic_t rcache[SIDTAB_RCACHE_SIZE]; +#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 + /* SID -> context string cache */ + u32 cache_free_slots; + struct list_head cache_lru_list; + spinlock_t cache_lock; +#endif /* index == SID - 1 (no entry for SECSID_NULL) */ struct sidtab_isid_entry isids[SECINITSID_NUM]; + + /* Hash table for fast reverse context-to-sid lookups. */ + DECLARE_HASHTABLE(context_to_sid, SIDTAB_HASH_BITS); }; int sidtab_init(struct sidtab *s); int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context); -struct context *sidtab_search(struct sidtab *s, u32 sid); -struct context *sidtab_search_force(struct sidtab *s, u32 sid); +struct sidtab_entry *sidtab_search_entry(struct sidtab *s, u32 sid); +struct sidtab_entry *sidtab_search_entry_force(struct sidtab *s, u32 sid); + +static inline struct context *sidtab_search(struct sidtab *s, u32 sid) +{ + struct sidtab_entry *entry = sidtab_search_entry(s, sid); + + return entry ? &entry->context : NULL; +} + +static inline struct context *sidtab_search_force(struct sidtab *s, u32 sid) +{ + struct sidtab_entry *entry = sidtab_search_entry_force(s, sid); + + return entry ? &entry->context : NULL; +} int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params); @@ -92,6 +126,27 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid); void sidtab_destroy(struct sidtab *s); +int sidtab_hash_stats(struct sidtab *sidtab, char *page); + +#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 +void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry, + const char *str, u32 str_len); +int sidtab_sid2str_get(struct sidtab *s, struct sidtab_entry *entry, + char **out, u32 *out_len); +#else +static inline void sidtab_sid2str_put(struct sidtab *s, + struct sidtab_entry *entry, + const char *str, u32 str_len) +{ +} +static inline int sidtab_sid2str_get(struct sidtab *s, + struct sidtab_entry *entry, + char **out, u32 *out_len) +{ + return -ENOENT; +} +#endif /* CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 */ + #endif /* _SS_SIDTAB_H_ */ |