On Mon, 2017-10-02 at 16:56 -0700, Casey Schaufler wrote: > On 10/2/2017 8:58 AM, Stephen Smalley wrote: > > Provide a userspace API to unshare the selinux namespace. > > Currently implemented via a selinuxfs node. This could be > > coupled with unsharing of other namespaces (e.g. mount namespace, > > network namespace) that will always be needed or left independent. > > Don't get hung up on the interface itself, it is just to allow > > experimentation and testing. > > > > Sample usage: > > echo 1 > /sys/fs/selinux/unshare > > unshare -m -n > > umount /sys/fs/selinux > > mount -t selinuxfs none /sys/fs/selinux > > load_policy > > getenforce > > id > > echo $$ > > > > The above will show that the process now views itself as running in > > the > > kernel domain in permissive mode, as would be the case at boot. > > > From a different shell on the host system, running ps -eZ or > > > > cat /proc/<pid>/attr/current will show that the process that > > unshared its selinux namespace is still running in its original > > context in the initial namespace, and getenforce will show the > > the initial namespace remains enforcing. Enforcing mode or policy > > changes in the child will not affect the parent. > > > > This is not yet safe; do not use on production systems. > > Known issues include at least the following items: > > > > * The policy loading code has not been thoroughly audited > > and hardened for use by unprivileged code, both with respect to > > ensuring that the policy is internally consistent and restricting > > the range of values used from the policy as loop bounds and memory > > allocation sizes to sane limits. > > > > * The SELinux hook functions have not been modified to be > > namespace-aware, so the hooks only perform checking against the > > current namespace. Thus, unsharing allows the process to escape > > confinement by the parent. Fixing this requires updating each hook > > to > > perform its processing on the current namespace and all of its > > ancestors > > up to the init namespace. > > > > * Some of the hook functions can be called outside of process > > context > > (e.g. task_kill, send_sigiotask, network input/forward) and should > > not use > > the current task's selinux namespace. These hooks need to be > > updated to > > obtain the proper selinux namespace to use instead from the caller > > or > > cached in a suitable data structure (e.g. the file or sock security > > structures). > > > > * There are number of issues with the inode and superblock security > > blob > > handling for multiple namespaces, see those commits for more > > details. > > > > * Only a subset of object security blobs have been updated to > > be namespace-aware and support multiple namespaces. The ones that > > have not yet been updated could end up performing permission checks > > or > > other operations on SIDs created in a different selinux namespace. > > > > * The network SID caches (netif, netnode, netport) have not yet > > been instantiated per selinux namespace, unlike the AVC and SS. > > > > * There is no way currently to restrict or bound nesting of > > namespaces; if you allow it to a domain in the init namespace, > > then that domain can in turn unshare to arbitrary depths and can > > grant the same to any domain in its own policy. Related to this > > is the fact that there is no way to control resource usage due to > > selinux namespaces and they can be substantial (per-namespace > > policydb, sidtab, AVC, etc). > > > > * SIDs may be cached by audit and networking code and in external > > kernel data structures and used later, potentially in a different > > selinux namespace than the one in which the SID was originally > > created. > > Is there a good reason that SIDs (and security contexts) need to > be maintained separately in the namespaces? Using the same secid > to map to a different context depending on the namespace seems like > you're asking for trouble you don't need. A namespace that hasn't > policy for a context/SID won't use it if it is defined for a > different namespace, and should detect the fact if it somehow gets > referenced, because it isn't in the policy. > > Do the context/SID mapping globally. Or, if you must duplicate > contexts, > allocate the SIDs from a single source so that they aren't ambiguous.
Yes, that may be the right answer, but it requires introducing a new SID/context layer above the current security server layer (or changing the latter), because the security server SID/context mappings are from SIDs to internal struct representations of the context, where the struct representation is policy-specific. Also, certain SIDs (e.g. the kernel SID, the unlabeled SID, etc) are predefined for system initialization before policy load and to support usage from policy- independent code, but may be mapped to different context values by different policies. So even the SID->string mappings may differ for different policies, and hence for different namespaces. > > > > > * No doubt other things I'm forgetting or haven't thought of. > > Use at your own risk. > > > > Not-signed-off-by: Stephen Smalley <s...@tycho.nsa.gov> > > --- > > security/selinux/include/classmap.h | 3 +- > > security/selinux/selinuxfs.c | 66 > > +++++++++++++++++++++++++++++++++++++ > > 2 files changed, 68 insertions(+), 1 deletion(-) > > > > diff --git a/security/selinux/include/classmap.h > > b/security/selinux/include/classmap.h > > index 35ffb29..82c8f9c 100644 > > --- a/security/selinux/include/classmap.h > > +++ b/security/selinux/include/classmap.h > > @@ -39,7 +39,8 @@ struct security_class_mapping secclass_map[] = { > > { "compute_av", "compute_create", "compute_member", > > "check_context", "load_policy", "compute_relabel", > > "compute_user", "setenforce", "setbool", > > "setsecparam", > > - "setcheckreqprot", "read_policy", "validate_trans", > > NULL } }, > > + "setcheckreqprot", "read_policy", "validate_trans", > > "unshare", > > + NULL } }, > > { "process", > > { "fork", "transition", "sigchld", "sigkill", > > "sigstop", "signull", "signal", "ptrace", "getsched", > > "setsched", > > diff --git a/security/selinux/selinuxfs.c > > b/security/selinux/selinuxfs.c > > index a7e6bdb..dedb3cc9 100644 > > --- a/security/selinux/selinuxfs.c > > +++ b/security/selinux/selinuxfs.c > > @@ -63,6 +63,7 @@ enum sel_inos { > > SEL_STATUS, /* export current status using mmap() > > */ > > SEL_POLICY, /* allow userspace to read the in > > kernel policy */ > > SEL_VALIDATE_TRANS, /* compute validatetrans decision */ > > + SEL_UNSHARE, /* unshare selinux namespace */ > > SEL_INO_NEXT, /* The next inode number to use */ > > }; > > > > @@ -321,6 +322,70 @@ static const struct file_operations > > sel_disable_ops = { > > .llseek = generic_file_llseek, > > }; > > > > +static ssize_t sel_write_unshare(struct file *file, const char > > __user *buf, > > + size_t count, loff_t *ppos) > > + > > +{ > > + struct selinux_fs_info *fsi = file_inode(file)->i_sb- > > >s_fs_info; > > + struct selinux_ns *ns = fsi->ns; > > + char *page; > > + ssize_t length; > > + bool set; > > + int rc; > > + > > + if (count >= PAGE_SIZE) > > + return -ENOMEM; > > + > > + /* No partial writes. */ > > + if (*ppos != 0) > > + return -EINVAL; > > + > > + rc = avc_has_perm(current_selinux_ns, current_sid(), > > + SECINITSID_SECURITY, SECCLASS_SECURITY, > > + SECURITY__UNSHARE, NULL); > > + if (rc) > > + return rc; > > + > > + page = memdup_user_nul(buf, count); > > + if (IS_ERR(page)) > > + return PTR_ERR(page); > > + > > + length = -EINVAL; > > + if (kstrtobool(page, &set)) > > + goto out; > > + > > + if (set) { > > + struct cred *cred = prepare_creds(); > > + struct task_security_struct *tsec; > > + > > + if (!cred) { > > + length = -ENOMEM; > > + goto out; > > + } > > + tsec = cred->security; > > + if (selinux_ns_create(ns, &tsec->ns)) { > > + abort_creds(cred); > > + length = -ENOMEM; > > + goto out; > > + } > > + tsec->osid = tsec->sid = SECINITSID_KERNEL; > > + tsec->exec_sid = tsec->create_sid = tsec- > > >keycreate_sid = > > + tsec->sockcreate_sid = SECSID_NULL; > > + tsec->parent_cred = get_current_cred(); > > + commit_creds(cred); > > + } > > + > > + length = count; > > +out: > > + kfree(page); > > + return length; > > +} > > + > > +static const struct file_operations sel_unshare_ops = { > > + .write = sel_write_unshare, > > + .llseek = generic_file_llseek, > > +}; > > + > > static ssize_t sel_read_policyvers(struct file *filp, char __user > > *buf, > > size_t count, loff_t *ppos) > > { > > @@ -1923,6 +1988,7 @@ static int sel_fill_super(struct super_block > > *sb, void *data, int silent) > > [SEL_POLICY] = {"policy", &sel_policy_ops, > > S_IRUGO}, > > [SEL_VALIDATE_TRANS] = {"validatetrans", > > &sel_transition_ops, > > S_IWUGO}, > > + [SEL_UNSHARE] = {"unshare", &sel_unshare_ops, > > 0222}, > > /* last one */ {""} > > }; > > > > > .