ipc,shm: cleanup do_shmat pasta
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / ipc / shm.c
index 34af1fe34701afd06678ea83a47649b1947a4a65..1eeef9f481542cff5f5a190ef07c0880e6b99b00 100644 (file)
--- a/ipc/shm.c
+++ b/ipc/shm.c
@@ -124,6 +124,26 @@ void __init shm_init (void)
                                IPC_SHM_IDS, sysvipc_shm_proc_show);
 }
 
+static inline struct shmid_kernel *shm_obtain_object(struct ipc_namespace *ns, int id)
+{
+       struct kern_ipc_perm *ipcp = ipc_obtain_object(&shm_ids(ns), id);
+
+       if (IS_ERR(ipcp))
+               return ERR_CAST(ipcp);
+
+       return container_of(ipcp, struct shmid_kernel, shm_perm);
+}
+
+static inline struct shmid_kernel *shm_obtain_object_check(struct ipc_namespace *ns, int id)
+{
+       struct kern_ipc_perm *ipcp = ipc_obtain_object_check(&shm_ids(ns), id);
+
+       if (IS_ERR(ipcp))
+               return ERR_CAST(ipcp);
+
+       return container_of(ipcp, struct shmid_kernel, shm_perm);
+}
+
 /*
  * shm_lock_(check_) routines are called in the paths where the rw_mutex
  * is not necessarily held.
@@ -141,7 +161,7 @@ static inline struct shmid_kernel *shm_lock(struct ipc_namespace *ns, int id)
 static inline void shm_lock_by_ptr(struct shmid_kernel *ipcp)
 {
        rcu_read_lock();
-       spin_lock(&ipcp->shm_perm.lock);
+       ipc_lock_object(&ipcp->shm_perm);
 }
 
 static inline struct shmid_kernel *shm_lock_check(struct ipc_namespace *ns,
@@ -493,7 +513,13 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
        if (shmflg & SHM_HUGETLB) {
                struct hstate *hs = hstate_sizelog((shmflg >> SHM_HUGE_SHIFT)
                                                & SHM_HUGE_MASK);
-               size_t hugesize = ALIGN(size, huge_page_size(hs));
+               size_t hugesize;
+
+               if (!hs) {
+                       error = -EINVAL;
+                       goto no_file;
+               }
+               hugesize = ALIGN(size, huge_page_size(hs));
 
                /* hugetlb_file_setup applies strict accounting */
                if (shmflg & SHM_NORESERVE)
@@ -529,6 +555,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
        shp->shm_nattch = 0;
        shp->shm_file = file;
        shp->shm_creator = current;
+
        /*
         * shmid gets reported as "inode#" in /proc/pid/maps.
         * proc-ps tools use this. Changing this will break them.
@@ -537,7 +564,9 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
 
        ns->shm_tot += numpages;
        error = shp->shm_perm.id;
-       shm_unlock(shp);
+
+       ipc_unlock_object(&shp->shm_perm);
+       rcu_read_unlock();
        return error;
 
 no_id:
@@ -748,59 +777,67 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
                        return -EFAULT;
        }
 
-       ipcp = ipcctl_pre_down(ns, &shm_ids(ns), shmid, cmd,
-                              &shmid64.shm_perm, 0);
-       if (IS_ERR(ipcp))
-               return PTR_ERR(ipcp);
+       down_write(&shm_ids(ns).rw_mutex);
+       rcu_read_lock();
+
+       ipcp = ipcctl_pre_down_nolock(ns, &shm_ids(ns), shmid, cmd,
+                                     &shmid64.shm_perm, 0);
+       if (IS_ERR(ipcp)) {
+               err = PTR_ERR(ipcp);
+               goto out_unlock1;
+       }
 
        shp = container_of(ipcp, struct shmid_kernel, shm_perm);
 
        err = security_shm_shmctl(shp, cmd);
        if (err)
-               goto out_unlock;
+               goto out_unlock1;
+
        switch (cmd) {
        case IPC_RMID:
+               ipc_lock_object(&shp->shm_perm);
+               /* do_shm_rmid unlocks the ipc object and rcu */
                do_shm_rmid(ns, ipcp);
                goto out_up;
        case IPC_SET:
+               ipc_lock_object(&shp->shm_perm);
                err = ipc_update_perm(&shmid64.shm_perm, ipcp);
                if (err)
-                       goto out_unlock;
+                       goto out_unlock0;
                shp->shm_ctim = get_seconds();
                break;
        default:
                err = -EINVAL;
+               goto out_unlock1;
        }
-out_unlock:
-       shm_unlock(shp);
+
+out_unlock0:
+       ipc_unlock_object(&shp->shm_perm);
+out_unlock1:
+       rcu_read_unlock();
 out_up:
        up_write(&shm_ids(ns).rw_mutex);
        return err;
 }
 
-SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
+static int shmctl_nolock(struct ipc_namespace *ns, int shmid,
+                        int cmd, int version, void __user *buf)
 {
+       int err;
        struct shmid_kernel *shp;
-       int err, version;
-       struct ipc_namespace *ns;
 
-       if (cmd < 0 || shmid < 0) {
-               err = -EINVAL;
-               goto out;
+       /* preliminary security checks for *_INFO */
+       if (cmd == IPC_INFO || cmd == SHM_INFO) {
+               err = security_shm_shmctl(NULL, cmd);
+               if (err)
+                       return err;
        }
 
-       version = ipc_parse_version(&cmd);
-       ns = current->nsproxy->ipc_ns;
-
-       switch (cmd) { /* replace with proc interface ? */
+       switch (cmd) {
        case IPC_INFO:
        {
                struct shminfo64 shminfo;
 
-               err = security_shm_shmctl(NULL, cmd);
-               if (err)
-                       return err;
-
                memset(&shminfo, 0, sizeof(shminfo));
                shminfo.shmmni = shminfo.shmseg = ns->shm_ctlmni;
                shminfo.shmmax = ns->shm_ctlmax;
@@ -822,10 +859,6 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
        {
                struct shm_info shm_info;
 
-               err = security_shm_shmctl(NULL, cmd);
-               if (err)
-                       return err;
-
                memset(&shm_info, 0, sizeof(shm_info));
                down_read(&shm_ids(ns).rw_mutex);
                shm_info.used_ids = shm_ids(ns).in_use;
@@ -849,27 +882,31 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
                struct shmid64_ds tbuf;
                int result;
 
+               rcu_read_lock();
                if (cmd == SHM_STAT) {
-                       shp = shm_lock(ns, shmid);
+                       shp = shm_obtain_object(ns, shmid);
                        if (IS_ERR(shp)) {
                                err = PTR_ERR(shp);
-                               goto out;
+                               goto out_unlock;
                        }
                        result = shp->shm_perm.id;
                } else {
-                       shp = shm_lock_check(ns, shmid);
+                       shp = shm_obtain_object_check(ns, shmid);
                        if (IS_ERR(shp)) {
                                err = PTR_ERR(shp);
-                               goto out;
+                               goto out_unlock;
                        }
                        result = 0;
                }
+
                err = -EACCES;
                if (ipcperms(ns, &shp->shm_perm, S_IRUGO))
                        goto out_unlock;
+
                err = security_shm_shmctl(shp, cmd);
                if (err)
                        goto out_unlock;
+
                memset(&tbuf, 0, sizeof(tbuf));
                kernel_to_ipc64_perm(&shp->shm_perm, &tbuf.shm_perm);
                tbuf.shm_segsz  = shp->shm_segsz;
@@ -879,43 +916,76 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
                tbuf.shm_cpid   = shp->shm_cprid;
                tbuf.shm_lpid   = shp->shm_lprid;
                tbuf.shm_nattch = shp->shm_nattch;
-               shm_unlock(shp);
-               if(copy_shmid_to_user (buf, &tbuf, version))
+               rcu_read_unlock();
+
+               if (copy_shmid_to_user(buf, &tbuf, version))
                        err = -EFAULT;
                else
                        err = result;
                goto out;
        }
+       default:
+               return -EINVAL;
+       }
+
+out_unlock:
+       rcu_read_unlock();
+out:
+       return err;
+}
+
+SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
+{
+       struct shmid_kernel *shp;
+       int err, version;
+       struct ipc_namespace *ns;
+
+       if (cmd < 0 || shmid < 0)
+               return -EINVAL;
+
+       version = ipc_parse_version(&cmd);
+       ns = current->nsproxy->ipc_ns;
+
+       switch (cmd) {
+       case IPC_INFO:
+       case SHM_INFO:
+       case SHM_STAT:
+       case IPC_STAT:
+               return shmctl_nolock(ns, shmid, cmd, version, buf);
+       case IPC_RMID:
+       case IPC_SET:
+               return shmctl_down(ns, shmid, cmd, buf, version);
        case SHM_LOCK:
        case SHM_UNLOCK:
        {
                struct file *shm_file;
 
-               shp = shm_lock_check(ns, shmid);
+               rcu_read_lock();
+               shp = shm_obtain_object_check(ns, shmid);
                if (IS_ERR(shp)) {
                        err = PTR_ERR(shp);
-                       goto out;
+                       goto out_unlock1;
                }
 
                audit_ipc_obj(&(shp->shm_perm));
+               err = security_shm_shmctl(shp, cmd);
+               if (err)
+                       goto out_unlock1;
 
+               ipc_lock_object(&shp->shm_perm);
                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))
-                               goto out_unlock;
+                               goto out_unlock0;
                        if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK))
-                               goto out_unlock;
+                               goto out_unlock0;
                }
 
-               err = security_shm_shmctl(shp, cmd);
-               if (err)
-                       goto out_unlock;
-
                shm_file = shp->shm_file;
                if (is_file_hugepages(shm_file))
-                       goto out_unlock;
+                       goto out_unlock0;
 
                if (cmd == SHM_LOCK) {
                        struct user_struct *user = current_user();
@@ -924,32 +994,31 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
                                shp->shm_perm.mode |= SHM_LOCKED;
                                shp->mlock_user = user;
                        }
-                       goto out_unlock;
+                       goto out_unlock0;
                }
 
                /* SHM_UNLOCK */
                if (!(shp->shm_perm.mode & SHM_LOCKED))
-                       goto out_unlock;
+                       goto out_unlock0;
                shmem_lock(shm_file, 0, shp->mlock_user);
                shp->shm_perm.mode &= ~SHM_LOCKED;
                shp->mlock_user = NULL;
                get_file(shm_file);
-               shm_unlock(shp);
+               ipc_unlock_object(&shp->shm_perm);
+               rcu_read_unlock();
                shmem_unlock_mapping(shm_file->f_mapping);
+
                fput(shm_file);
-               goto out;
-       }
-       case IPC_RMID:
-       case IPC_SET:
-               err = shmctl_down(ns, shmid, cmd, buf, version);
                return err;
+       }
        default:
                return -EINVAL;
        }
 
-out_unlock:
-       shm_unlock(shp);
-out:
+out_unlock0:
+       ipc_unlock_object(&shp->shm_perm);
+out_unlock1:
+       rcu_read_unlock();
        return err;
 }
 
@@ -1039,16 +1108,21 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,
 
        err = -ENOMEM;
        sfd = kzalloc(sizeof(*sfd), GFP_KERNEL);
-       if (!sfd)
-               goto out_put_dentry;
+       if (!sfd) {
+               path_put(&path);
+               goto out_nattch;
+       }
 
        file = alloc_file(&path, f_mode,
                          is_file_hugepages(shp->shm_file) ?
                                &shm_file_operations_huge :
                                &shm_file_operations);
        err = PTR_ERR(file);
-       if (IS_ERR(file))
-               goto out_free;
+       if (IS_ERR(file)) {
+               kfree(sfd);
+               path_put(&path);
+               goto out_nattch;
+       }
 
        file->private_data = sfd;
        file->f_mapping = shp->shm_file->f_mapping;
@@ -1074,7 +1148,7 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,
                    addr > current->mm->start_stack - size - PAGE_SIZE * 5)
                        goto invalid;
        }
-               
+
        addr = do_mmap_pgoff(file, addr, size, prot, flags, 0, &populate);
        *raddr = addr;
        err = 0;
@@ -1098,19 +1172,12 @@ out_nattch:
        else
                shm_unlock(shp);
        up_write(&shm_ids(ns).rw_mutex);
-
-out:
        return err;
 
 out_unlock:
        shm_unlock(shp);
-       goto out;
-
-out_free:
-       kfree(sfd);
-out_put_dentry:
-       path_put(&path);
-       goto out_nattch;
+out:
+       return err;
 }
 
 SYSCALL_DEFINE3(shmat, int, shmid, char __user *, shmaddr, int, shmflg)