userns: user namespaces: convert several capable() calls

CAP_IPC_OWNER and CAP_IPC_LOCK can be checked against current_user_ns(),
because the resource comes from current's own ipc namespace.

setuid/setgid are to uids in own namespace, so again checks can be against
current_user_ns().

Changelog:
	Jan 11: Use task_ns_capable() in place of sched_capable().
	Jan 11: Use nsown_capable() as suggested by Bastian Blank.
	Jan 11: Clarify (hopefully) some logic in futex and sched.c
	Feb 15: use ns_capable for ipc, not nsown_capable
	Feb 23: let copy_ipcs handle setting ipc_ns->user_ns
	Feb 23: pass ns down rather than taking it from current

[akpm@linux-foundation.org: coding-style fixes]
Signed-off-by: Serge E. Hallyn <serge.hallyn@canonical.com>
Acked-by: "Eric W. Biederman" <ebiederm@xmission.com>
Acked-by: Daniel Lezcano <daniel.lezcano@free.fr>
Acked-by: David Howells <dhowells@redhat.com>
Cc: James Morris <jmorris@namei.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Serge E. Hallyn 2011-03-23 16:43:24 -07:00 committed by Linus Torvalds
parent b515498f5b
commit b0e77598f8
13 changed files with 75 additions and 45 deletions

View file

@ -5,6 +5,7 @@
#include <linux/idr.h> #include <linux/idr.h>
#include <linux/rwsem.h> #include <linux/rwsem.h>
#include <linux/notifier.h> #include <linux/notifier.h>
#include <linux/nsproxy.h>
/* /*
* ipc namespace events * ipc namespace events
@ -93,7 +94,7 @@ static inline int mq_init_ns(struct ipc_namespace *ns) { return 0; }
#if defined(CONFIG_IPC_NS) #if defined(CONFIG_IPC_NS)
extern struct ipc_namespace *copy_ipcs(unsigned long flags, extern struct ipc_namespace *copy_ipcs(unsigned long flags,
struct ipc_namespace *ns); struct task_struct *tsk);
static inline struct ipc_namespace *get_ipc_ns(struct ipc_namespace *ns) static inline struct ipc_namespace *get_ipc_ns(struct ipc_namespace *ns)
{ {
if (ns) if (ns)
@ -104,12 +105,12 @@ static inline struct ipc_namespace *get_ipc_ns(struct ipc_namespace *ns)
extern void put_ipc_ns(struct ipc_namespace *ns); extern void put_ipc_ns(struct ipc_namespace *ns);
#else #else
static inline struct ipc_namespace *copy_ipcs(unsigned long flags, static inline struct ipc_namespace *copy_ipcs(unsigned long flags,
struct ipc_namespace *ns) struct task_struct *tsk)
{ {
if (flags & CLONE_NEWIPC) if (flags & CLONE_NEWIPC)
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
return ns; return tsk->nsproxy->ipc_ns;
} }
static inline struct ipc_namespace *get_ipc_ns(struct ipc_namespace *ns) static inline struct ipc_namespace *get_ipc_ns(struct ipc_namespace *ns)

View file

@ -421,7 +421,7 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd,
return -EFAULT; return -EFAULT;
} }
ipcp = ipcctl_pre_down(&msg_ids(ns), msqid, cmd, ipcp = ipcctl_pre_down(ns, &msg_ids(ns), msqid, cmd,
&msqid64.msg_perm, msqid64.msg_qbytes); &msqid64.msg_perm, msqid64.msg_qbytes);
if (IS_ERR(ipcp)) if (IS_ERR(ipcp))
return PTR_ERR(ipcp); return PTR_ERR(ipcp);
@ -539,7 +539,7 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
success_return = 0; success_return = 0;
} }
err = -EACCES; err = -EACCES;
if (ipcperms(&msq->q_perm, S_IRUGO)) if (ipcperms(ns, &msq->q_perm, S_IRUGO))
goto out_unlock; goto out_unlock;
err = security_msg_queue_msgctl(msq, cmd); err = security_msg_queue_msgctl(msq, cmd);
@ -664,7 +664,7 @@ long do_msgsnd(int msqid, long mtype, void __user *mtext,
struct msg_sender s; struct msg_sender s;
err = -EACCES; err = -EACCES;
if (ipcperms(&msq->q_perm, S_IWUGO)) if (ipcperms(ns, &msq->q_perm, S_IWUGO))
goto out_unlock_free; goto out_unlock_free;
err = security_msg_queue_msgsnd(msq, msg, msgflg); err = security_msg_queue_msgsnd(msq, msg, msgflg);
@ -774,7 +774,7 @@ long do_msgrcv(int msqid, long *pmtype, void __user *mtext,
struct list_head *tmp; struct list_head *tmp;
msg = ERR_PTR(-EACCES); msg = ERR_PTR(-EACCES);
if (ipcperms(&msq->q_perm, S_IRUGO)) if (ipcperms(ns, &msq->q_perm, S_IRUGO))
goto out_unlock; goto out_unlock;
msg = ERR_PTR(-EAGAIN); msg = ERR_PTR(-EAGAIN);

View file

@ -15,7 +15,8 @@
#include "util.h" #include "util.h"
static struct ipc_namespace *create_ipc_ns(struct ipc_namespace *old_ns) static struct ipc_namespace *create_ipc_ns(struct task_struct *tsk,
struct ipc_namespace *old_ns)
{ {
struct ipc_namespace *ns; struct ipc_namespace *ns;
int err; int err;
@ -44,17 +45,19 @@ static struct ipc_namespace *create_ipc_ns(struct ipc_namespace *old_ns)
ipcns_notify(IPCNS_CREATED); ipcns_notify(IPCNS_CREATED);
register_ipcns_notifier(ns); register_ipcns_notifier(ns);
ns->user_ns = old_ns->user_ns; ns->user_ns = get_user_ns(task_cred_xxx(tsk, user)->user_ns);
get_user_ns(ns->user_ns);
return ns; return ns;
} }
struct ipc_namespace *copy_ipcs(unsigned long flags, struct ipc_namespace *ns) struct ipc_namespace *copy_ipcs(unsigned long flags,
struct task_struct *tsk)
{ {
struct ipc_namespace *ns = tsk->nsproxy->ipc_ns;
if (!(flags & CLONE_NEWIPC)) if (!(flags & CLONE_NEWIPC))
return get_ipc_ns(ns); return get_ipc_ns(ns);
return create_ipc_ns(ns); return create_ipc_ns(tsk, ns);
} }
/* /*

View file

@ -817,7 +817,7 @@ static int semctl_nolock(struct ipc_namespace *ns, int semid,
} }
err = -EACCES; err = -EACCES;
if (ipcperms (&sma->sem_perm, S_IRUGO)) if (ipcperms(ns, &sma->sem_perm, S_IRUGO))
goto out_unlock; goto out_unlock;
err = security_sem_semctl(sma, cmd); err = security_sem_semctl(sma, cmd);
@ -862,7 +862,8 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum,
nsems = sma->sem_nsems; nsems = sma->sem_nsems;
err = -EACCES; err = -EACCES;
if (ipcperms (&sma->sem_perm, (cmd==SETVAL||cmd==SETALL)?S_IWUGO:S_IRUGO)) if (ipcperms(ns, &sma->sem_perm,
(cmd == SETVAL || cmd == SETALL) ? S_IWUGO : S_IRUGO))
goto out_unlock; goto out_unlock;
err = security_sem_semctl(sma, cmd); err = security_sem_semctl(sma, cmd);
@ -1047,7 +1048,8 @@ static int semctl_down(struct ipc_namespace *ns, int semid,
return -EFAULT; return -EFAULT;
} }
ipcp = ipcctl_pre_down(&sem_ids(ns), semid, cmd, &semid64.sem_perm, 0); ipcp = ipcctl_pre_down(ns, &sem_ids(ns), semid, cmd,
&semid64.sem_perm, 0);
if (IS_ERR(ipcp)) if (IS_ERR(ipcp))
return PTR_ERR(ipcp); return PTR_ERR(ipcp);
@ -1386,7 +1388,7 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
goto out_unlock_free; goto out_unlock_free;
error = -EACCES; error = -EACCES;
if (ipcperms(&sma->sem_perm, alter ? S_IWUGO : S_IRUGO)) if (ipcperms(ns, &sma->sem_perm, alter ? S_IWUGO : S_IRUGO))
goto out_unlock_free; goto out_unlock_free;
error = security_sem_semop(sma, sops, nsops, alter); error = security_sem_semop(sma, sops, nsops, alter);

View file

@ -623,7 +623,8 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
return -EFAULT; return -EFAULT;
} }
ipcp = ipcctl_pre_down(&shm_ids(ns), shmid, cmd, &shmid64.shm_perm, 0); ipcp = ipcctl_pre_down(ns, &shm_ids(ns), shmid, cmd,
&shmid64.shm_perm, 0);
if (IS_ERR(ipcp)) if (IS_ERR(ipcp))
return PTR_ERR(ipcp); return PTR_ERR(ipcp);
@ -737,7 +738,7 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
result = 0; result = 0;
} }
err = -EACCES; err = -EACCES;
if (ipcperms (&shp->shm_perm, S_IRUGO)) if (ipcperms(ns, &shp->shm_perm, S_IRUGO))
goto out_unlock; goto out_unlock;
err = security_shm_shmctl(shp, cmd); err = security_shm_shmctl(shp, cmd);
if (err) if (err)
@ -773,7 +774,7 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
audit_ipc_obj(&(shp->shm_perm)); audit_ipc_obj(&(shp->shm_perm));
if (!capable(CAP_IPC_LOCK)) { if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) {
uid_t euid = current_euid(); uid_t euid = current_euid();
err = -EPERM; err = -EPERM;
if (euid != shp->shm_perm.uid && if (euid != shp->shm_perm.uid &&
@ -888,7 +889,7 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr)
} }
err = -EACCES; err = -EACCES;
if (ipcperms(&shp->shm_perm, acc_mode)) if (ipcperms(ns, &shp->shm_perm, acc_mode))
goto out_unlock; goto out_unlock;
err = security_shm_shmat(shp, shmaddr, shmflg); err = security_shm_shmat(shp, shmaddr, shmflg);

View file

@ -329,12 +329,14 @@ static int ipcget_new(struct ipc_namespace *ns, struct ipc_ids *ids,
* *
* It is called with ipc_ids.rw_mutex and ipcp->lock held. * It is called with ipc_ids.rw_mutex and ipcp->lock held.
*/ */
static int ipc_check_perms(struct kern_ipc_perm *ipcp, struct ipc_ops *ops, static int ipc_check_perms(struct ipc_namespace *ns,
struct ipc_params *params) struct kern_ipc_perm *ipcp,
struct ipc_ops *ops,
struct ipc_params *params)
{ {
int err; int err;
if (ipcperms(ipcp, params->flg)) if (ipcperms(ns, ipcp, params->flg))
err = -EACCES; err = -EACCES;
else { else {
err = ops->associate(ipcp, params->flg); err = ops->associate(ipcp, params->flg);
@ -396,7 +398,7 @@ static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids,
* ipc_check_perms returns the IPC id on * ipc_check_perms returns the IPC id on
* success * success
*/ */
err = ipc_check_perms(ipcp, ops, params); err = ipc_check_perms(ns, ipcp, ops, params);
} }
ipc_unlock(ipcp); ipc_unlock(ipcp);
} }
@ -610,10 +612,12 @@ void ipc_rcu_putref(void *ptr)
* *
* Check user, group, other permissions for access * Check user, group, other permissions for access
* to ipc resources. return 0 if allowed * to ipc resources. return 0 if allowed
*
* @flag will most probably be 0 or S_...UGO from <linux/stat.h>
*/ */
int ipcperms (struct kern_ipc_perm *ipcp, short flag) int ipcperms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, short flag)
{ /* flag will most probably be 0 or S_...UGO from <linux/stat.h> */ {
uid_t euid = current_euid(); uid_t euid = current_euid();
int requested_mode, granted_mode; int requested_mode, granted_mode;
@ -627,7 +631,7 @@ int ipcperms (struct kern_ipc_perm *ipcp, short flag)
granted_mode >>= 3; granted_mode >>= 3;
/* is there some bit set in requested_mode but not in granted_mode? */ /* is there some bit set in requested_mode but not in granted_mode? */
if ((requested_mode & ~granted_mode & 0007) && if ((requested_mode & ~granted_mode & 0007) &&
!capable(CAP_IPC_OWNER)) !ns_capable(ns->user_ns, CAP_IPC_OWNER))
return -1; return -1;
return security_ipc_permission(ipcp, flag); return security_ipc_permission(ipcp, flag);
@ -765,6 +769,7 @@ void ipc_update_perm(struct ipc64_perm *in, struct kern_ipc_perm *out)
/** /**
* ipcctl_pre_down - retrieve an ipc and check permissions for some IPC_XXX cmd * ipcctl_pre_down - retrieve an ipc and check permissions for some IPC_XXX cmd
* @ids: the ipc namespace
* @ids: the table of ids where to look for the ipc * @ids: the table of ids where to look for the ipc
* @id: the id of the ipc to retrieve * @id: the id of the ipc to retrieve
* @cmd: the cmd to check * @cmd: the cmd to check
@ -779,7 +784,8 @@ void ipc_update_perm(struct ipc64_perm *in, struct kern_ipc_perm *out)
* - returns the ipc with both ipc and rw_mutex locks held in case of success * - returns the ipc with both ipc and rw_mutex locks held in case of success
* or an err-code without any lock held otherwise. * or an err-code without any lock held otherwise.
*/ */
struct kern_ipc_perm *ipcctl_pre_down(struct ipc_ids *ids, int id, int cmd, struct kern_ipc_perm *ipcctl_pre_down(struct ipc_namespace *ns,
struct ipc_ids *ids, int id, int cmd,
struct ipc64_perm *perm, int extra_perm) struct ipc64_perm *perm, int extra_perm)
{ {
struct kern_ipc_perm *ipcp; struct kern_ipc_perm *ipcp;
@ -799,8 +805,8 @@ struct kern_ipc_perm *ipcctl_pre_down(struct ipc_ids *ids, int id, int cmd,
perm->gid, perm->mode); perm->gid, perm->mode);
euid = current_euid(); euid = current_euid();
if (euid == ipcp->cuid || if (euid == ipcp->cuid || euid == ipcp->uid ||
euid == ipcp->uid || capable(CAP_SYS_ADMIN)) ns_capable(ns->user_ns, CAP_SYS_ADMIN))
return ipcp; return ipcp;
err = -EPERM; err = -EPERM;

View file

@ -103,7 +103,7 @@ int ipc_get_maxid(struct ipc_ids *);
void ipc_rmid(struct ipc_ids *, struct kern_ipc_perm *); void ipc_rmid(struct ipc_ids *, struct kern_ipc_perm *);
/* must be called with ipcp locked */ /* must be called with ipcp locked */
int ipcperms(struct kern_ipc_perm *ipcp, short flg); int ipcperms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, short flg);
/* for rare, potentially huge allocations. /* for rare, potentially huge allocations.
* both function can sleep * both function can sleep
@ -126,7 +126,8 @@ struct kern_ipc_perm *ipc_lock(struct ipc_ids *, int);
void kernel_to_ipc64_perm(struct kern_ipc_perm *in, struct ipc64_perm *out); void kernel_to_ipc64_perm(struct kern_ipc_perm *in, struct ipc64_perm *out);
void ipc64_perm_to_ipc_perm(struct ipc64_perm *in, struct ipc_perm *out); void ipc64_perm_to_ipc_perm(struct ipc64_perm *in, struct ipc_perm *out);
void ipc_update_perm(struct ipc64_perm *in, struct kern_ipc_perm *out); void ipc_update_perm(struct ipc64_perm *in, struct kern_ipc_perm *out);
struct kern_ipc_perm *ipcctl_pre_down(struct ipc_ids *ids, int id, int cmd, struct kern_ipc_perm *ipcctl_pre_down(struct ipc_namespace *ns,
struct ipc_ids *ids, int id, int cmd,
struct ipc64_perm *perm, int extra_perm); struct ipc64_perm *perm, int extra_perm);
#ifndef __ARCH_WANT_IPC_PARSE_VERSION #ifndef __ARCH_WANT_IPC_PARSE_VERSION

View file

@ -2418,10 +2418,19 @@ SYSCALL_DEFINE3(get_robust_list, int, pid,
goto err_unlock; goto err_unlock;
ret = -EPERM; ret = -EPERM;
pcred = __task_cred(p); pcred = __task_cred(p);
/* If victim is in different user_ns, then uids are not
comparable, so we must have CAP_SYS_PTRACE */
if (cred->user->user_ns != pcred->user->user_ns) {
if (!ns_capable(pcred->user->user_ns, CAP_SYS_PTRACE))
goto err_unlock;
goto ok;
}
/* If victim is in same user_ns, then uids are comparable */
if (cred->euid != pcred->euid && if (cred->euid != pcred->euid &&
cred->euid != pcred->uid && cred->euid != pcred->uid &&
!capable(CAP_SYS_PTRACE)) !ns_capable(pcred->user->user_ns, CAP_SYS_PTRACE))
goto err_unlock; goto err_unlock;
ok:
head = p->robust_list; head = p->robust_list;
rcu_read_unlock(); rcu_read_unlock();
} }

View file

@ -153,10 +153,19 @@ compat_sys_get_robust_list(int pid, compat_uptr_t __user *head_ptr,
goto err_unlock; goto err_unlock;
ret = -EPERM; ret = -EPERM;
pcred = __task_cred(p); pcred = __task_cred(p);
/* If victim is in different user_ns, then uids are not
comparable, so we must have CAP_SYS_PTRACE */
if (cred->user->user_ns != pcred->user->user_ns) {
if (!ns_capable(pcred->user->user_ns, CAP_SYS_PTRACE))
goto err_unlock;
goto ok;
}
/* If victim is in same user_ns, then uids are comparable */
if (cred->euid != pcred->euid && if (cred->euid != pcred->euid &&
cred->euid != pcred->uid && cred->euid != pcred->uid &&
!capable(CAP_SYS_PTRACE)) !ns_capable(pcred->user->user_ns, CAP_SYS_PTRACE))
goto err_unlock; goto err_unlock;
ok:
head = p->compat_robust_list; head = p->compat_robust_list;
rcu_read_unlock(); rcu_read_unlock();
} }

View file

@ -233,7 +233,7 @@ SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
struct group_info *group_info; struct group_info *group_info;
int retval; int retval;
if (!capable(CAP_SETGID)) if (!nsown_capable(CAP_SETGID))
return -EPERM; return -EPERM;
if ((unsigned)gidsetsize > NGROUPS_MAX) if ((unsigned)gidsetsize > NGROUPS_MAX)
return -EINVAL; return -EINVAL;

View file

@ -75,16 +75,11 @@ static struct nsproxy *create_new_namespaces(unsigned long flags,
goto out_uts; goto out_uts;
} }
new_nsp->ipc_ns = copy_ipcs(flags, tsk->nsproxy->ipc_ns); new_nsp->ipc_ns = copy_ipcs(flags, tsk);
if (IS_ERR(new_nsp->ipc_ns)) { if (IS_ERR(new_nsp->ipc_ns)) {
err = PTR_ERR(new_nsp->ipc_ns); err = PTR_ERR(new_nsp->ipc_ns);
goto out_ipc; goto out_ipc;
} }
if (new_nsp->ipc_ns != tsk->nsproxy->ipc_ns) {
put_user_ns(new_nsp->ipc_ns->user_ns);
new_nsp->ipc_ns->user_ns = task_cred_xxx(tsk, user)->user_ns;
get_user_ns(new_nsp->ipc_ns->user_ns);
}
new_nsp->pid_ns = copy_pid_ns(flags, task_active_pid_ns(tsk)); new_nsp->pid_ns = copy_pid_ns(flags, task_active_pid_ns(tsk));
if (IS_ERR(new_nsp->pid_ns)) { if (IS_ERR(new_nsp->pid_ns)) {

View file

@ -4892,8 +4892,11 @@ static bool check_same_owner(struct task_struct *p)
rcu_read_lock(); rcu_read_lock();
pcred = __task_cred(p); pcred = __task_cred(p);
match = (cred->euid == pcred->euid || if (cred->user->user_ns == pcred->user->user_ns)
cred->euid == pcred->uid); match = (cred->euid == pcred->euid ||
cred->euid == pcred->uid);
else
match = false;
rcu_read_unlock(); rcu_read_unlock();
return match; return match;
} }
@ -5221,7 +5224,7 @@ long sched_setaffinity(pid_t pid, const struct cpumask *in_mask)
goto out_free_cpus_allowed; goto out_free_cpus_allowed;
} }
retval = -EPERM; retval = -EPERM;
if (!check_same_owner(p) && !capable(CAP_SYS_NICE)) if (!check_same_owner(p) && !task_ns_capable(p, CAP_SYS_NICE))
goto out_unlock; goto out_unlock;
retval = security_task_setscheduler(p); retval = security_task_setscheduler(p);

View file

@ -189,7 +189,7 @@ SYSCALL_DEFINE2(setgroups16, int, gidsetsize, old_gid_t __user *, grouplist)
struct group_info *group_info; struct group_info *group_info;
int retval; int retval;
if (!capable(CAP_SETGID)) if (!nsown_capable(CAP_SETGID))
return -EPERM; return -EPERM;
if ((unsigned)gidsetsize > NGROUPS_MAX) if ((unsigned)gidsetsize > NGROUPS_MAX)
return -EINVAL; return -EINVAL;