diff options
Diffstat (limited to 'ipc/shm.c')
| -rw-r--r-- | ipc/shm.c | 110 | 
1 files changed, 72 insertions, 38 deletions
diff --git a/ipc/shm.c b/ipc/shm.c index 2821cdf93ad..89fc354156c 100644 --- a/ipc/shm.c +++ b/ipc/shm.c @@ -43,7 +43,7 @@  #include <linux/mount.h>  #include <linux/ipc_namespace.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h>  #include "util.h" @@ -67,7 +67,7 @@ static const struct vm_operations_struct shm_vm_ops;  static int newseg(struct ipc_namespace *, struct ipc_params *);  static void shm_open(struct vm_area_struct *vma);  static void shm_close(struct vm_area_struct *vma); -static void shm_destroy (struct ipc_namespace *ns, struct shmid_kernel *shp); +static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp);  #ifdef CONFIG_PROC_FS  static int sysvipc_shm_proc_show(struct seq_file *s, void *it);  #endif @@ -91,7 +91,7 @@ static void do_shm_rmid(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp)  	struct shmid_kernel *shp;  	shp = container_of(ipcp, struct shmid_kernel, shm_perm); -	if (shp->shm_nattch){ +	if (shp->shm_nattch) {  		shp->shm_perm.mode |= SHM_DEST;  		/* Do not find it any more */  		shp->shm_perm.key = IPC_PRIVATE; @@ -116,7 +116,7 @@ static int __init ipc_ns_init(void)  pure_initcall(ipc_ns_init); -void __init shm_init (void) +void __init shm_init(void)  {  	ipc_init_proc_interface("sysvipc/shm",  #if BITS_PER_LONG <= 32 @@ -167,6 +167,15 @@ static inline void shm_lock_by_ptr(struct shmid_kernel *ipcp)  	ipc_lock_object(&ipcp->shm_perm);  } +static void shm_rcu_free(struct rcu_head *head) +{ +	struct ipc_rcu *p = container_of(head, struct ipc_rcu, rcu); +	struct shmid_kernel *shp = ipc_rcu_to_struct(p); + +	security_shm_free(shp); +	ipc_rcu_free(head); +} +  static inline void shm_rmid(struct ipc_namespace *ns, struct shmid_kernel *s)  {  	ipc_rmid(&shm_ids(ns), &s->shm_perm); @@ -199,17 +208,19 @@ static void shm_open(struct vm_area_struct *vma)   */  static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp)  { +	struct file *shm_file; + +	shm_file = shp->shm_file; +	shp->shm_file = NULL;  	ns->shm_tot -= (shp->shm_segsz + PAGE_SIZE - 1) >> PAGE_SHIFT;  	shm_rmid(ns, shp);  	shm_unlock(shp); -	if (!is_file_hugepages(shp->shm_file)) -		shmem_lock(shp->shm_file, 0, shp->mlock_user); +	if (!is_file_hugepages(shm_file)) +		shmem_lock(shm_file, 0, shp->mlock_user);  	else if (shp->mlock_user) -		user_shm_unlock(file_inode(shp->shm_file)->i_size, -						shp->mlock_user); -	fput (shp->shm_file); -	security_shm_free(shp); -	ipc_rcu_putref(shp); +		user_shm_unlock(file_inode(shm_file)->i_size, shp->mlock_user); +	fput(shm_file); +	ipc_rcu_putref(shp, shm_rcu_free);  }  /* @@ -237,7 +248,7 @@ static bool shm_may_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp)   */  static void shm_close(struct vm_area_struct *vma)  { -	struct file * file = vma->vm_file; +	struct file *file = vma->vm_file;  	struct shm_file_data *sfd = shm_file_data(file);  	struct shmid_kernel *shp;  	struct ipc_namespace *ns = sfd->ns; @@ -368,7 +379,7 @@ static struct mempolicy *shm_get_policy(struct vm_area_struct *vma,  }  #endif -static int shm_mmap(struct file * file, struct vm_area_struct * vma) +static int shm_mmap(struct file *file, struct vm_area_struct *vma)  {  	struct shm_file_data *sfd = shm_file_data(file);  	int ret; @@ -466,7 +477,6 @@ static const struct vm_operations_struct shm_vm_ops = {   *   * Called with shm_ids.rwsem held as a writer.   */ -  static int newseg(struct ipc_namespace *ns, struct ipc_params *params)  {  	key_t key = params->key; @@ -475,7 +485,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)  	int error;  	struct shmid_kernel *shp;  	size_t numpages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; -	struct file * file; +	struct file *file;  	char name[13];  	int id;  	vm_flags_t acctflag = 0; @@ -483,7 +493,11 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)  	if (size < SHMMIN || size > ns->shm_ctlmax)  		return -EINVAL; -	if (ns->shm_tot + numpages > ns->shm_ctlall) +	if (numpages << PAGE_SHIFT < size) +		return -ENOSPC; + +	if (ns->shm_tot + numpages < ns->shm_tot || +			ns->shm_tot + numpages > ns->shm_ctlall)  		return -ENOSPC;  	shp = ipc_rcu_alloc(sizeof(*shp)); @@ -497,11 +511,11 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)  	shp->shm_perm.security = NULL;  	error = security_shm_alloc(shp);  	if (error) { -		ipc_rcu_putref(shp); +		ipc_rcu_putref(shp, ipc_rcu_free);  		return error;  	} -	sprintf (name, "SYSV%08x", key); +	sprintf(name, "SYSV%08x", key);  	if (shmflg & SHM_HUGETLB) {  		struct hstate *hs;  		size_t hugesize; @@ -522,7 +536,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)  	} else {  		/*  		 * Do not allow no accounting for OVERCOMMIT_NEVER, even -	 	 * if it's asked for. +		 * if it's asked for.  		 */  		if  ((shmflg & SHM_NORESERVE) &&  				sysctl_overcommit_memory != OVERCOMMIT_NEVER) @@ -566,8 +580,7 @@ no_id:  		user_shm_unlock(size, shp->mlock_user);  	fput(file);  no_file: -	security_shm_free(shp); -	ipc_rcu_putref(shp); +	ipc_rcu_putref(shp, shm_rcu_free);  	return error;  } @@ -600,15 +613,15 @@ static inline int shm_more_checks(struct kern_ipc_perm *ipcp,  SYSCALL_DEFINE3(shmget, key_t, key, size_t, size, int, shmflg)  {  	struct ipc_namespace *ns; -	struct ipc_ops shm_ops; +	static const struct ipc_ops shm_ops = { +		.getnew = newseg, +		.associate = shm_security, +		.more_checks = shm_more_checks, +	};  	struct ipc_params shm_params;  	ns = current->nsproxy->ipc_ns; -	shm_ops.getnew = newseg; -	shm_ops.associate = shm_security; -	shm_ops.more_checks = shm_more_checks; -  	shm_params.key = key;  	shm_params.flg = shmflg;  	shm_params.u.size = size; @@ -618,7 +631,7 @@ SYSCALL_DEFINE3(shmget, key_t, key, size_t, size, int, shmflg)  static inline unsigned long copy_shmid_to_user(void __user *buf, struct shmid64_ds *in, int version)  { -	switch(version) { +	switch (version) {  	case IPC_64:  		return copy_to_user(buf, in, sizeof(*in));  	case IPC_OLD: @@ -645,7 +658,7 @@ static inline unsigned long copy_shmid_to_user(void __user *buf, struct shmid64_  static inline unsigned long  copy_shmid_from_user(struct shmid64_ds *out, void __user *buf, int version)  { -	switch(version) { +	switch (version) {  	case IPC_64:  		if (copy_from_user(out, buf, sizeof(*out)))  			return -EFAULT; @@ -670,14 +683,14 @@ copy_shmid_from_user(struct shmid64_ds *out, void __user *buf, int version)  static inline unsigned long copy_shminfo_to_user(void __user *buf, struct shminfo64 *in, int version)  { -	switch(version) { +	switch (version) {  	case IPC_64:  		return copy_to_user(buf, in, sizeof(*in));  	case IPC_OLD:  	    {  		struct shminfo out; -		if(in->shmmax > INT_MAX) +		if (in->shmmax > INT_MAX)  			out.shmmax = INT_MAX;  		else  			out.shmmax = (int)in->shmmax; @@ -685,7 +698,7 @@ static inline unsigned long copy_shminfo_to_user(void __user *buf, struct shminf  		out.shmmin	= in->shmmin;  		out.shmmni	= in->shmmni;  		out.shmseg	= in->shmseg; -		out.shmall	= in->shmall;  +		out.shmall	= in->shmall;  		return copy_to_user(buf, &out, sizeof(out));  	    } @@ -836,14 +849,14 @@ static int shmctl_nolock(struct ipc_namespace *ns, int shmid,  		shminfo.shmall = ns->shm_ctlall;  		shminfo.shmmin = SHMMIN; -		if(copy_shminfo_to_user (buf, &shminfo, version)) +		if (copy_shminfo_to_user(buf, &shminfo, version))  			return -EFAULT;  		down_read(&shm_ids(ns).rwsem);  		err = ipc_get_maxid(&shm_ids(ns));  		up_read(&shm_ids(ns).rwsem); -		if(err<0) +		if (err < 0)  			err = 0;  		goto out;  	} @@ -854,7 +867,7 @@ static int shmctl_nolock(struct ipc_namespace *ns, int shmid,  		memset(&shm_info, 0, sizeof(shm_info));  		down_read(&shm_ids(ns).rwsem);  		shm_info.used_ids = shm_ids(ns).in_use; -		shm_get_stat (ns, &shm_info.shm_rss, &shm_info.shm_swp); +		shm_get_stat(ns, &shm_info.shm_rss, &shm_info.shm_swp);  		shm_info.shm_tot = ns->shm_tot;  		shm_info.swap_attempts = 0;  		shm_info.swap_successes = 0; @@ -965,14 +978,24 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)  			goto out_unlock1;  		ipc_lock_object(&shp->shm_perm); + +		/* check if shm_destroy() is tearing down shp */ +		if (!ipc_valid_object(&shp->shm_perm)) { +			err = -EIDRM; +			goto out_unlock0; +		} +  		if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) {  			kuid_t euid = current_euid(); -			err = -EPERM;  			if (!uid_eq(euid, shp->shm_perm.uid) && -			    !uid_eq(euid, shp->shm_perm.cuid)) +			    !uid_eq(euid, shp->shm_perm.cuid)) { +				err = -EPERM;  				goto out_unlock0; -			if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK)) +			} +			if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK)) { +				err = -EPERM;  				goto out_unlock0; +			}  		}  		shm_file = shp->shm_file; @@ -1027,7 +1050,7 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,  	struct shmid_kernel *shp;  	unsigned long addr;  	unsigned long size; -	struct file * file; +	struct file *file;  	int    err;  	unsigned long flags;  	unsigned long prot; @@ -1094,6 +1117,14 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,  		goto out_unlock;  	ipc_lock_object(&shp->shm_perm); + +	/* check if shm_destroy() is tearing down shp */ +	if (!ipc_valid_object(&shp->shm_perm)) { +		ipc_unlock_object(&shp->shm_perm); +		err = -EIDRM; +		goto out_unlock; +	} +  	path = shp->shm_file->f_path;  	path_get(&path);  	shp->shm_nattch++; @@ -1133,6 +1164,9 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,  	down_write(¤t->mm->mmap_sem);  	if (addr && !(shmflg & SHM_REMAP)) {  		err = -EINVAL; +		if (addr + size < addr) +			goto invalid; +  		if (find_vma_intersection(current->mm, addr, addr + size))  			goto invalid;  		/*  | 
