On Mon, 2008-05-05 at 11:43 -0400, Stephen Smalley wrote: > Introduce SELinux support for deferred mapping of security contexts in > the SID table upon policy reload, and use this support for inode > security contexts when the context is not yet valid under the current > policy. Only processes with CAP_MAC_ADMIN + mac_admin permission in > policy can set undefined security contexts on inodes. Inodes with > such undefined contexts are treated as having the unlabeled context > until the context becomes valid upon a policy reload that defines the > context. Context invalidation upon policy reload also uses this > support to save the context information in the SID table and later > recover it upon a subsequent policy reload that defines the context > again. BTW, I assume that this patch will bake in -mm for a while and only go in for 2.6.27, since 2.6.26 merge window is now closed and this is definitely not a bug fix or minor cleanup. > This support is to enable package managers and similar programs to set > down file contexts unknown to the system policy at the time the file > is created in order to better support placing loadable policy modules > in packages and to support build systems that need to create images of > different distro releases with different policies w/o requiring all of > the contexts to be defined or legal in the build host policy. > > With this patch applied, the following sequence is possible, although > in practice it is recommended that this permission only be allowed to > specific program domains such as the package manager. > > # cat setundefined.te > policy_module(setundefined, 1.0) > require { > type unconfined_t; > type unlabeled_t; > } > files_type(unlabeled_t) > allow unconfined_t self:capability2 mac_admin; > # make -f /usr/share/selinux/devel/Makefile setundefined.pp > # semodule -i setundefined.pp > # touch bar > # chcon -t foo_exec_t bar # foo_exec_t is not yet defined > # ls -Z bar > -rw-r--r-- root root system_u:object_r:unlabeled_t bar > # cat foo.te > policy_module(foo, 1.0) > type foo_exec_t; > files_type(foo_exec_t) > # make -f /usr/share/selinux/devel/Makefile foo.pp > # semodule -i foo.pp # defines foo_exec_t > # ls -Z bar > -rw-r--r-- root root user_u:object_r:foo_exec_t bar > # semodule -r foo > # ls -Z bar > -rw-r--r-- root root system_u:object_r:unlabeled_t bar > # semodule -i foo.pp > # ls -Z bar > -rw-r--r-- root root user_u:object_r:foo_exec_t bar > > Signed-off-by: Stephen Smalley <sds@xxxxxxxxxxxxx> > > --- > > security/selinux/hooks.c | 18 ++- > security/selinux/include/security.h | 2 > security/selinux/ss/context.h | 27 ++++ > security/selinux/ss/mls.c | 11 +- > security/selinux/ss/mls.h | 3 > security/selinux/ss/services.c | 198 +++++++++++++++++++++++++----------- > security/selinux/ss/sidtab.c | 6 - > 7 files changed, 194 insertions(+), 71 deletions(-) > > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c > index 308e2cf..530aa00 100644 > --- a/security/selinux/hooks.c > +++ b/security/selinux/hooks.c > @@ -2667,6 +2667,11 @@ static int selinux_inode_setxattr(struct dentry *dentry, char *name, void *value > return rc; > > rc = security_context_to_sid(value, size, &newsid); > + if (rc == -EINVAL) { > + if (!capable(CAP_MAC_ADMIN)) > + return rc; > + rc = security_context_to_sid_force(value, size, &newsid); > + } > if (rc) > return rc; > > @@ -2700,10 +2705,11 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, char *name, > return; > } > > - rc = security_context_to_sid(value, size, &newsid); > + rc = security_context_to_sid_force(value, size, &newsid); > if (rc) { > - printk(KERN_WARNING "%s: unable to obtain SID for context " > - "%s, rc=%d\n", __func__, (char *)value, -rc); > + printk(KERN_ERR "SELinux: unable to map context to SID" > + "for (%s, %lu), rc=%d\n", > + inode->i_sb->s_id, inode->i_ino, -rc); > return; > } > > @@ -5152,6 +5158,12 @@ static int selinux_setprocattr(struct task_struct *p, > size--; > } > error = security_context_to_sid(value, size, &sid); > + if (error == -EINVAL && !strcmp(name, "fscreate")) { > + if (!capable(CAP_MAC_ADMIN)) > + return error; > + error = security_context_to_sid_force(value, size, > + &sid); > + } > if (error) > return error; > } > diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h > index 1904c46..112d4b1 100644 > --- a/security/selinux/include/security.h > +++ b/security/selinux/include/security.h > @@ -99,6 +99,8 @@ int security_context_to_sid(char *scontext, u32 scontext_len, > int security_context_to_sid_default(char *scontext, u32 scontext_len, > u32 *out_sid, u32 def_sid, gfp_t gfp_flags); > > +int security_context_to_sid_force(char *scontext, u32 scontext_len, u32 *sid); > + > int security_get_user_sids(u32 callsid, char *username, > u32 **sids, u32 *nel); > > diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h > index 2eee0da..776d90f 100644 > --- a/security/selinux/ss/context.h > +++ b/security/selinux/ss/context.h > @@ -28,6 +28,8 @@ struct context { > u32 role; > u32 type; > struct mls_range range; > + char *str; /* string representation if context cannot be mapped. */ > + u32 len; /* length of string in bytes */ > }; > > static inline void mls_context_init(struct context *c) > @@ -106,20 +108,43 @@ static inline void context_init(struct context *c) > > static inline int context_cpy(struct context *dst, struct context *src) > { > + int rc; > + > dst->user = src->user; > dst->role = src->role; > dst->type = src->type; > - return mls_context_cpy(dst, src); > + if (src->str) { > + dst->str = kstrdup(src->str, GFP_ATOMIC); > + if (!dst->str) > + return -ENOMEM; > + dst->len = src->len; > + } else { > + dst->str = NULL; > + dst->len = 0; > + } > + rc = mls_context_cpy(dst, src); > + if (rc) { > + kfree(dst->str); > + return rc; > + } > + return 0; > } > > static inline void context_destroy(struct context *c) > { > c->user = c->role = c->type = 0; > + kfree(c->str); > + c->str = NULL; > + c->len = 0; > mls_context_destroy(c); > } > > static inline int context_cmp(struct context *c1, struct context *c2) > { > + if (c1->len && c2->len) > + return (c1->len == c2->len && !strcmp(c1->str, c2->str)); > + if (c1->len || c2->len) > + return 0; > return ((c1->user == c2->user) && > (c1->role == c2->role) && > (c1->type == c2->type) && > diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c > index 8b1706b..a6ca058 100644 > --- a/security/selinux/ss/mls.c > +++ b/security/selinux/ss/mls.c > @@ -239,7 +239,8 @@ int mls_context_isvalid(struct policydb *p, struct context *c) > * Policy read-lock must be held for sidtab lookup. > * > */ > -int mls_context_to_sid(char oldc, > +int mls_context_to_sid(struct policydb *pol, > + char oldc, > char **scontext, > struct context *context, > struct sidtab *s, > @@ -286,7 +287,7 @@ int mls_context_to_sid(char oldc, > *p++ = 0; > > for (l = 0; l < 2; l++) { > - levdatum = hashtab_search(policydb.p_levels.table, scontextp); > + levdatum = hashtab_search(pol->p_levels.table, scontextp); > if (!levdatum) { > rc = -EINVAL; > goto out; > @@ -311,7 +312,7 @@ int mls_context_to_sid(char oldc, > *rngptr++ = 0; > } > > - catdatum = hashtab_search(policydb.p_cats.table, > + catdatum = hashtab_search(pol->p_cats.table, > scontextp); > if (!catdatum) { > rc = -EINVAL; > @@ -327,7 +328,7 @@ int mls_context_to_sid(char oldc, > if (rngptr) { > int i; > > - rngdatum = hashtab_search(policydb.p_cats.table, rngptr); > + rngdatum = hashtab_search(pol->p_cats.table, rngptr); > if (!rngdatum) { > rc = -EINVAL; > goto out; > @@ -395,7 +396,7 @@ int mls_from_string(char *str, struct context *context, gfp_t gfp_mask) > if (!tmpstr) { > rc = -ENOMEM; > } else { > - rc = mls_context_to_sid(':', &tmpstr, context, > + rc = mls_context_to_sid(&policydb, ':', &tmpstr, context, > NULL, SECSID_NULL); > kfree(freestr); > } > diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h > index ab53663..900fb0e 100644 > --- a/security/selinux/ss/mls.h > +++ b/security/selinux/ss/mls.h > @@ -30,7 +30,8 @@ int mls_context_isvalid(struct policydb *p, struct context *c); > int mls_range_isvalid(struct policydb *p, struct mls_range *r); > int mls_level_isvalid(struct policydb *p, struct mls_level *l); > > -int mls_context_to_sid(char oldc, > +int mls_context_to_sid(struct policydb *p, > + char oldc, > char **scontext, > struct context *context, > struct sidtab *s, > diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c > index 2daaddb..6724b4f 100644 > --- a/security/selinux/ss/services.c > +++ b/security/selinux/ss/services.c > @@ -708,36 +708,24 @@ out: > > } > > -static int security_context_to_sid_core(char *scontext, u32 scontext_len, > - u32 *sid, u32 def_sid, gfp_t gfp_flags) > +static int string_to_context_struct(struct policydb *pol, > + struct sidtab *sidtabp, > + char *scontext, > + u32 scontext_len, > + struct context *ctx, > + u32 def_sid, > + gfp_t gfp_flags) > { > - char *scontext2; > - struct context context; > + char *scontext2 = NULL; > struct role_datum *role; > struct type_datum *typdatum; > struct user_datum *usrdatum; > char *scontextp, *p, oldc; > int rc = 0; > > - if (!ss_initialized) { > - int i; > + context_init(ctx); > > - for (i = 1; i < SECINITSID_NUM; i++) { > - if (!strcmp(initial_sid_to_string[i], scontext)) { > - *sid = i; > - goto out; > - } > - } > - *sid = SECINITSID_KERNEL; > - goto out; > - } > - *sid = SECSID_NULL; > - > - /* Copy the string so that we can modify the copy as we parse it. > - The string should already by null terminated, but we append a > - null suffix to the copy to avoid problems with the existing > - attr package, which doesn't view the null terminator as part > - of the attribute value. */ > + /* Copy the string so that we can modify the copy as we parse it. */ > scontext2 = kmalloc(scontext_len+1, gfp_flags); > if (!scontext2) { > rc = -ENOMEM; > @@ -746,11 +734,6 @@ static int security_context_to_sid_core(char *scontext, u32 scontext_len, > memcpy(scontext2, scontext, scontext_len); > scontext2[scontext_len] = 0; > > - context_init(&context); > - *sid = SECSID_NULL; > - > - POLICY_RDLOCK; > - > /* Parse the security context. */ > > rc = -EINVAL; > @@ -762,15 +745,15 @@ static int security_context_to_sid_core(char *scontext, u32 scontext_len, > p++; > > if (*p == 0) > - goto out_unlock; > + goto out; > > *p++ = 0; > > - usrdatum = hashtab_search(policydb.p_users.table, scontextp); > + usrdatum = hashtab_search(pol->p_users.table, scontextp); > if (!usrdatum) > - goto out_unlock; > + goto out; > > - context.user = usrdatum->value; > + ctx->user = usrdatum->value; > > /* Extract role. */ > scontextp = p; > @@ -778,14 +761,14 @@ static int security_context_to_sid_core(char *scontext, u32 scontext_len, > p++; > > if (*p == 0) > - goto out_unlock; > + goto out; > > *p++ = 0; > > - role = hashtab_search(policydb.p_roles.table, scontextp); > + role = hashtab_search(pol->p_roles.table, scontextp); > if (!role) > - goto out_unlock; > - context.role = role->value; > + goto out; > + ctx->role = role->value; > > /* Extract type. */ > scontextp = p; > @@ -794,33 +777,74 @@ static int security_context_to_sid_core(char *scontext, u32 scontext_len, > oldc = *p; > *p++ = 0; > > - typdatum = hashtab_search(policydb.p_types.table, scontextp); > + typdatum = hashtab_search(pol->p_types.table, scontextp); > if (!typdatum) > - goto out_unlock; > + goto out; > > - context.type = typdatum->value; > + ctx->type = typdatum->value; > > - rc = mls_context_to_sid(oldc, &p, &context, &sidtab, def_sid); > + rc = mls_context_to_sid(pol, oldc, &p, ctx, sidtabp, def_sid); > if (rc) > - goto out_unlock; > + goto out; > > if ((p - scontext2) < scontext_len) { > rc = -EINVAL; > - goto out_unlock; > + goto out; > } > > /* Check the validity of the new context. */ > - if (!policydb_context_isvalid(&policydb, &context)) { > + if (!policydb_context_isvalid(pol, ctx)) { > rc = -EINVAL; > - goto out_unlock; > + context_destroy(ctx); > + goto out; > } > - /* Obtain the new sid. */ > - rc = sidtab_context_to_sid(&sidtab, &context, sid); > -out_unlock: > - POLICY_RDUNLOCK; > - context_destroy(&context); > + rc = 0; > +out: > kfree(scontext2); > + return rc; > +} > + > +static int security_context_to_sid_core(char *scontext, u32 scontext_len, > + u32 *sid, u32 def_sid, gfp_t gfp_flags, > + int force) > +{ > + struct context context; > + int rc = 0; > + > + if (!ss_initialized) { > + int i; > + > + for (i = 1; i < SECINITSID_NUM; i++) { > + if (!strcmp(initial_sid_to_string[i], scontext)) { > + *sid = i; > + goto out; > + } > + } > + *sid = SECINITSID_KERNEL; > + goto out; > + } > + *sid = SECSID_NULL; > + > + POLICY_RDLOCK; > + rc = string_to_context_struct(&policydb, &sidtab, > + scontext, scontext_len, > + &context, def_sid, gfp_flags); > + if (rc == -EINVAL && force) { > + context.str = kmalloc(scontext_len+1, gfp_flags); > + if (!context.str) { > + rc = -ENOMEM; > + goto out; > + } > + memcpy(context.str, scontext, scontext_len); > + context.str[scontext_len] = 0; > + context.len = scontext_len; > + } else if (rc) > + goto out; > + rc = sidtab_context_to_sid(&sidtab, &context, sid); > + if (rc) > + context_destroy(&context); > out: > + POLICY_RDUNLOCK; > return rc; > } > > @@ -838,7 +862,7 @@ out: > int security_context_to_sid(char *scontext, u32 scontext_len, u32 *sid) > { > return security_context_to_sid_core(scontext, scontext_len, > - sid, SECSID_NULL, GFP_KERNEL); > + sid, SECSID_NULL, GFP_KERNEL, 0); > } > > /** > @@ -855,6 +879,7 @@ int security_context_to_sid(char *scontext, u32 scontext_len, u32 *sid) > * The default SID is passed to the MLS layer to be used to allow > * kernel labeling of the MLS field if the MLS field is not present > * (for upgrading to MLS without full relabel). > + * Implicitly forces adding of the context even if it cannot be mapped yet. > * Returns -%EINVAL if the context is invalid, -%ENOMEM if insufficient > * memory is available, or 0 on success. > */ > @@ -862,7 +887,13 @@ int security_context_to_sid_default(char *scontext, u32 scontext_len, u32 *sid, > u32 def_sid, gfp_t gfp_flags) > { > return security_context_to_sid_core(scontext, scontext_len, > - sid, def_sid, gfp_flags); > + sid, def_sid, gfp_flags, 1); > +} > + > +int security_context_to_sid_force(char *scontext, u32 scontext_len, u32 *sid) > +{ > + return security_context_to_sid_core(scontext, scontext_len, > + sid, SECSID_NULL, GFP_KERNEL, 1); > } > > static int compute_sid_handle_invalid_context( > @@ -1246,9 +1277,12 @@ static inline int convert_context_handle_invalid_context(struct context *context > char *s; > u32 len; > > - context_struct_to_string(context, &s, &len); > - printk(KERN_ERR "SELinux: context %s is invalid\n", s); > - kfree(s); > + if (!context_struct_to_string(context, &s, &len)) { > + printk(KERN_WARNING > + "SELinux: Context %s would be invalid if enforcing\n", > + s); > + kfree(s); > + } > } > return rc; > } > @@ -1280,6 +1314,32 @@ static int convert_context(u32 key, > > args = p; > > + if (c->str) { > + struct context ctx; > + rc = string_to_context_struct(args->newp, NULL, c->str, > + c->len, &ctx, SECSID_NULL, > + GFP_KERNEL); > + if (!rc) { > + printk(KERN_INFO > + "SELinux: Context %s became valid (mapped).\n", > + c->str); > + /* Replace string with mapped representation. */ > + kfree(c->str); > + memcpy(c, &ctx, sizeof(*c)); > + goto out; > + } else if (rc == -EINVAL) { > + /* Retain string representation for later mapping. */ > + rc = 0; > + goto out; > + } else { > + /* Other error condition, e.g. ENOMEM. */ > + printk(KERN_ERR > + "SELinux: Unable to map context %s, rc = %d.\n", > + c->str, -rc); > + goto out; > + } > + } > + > rc = context_cpy(&oldc, c); > if (rc) > goto out; > @@ -1319,13 +1379,21 @@ static int convert_context(u32 key, > } > > context_destroy(&oldc); > + rc = 0; > out: > return rc; > bad: > - context_struct_to_string(&oldc, &s, &len); > + /* Map old representation to string and save it. */ > + if (context_struct_to_string(&oldc, &s, &len)) > + return -ENOMEM; > context_destroy(&oldc); > - printk(KERN_ERR "SELinux: invalidating context %s\n", s); > - kfree(s); > + context_destroy(c); > + c->str = s; > + c->len = len; > + printk(KERN_INFO > + "SELinux: Context %s became invalid (unmapped).\n", > + c->str); > + rc = 0; > goto out; > } > > @@ -1406,7 +1474,11 @@ int security_load_policy(void *data, size_t len) > return -EINVAL; > } > > - sidtab_init(&newsidtab); > + if (sidtab_init(&newsidtab)) { > + LOAD_UNLOCK; > + policydb_destroy(&newpolicydb); > + return -ENOMEM; > + } > > /* Verify that the kernel defined classes are correct. */ > if (validate_classes(&newpolicydb)) { > @@ -1429,11 +1501,15 @@ int security_load_policy(void *data, size_t len) > goto err; > } > > - /* Convert the internal representations of contexts > - in the new SID table and remove invalid SIDs. */ > + /* > + * Convert the internal representations of contexts > + * in the new SID table. > + */ > args.oldp = &policydb; > args.newp = &newpolicydb; > - sidtab_map_remove_on_error(&newsidtab, convert_context, &args); > + rc = sidtab_map(&newsidtab, convert_context, &args); > + if (rc) > + goto err; > > /* Save the old policydb and SID table to free later. */ > memcpy(&oldpolicydb, &policydb, sizeof policydb); > @@ -1673,6 +1749,8 @@ int security_get_user_sids(u32 fromsid, > > POLICY_RDLOCK; > > + context_init(&usercon); > + > fromcon = sidtab_search(&sidtab, fromsid); > if (!fromcon) { > rc = -EINVAL; > diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c > index 4a516ff..7f4c0d0 100644 > --- a/security/selinux/ss/sidtab.c > +++ b/security/selinux/ss/sidtab.c > @@ -99,7 +99,7 @@ struct context *sidtab_search(struct sidtab *s, u32 sid) > while (cur != NULL && sid > cur->sid) > cur = cur->next; > > - if (cur == NULL || sid != cur->sid) { > + if (cur == NULL || sid != cur->sid || cur->context.len) { > /* Remap invalid SIDs to the unlabeled SID. */ > sid = SECINITSID_UNLABELED; > hvalue = SIDTAB_HASH(sid); > @@ -215,6 +215,10 @@ int sidtab_context_to_sid(struct sidtab *s, > goto unlock_out; > } > sid = s->next_sid++; > + if (context->len) > + printk(KERN_INFO > + "SELinux: Context %s is not valid (left unmapped).\n", > + context->str); > ret = sidtab_insert(s, sid, context); > if (ret) > s->next_sid--; > -- Stephen Smalley National Security Agency -- This message was distributed to subscribers of the selinux mailing list. If you no longer wish to subscribe, send mail to majordomo@xxxxxxxxxxxxx with the words "unsubscribe selinux" without quotes as the message.