On Mon, 2017-07-10 at 16:25 -0400, Stephen Smalley wrote: > As systemd ramps up enabling NoNewPrivileges (either explicitly in > service unit files or as a side effect of other security-related > settings in service unit files), we're increasingly running afoul of > its interactions with SELinux. > > The end result is bad for the security of both SELinux-disabled and > SELinux-enabled systems. Packagers have to turn off these > options in the unit files to preserve SELinux domain > transitions. For > users who choose to disable SELinux, this means that they miss out on > at least having the systemd-supported protections. For users who > keep > SELinux enabled, they may still be missing out on some protections > because it isn't necessarily guaranteed that the SELinux policy for > that service provides the same protections in all cases. > > Our options seem to be: > > 1) Just keep on the way we are now, i.e. packagers have to remove > default protection settings from upstream package unit files in order > to have them work with SELinux (and not just NoNewPrivileges= > itself; increasingly systemd is enabling NNP as a side effect of > other > unit file options, even seemingly unrelated ones like > PrivateDevices). > SELinux-disabled users lose entirely, SELinux-enabled users may lose > (depending on whether SELinux policy provides equivalent or > better guarantees). > > 2) Change systemd to automatically disable NNP on SELinux-enabled > systems. Unit files can be left unmodified from upstream. SELinux- > disabled users win. SELinux-enabled users may lose. > > 3) Try to use typebounds, since we allow bounded transitions under > NNP. > Unit files can be left unmodified from upstream. SELinux-disabled > users > win. SELinux-enabled users get to benefit from systemd-provided > protections. However, this option is impractical to implement in > policy > currently, since typebounds requires us to ensure that each domain is > allowed everything all of its descendant domains are allowed, and > this > has to be repeated for the entire chain of domain transitions. There > is > no way to clone all allow rules from children to the parent in policy > currently, and it is doubtful that doing so would be desirable even > if > it were practical, as it requires leaking permissions to objects and > operations into parent domains that could weaken their own security > in > order to allow them to the children (e.g. if a child requires execmem > permission, then so does the parent; if a child requires read to a > symbolic > link or temporary file that it can write, then so does the parent, > ...). > > 4) Decouple NNP from SELinux transitions, so that we don't have to > make a choice between them. Introduce a new policy capability that > causes the ability to transition under NNP to be based on a new > permission > check between the old and new contexts rather than > typebounds. Domain > transitions can then be allowed in policy without requiring the > parent > to be a strict superset of all of its children. The rationale for > this > divergence from NNP behavior for capabilities is that SELinux > permissions > are substantially broader than just capabilities (they express a full > access matrix, not just privileges) and can only be used to further > restrict capabilities, not grant them beyond what is already > permitted. > Unit files can be left unmodified from upstream. SELinux-disabled > users > win. SELinux-enabled users can benefit from systemd-provided > protections > and policy won't need to radically change. > > This change takes the last approach above, as it seems to be the > best option. > > Signed-off-by: Stephen Smalley <sds@xxxxxxxxxxxxx> > --- > security/selinux/hooks.c | 41 ++++++++++++++++++++++++--- > ---------- > security/selinux/include/classmap.h | 2 +- > security/selinux/include/security.h | 2 ++ > security/selinux/ss/services.c | 7 ++++++- > 4 files changed, 36 insertions(+), 16 deletions(-) > > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c > index 3a06afb..f0c11c2 100644 > --- a/security/selinux/hooks.c > +++ b/security/selinux/hooks.c > @@ -2326,24 +2326,37 @@ static int check_nnp_nosuid(const struct > linux_binprm *bprm, > return 0; /* No change in credentials */ > > /* > - * The only transitions we permit under NNP or nosuid > - * are transitions to bounded SIDs, i.e. SIDs that are > - * guaranteed to only be allowed a subset of the permissions > - * of the current SID. > + * If the policy enables the nnp_transition policy > capability, > + * then we permit transitions under NNP or nosuid if the > + * policy explicitly allows nnp_transition permission > between > + * the old and new contexts. > */ > - rc = security_bounded_transition(old_tsec->sid, new_tsec- > >sid); > - if (rc) { > + if (selinux_policycap_nnptransition) { > + rc = avc_has_perm(old_tsec->sid, new_tsec->sid, > + SECCLASS_PROCESS, > + PROCESS__NNP_TRANSITION, NULL); > + if (!rc) > + return 0; > + } else { > /* > - * On failure, preserve the errno values for NNP vs > nosuid. > - * NNP: Operation not permitted for caller. > - * nosuid: Permission denied to file. > + * Otherwise, the only transitions we permit under > NNP or nosuid > + * are transitions to bounded SIDs, i.e. SIDs that > are > + * guaranteed to only be allowed a subset of the > permissions > + * of the current SID. > */ > - if (nnp) > - return -EPERM; > - else > - return -EACCES; > + rc = security_bounded_transition(old_tsec->sid, > new_tsec->sid); > + if (!rc) > + return 0; NB: As currently written, this logic means that if you enable the new policy capability, the only way to allow a transition under NNP is by allowing nnp_transition permission between the old and new domains. The alternative would be to fall back to checking for a bounded SID if nnp_transition permission is not allowed. The former approach has the advantage of being simpler (only a single check with a single audit message), but means that you can't mix usage of bounded SIDs and nnp_transition permission as a means of allowing a transitions under NNP within the same policy. The latter approach provides more flexibility / compatibility but will end up producing two audit messages in the case where the transition is denied by both checks: an AVC message for the permission denial and a SELINUX_ERR message for the security_bounded_transition failure, which might be confusing to users (but probably not; they'll likely just feed the AVC through audit2allow and be done with it). Thoughts? > } > - return 0; > + > + /* > + * On failure, preserve the errno values for NNP vs nosuid. > + * NNP: Operation not permitted for caller. > + * nosuid: Permission denied to file. > + */ > + if (nnp) > + return -EPERM; > + return -EACCES; > } > > static int selinux_bprm_set_creds(struct linux_binprm *bprm) > diff --git a/security/selinux/include/classmap.h > b/security/selinux/include/classmap.h > index b9fe343..7fde56d 100644 > --- a/security/selinux/include/classmap.h > +++ b/security/selinux/include/classmap.h > @@ -47,7 +47,7 @@ struct security_class_mapping secclass_map[] = { > "getattr", "setexec", "setfscreate", "noatsecure", > "siginh", > "setrlimit", "rlimitinh", "dyntransition", "setcurrent", > "execmem", "execstack", "execheap", "setkeycreate", > - "setsockcreate", "getrlimit", NULL } }, > + "setsockcreate", "getrlimit", "nnp_transition", NULL } > }, > { "system", > { "ipc_info", "syslog_read", "syslog_mod", > "syslog_console", "module_request", "module_load", NULL > } }, > diff --git a/security/selinux/include/security.h > b/security/selinux/include/security.h > index e91f08c..88efb1b 100644 > --- a/security/selinux/include/security.h > +++ b/security/selinux/include/security.h > @@ -73,6 +73,7 @@ enum { > POLICYDB_CAPABILITY_EXTSOCKCLASS, > POLICYDB_CAPABILITY_ALWAYSNETWORK, > POLICYDB_CAPABILITY_CGROUPSECLABEL, > + POLICYDB_CAPABILITY_NNPTRANSITION, > __POLICYDB_CAPABILITY_MAX > }; > #define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1) > @@ -84,6 +85,7 @@ extern int selinux_policycap_openperm; > extern int selinux_policycap_extsockclass; > extern int selinux_policycap_alwaysnetwork; > extern int selinux_policycap_cgroupseclabel; > +extern int selinux_policycap_nnptransition; > > /* > * type_datum properties > diff --git a/security/selinux/ss/services.c > b/security/selinux/ss/services.c > index 2f02fa6..2faf47a 100644 > --- a/security/selinux/ss/services.c > +++ b/security/selinux/ss/services.c > @@ -76,7 +76,8 @@ char > *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = { > "open_perms", > "extended_socket_class", > "always_check_network", > - "cgroup_seclabel" > + "cgroup_seclabel", > + "nnp_transition" > }; > > int selinux_policycap_netpeer; > @@ -84,6 +85,7 @@ int selinux_policycap_openperm; > int selinux_policycap_extsockclass; > int selinux_policycap_alwaysnetwork; > int selinux_policycap_cgroupseclabel; > +int selinux_policycap_nnptransition; > > static DEFINE_RWLOCK(policy_rwlock); > > @@ -2009,6 +2011,9 @@ static void security_load_policycaps(void) > selinux_policycap_cgroupseclabel = > ebitmap_get_bit(&policydb.policycaps, > POLICYDB_CAPABILITY_CGROUPSECLABEL); > + selinux_policycap_nnptransition = > + ebitmap_get_bit(&policydb.policycaps, > + POLICYDB_CAPABILITY_NNPTRANSITION); > > for (i = 0; i < ARRAY_SIZE(selinux_policycap_names); i++) > pr_info("SELinux: policy capability %s=%d\n",