Merge branch 'work.ipc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[muen/linux.git] / ipc / shm.c
index 8fc97beb52348a4c0fb140b7b3377d3c4fa38b2f..1b3adfe3c60e259e7366aa76a23b14e7c1a173b5 100644 (file)
--- a/ipc/shm.c
+++ b/ipc/shm.c
@@ -202,7 +202,7 @@ static int __shm_open(struct vm_area_struct *vma)
        if (IS_ERR(shp))
                return PTR_ERR(shp);
 
-       shp->shm_atim = get_seconds();
+       shp->shm_atim = ktime_get_real_seconds();
        shp->shm_lprid = task_tgid_vnr(current);
        shp->shm_nattch++;
        shm_unlock(shp);
@@ -289,7 +289,7 @@ static void shm_close(struct vm_area_struct *vma)
                goto done; /* no-op */
 
        shp->shm_lprid = task_tgid_vnr(current);
-       shp->shm_dtim = get_seconds();
+       shp->shm_dtim = ktime_get_real_seconds();
        shp->shm_nattch--;
        if (shm_may_destroy(ns, shp))
                shm_destroy(ns, shp);
@@ -594,7 +594,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
        shp->shm_cprid = task_tgid_vnr(current);
        shp->shm_lprid = 0;
        shp->shm_atim = shp->shm_dtim = 0;
-       shp->shm_ctim = get_seconds();
+       shp->shm_ctim = ktime_get_real_seconds();
        shp->shm_segsz = size;
        shp->shm_nattch = 0;
        shp->shm_file = file;
@@ -815,23 +815,17 @@ static void shm_get_stat(struct ipc_namespace *ns, unsigned long *rss,
  * NOTE: no locks must be held, the rwsem is taken inside this function.
  */
 static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
-                      struct shmid_ds __user *buf, int version)
+                      struct shmid64_ds *shmid64)
 {
        struct kern_ipc_perm *ipcp;
-       struct shmid64_ds shmid64;
        struct shmid_kernel *shp;
        int err;
 
-       if (cmd == IPC_SET) {
-               if (copy_shmid_from_user(&shmid64, buf, version))
-                       return -EFAULT;
-       }
-
        down_write(&shm_ids(ns).rwsem);
        rcu_read_lock();
 
        ipcp = ipcctl_pre_down_nolock(ns, &shm_ids(ns), shmid, cmd,
-                                     &shmid64.shm_perm, 0);
+                                     &shmid64->shm_perm, 0);
        if (IS_ERR(ipcp)) {
                err = PTR_ERR(ipcp);
                goto out_unlock1;
@@ -851,10 +845,10 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
                goto out_up;
        case IPC_SET:
                ipc_lock_object(&shp->shm_perm);
-               err = ipc_update_perm(&shmid64.shm_perm, ipcp);
+               err = ipc_update_perm(&shmid64->shm_perm, ipcp);
                if (err)
                        goto out_unlock0;
-               shp->shm_ctim = get_seconds();
+               shp->shm_ctim = ktime_get_real_seconds();
                break;
        default:
                err = -EINVAL;
@@ -870,125 +864,175 @@ out_up:
        return err;
 }
 
-static int shmctl_nolock(struct ipc_namespace *ns, int shmid,
-                        int cmd, int version, void __user *buf)
+static int shmctl_ipc_info(struct ipc_namespace *ns,
+                          struct shminfo64 *shminfo)
 {
-       int err;
-       struct shmid_kernel *shp;
-
-       /* preliminary security checks for *_INFO */
-       if (cmd == IPC_INFO || cmd == SHM_INFO) {
-               err = security_shm_shmctl(NULL, cmd);
-               if (err)
-                       return err;
-       }
-
-       switch (cmd) {
-       case IPC_INFO:
-       {
-               struct shminfo64 shminfo;
-
-               memset(&shminfo, 0, sizeof(shminfo));
-               shminfo.shmmni = shminfo.shmseg = ns->shm_ctlmni;
-               shminfo.shmmax = ns->shm_ctlmax;
-               shminfo.shmall = ns->shm_ctlall;
-
-               shminfo.shmmin = SHMMIN;
-               if (copy_shminfo_to_user(buf, &shminfo, version))
-                       return -EFAULT;
-
+       int err = security_shm_shmctl(NULL, IPC_INFO);
+       if (!err) {
+               memset(shminfo, 0, sizeof(*shminfo));
+               shminfo->shmmni = shminfo->shmseg = ns->shm_ctlmni;
+               shminfo->shmmax = ns->shm_ctlmax;
+               shminfo->shmall = ns->shm_ctlall;
+               shminfo->shmmin = SHMMIN;
                down_read(&shm_ids(ns).rwsem);
                err = ipc_get_maxid(&shm_ids(ns));
                up_read(&shm_ids(ns).rwsem);
-
                if (err < 0)
                        err = 0;
-               goto out;
        }
-       case SHM_INFO:
-       {
-               struct shm_info shm_info;
+       return err;
+}
 
-               memset(&shm_info, 0, sizeof(shm_info));
+static int shmctl_shm_info(struct ipc_namespace *ns,
+                          struct shm_info *shm_info)
+{
+       int err = security_shm_shmctl(NULL, SHM_INFO);
+       if (!err) {
+               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_info.shm_tot = ns->shm_tot;
-               shm_info.swap_attempts = 0;
-               shm_info.swap_successes = 0;
+               shm_info->used_ids = shm_ids(ns).in_use;
+               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;
                err = ipc_get_maxid(&shm_ids(ns));
                up_read(&shm_ids(ns).rwsem);
-               if (copy_to_user(buf, &shm_info, sizeof(shm_info))) {
-                       err = -EFAULT;
-                       goto out;
+               if (err < 0)
+                       err = 0;
+       }
+       return err;
+}
+
+static int shmctl_stat(struct ipc_namespace *ns, int shmid,
+                       int cmd, struct shmid64_ds *tbuf)
+{
+       struct shmid_kernel *shp;
+       int result;
+       int err;
+
+       rcu_read_lock();
+       if (cmd == SHM_STAT) {
+               shp = shm_obtain_object(ns, shmid);
+               if (IS_ERR(shp)) {
+                       err = PTR_ERR(shp);
+                       goto out_unlock;
+               }
+               result = shp->shm_perm.id;
+       } else {
+               shp = shm_obtain_object_check(ns, shmid);
+               if (IS_ERR(shp)) {
+                       err = PTR_ERR(shp);
+                       goto out_unlock;
                }
+               result = 0;
+       }
 
-               err = err < 0 ? 0 : err;
-               goto out;
+       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;
+       tbuf->shm_atime = shp->shm_atim;
+       tbuf->shm_dtime = shp->shm_dtim;
+       tbuf->shm_ctime = shp->shm_ctim;
+       tbuf->shm_cpid  = shp->shm_cprid;
+       tbuf->shm_lpid  = shp->shm_lprid;
+       tbuf->shm_nattch = shp->shm_nattch;
+       rcu_read_unlock();
+       return result;
+
+out_unlock:
+       rcu_read_unlock();
+       return err;
+}
+
+static int shmctl_do_lock(struct ipc_namespace *ns, int shmid, int cmd)
+{
+       struct shmid_kernel *shp;
+       struct file *shm_file;
+       int err;
+
+       rcu_read_lock();
+       shp = shm_obtain_object_check(ns, shmid);
+       if (IS_ERR(shp)) {
+               err = PTR_ERR(shp);
+               goto out_unlock1;
        }
-       case SHM_STAT:
-       case IPC_STAT:
-       {
-               struct shmid64_ds tbuf;
-               int result;
-
-               rcu_read_lock();
-               if (cmd == SHM_STAT) {
-                       shp = shm_obtain_object(ns, shmid);
-                       if (IS_ERR(shp)) {
-                               err = PTR_ERR(shp);
-                               goto out_unlock;
-                       }
-                       result = shp->shm_perm.id;
-               } else {
-                       shp = shm_obtain_object_check(ns, shmid);
-                       if (IS_ERR(shp)) {
-                               err = PTR_ERR(shp);
-                               goto out_unlock;
-                       }
-                       result = 0;
-               }
 
-               err = -EACCES;
-               if (ipcperms(ns, &shp->shm_perm, S_IRUGO))
-                       goto out_unlock;
+       audit_ipc_obj(&(shp->shm_perm));
+       err = security_shm_shmctl(shp, cmd);
+       if (err)
+               goto out_unlock1;
 
-               err = security_shm_shmctl(shp, cmd);
-               if (err)
-                       goto out_unlock;
+       ipc_lock_object(&shp->shm_perm);
 
-               memset(&tbuf, 0, sizeof(tbuf));
-               kernel_to_ipc64_perm(&shp->shm_perm, &tbuf.shm_perm);
-               tbuf.shm_segsz  = shp->shm_segsz;
-               tbuf.shm_atime  = shp->shm_atim;
-               tbuf.shm_dtime  = shp->shm_dtim;
-               tbuf.shm_ctime  = shp->shm_ctim;
-               tbuf.shm_cpid   = shp->shm_cprid;
-               tbuf.shm_lpid   = shp->shm_lprid;
-               tbuf.shm_nattch = shp->shm_nattch;
-               rcu_read_unlock();
-
-               if (copy_shmid_to_user(buf, &tbuf, version))
-                       err = -EFAULT;
-               else
-                       err = result;
-               goto out;
+       /* check if shm_destroy() is tearing down shp */
+       if (!ipc_valid_object(&shp->shm_perm)) {
+               err = -EIDRM;
+               goto out_unlock0;
        }
-       default:
-               return -EINVAL;
+
+       if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) {
+               kuid_t euid = current_euid();
+
+               if (!uid_eq(euid, shp->shm_perm.uid) &&
+                   !uid_eq(euid, shp->shm_perm.cuid)) {
+                       err = -EPERM;
+                       goto out_unlock0;
+               }
+               if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK)) {
+                       err = -EPERM;
+                       goto out_unlock0;
+               }
        }
 
-out_unlock:
+       shm_file = shp->shm_file;
+       if (is_file_hugepages(shm_file))
+               goto out_unlock0;
+
+       if (cmd == SHM_LOCK) {
+               struct user_struct *user = current_user();
+
+               err = shmem_lock(shm_file, 1, user);
+               if (!err && !(shp->shm_perm.mode & SHM_LOCKED)) {
+                       shp->shm_perm.mode |= SHM_LOCKED;
+                       shp->mlock_user = user;
+               }
+               goto out_unlock0;
+       }
+
+       /* SHM_UNLOCK */
+       if (!(shp->shm_perm.mode & SHM_LOCKED))
+               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);
+       ipc_unlock_object(&shp->shm_perm);
+       rcu_read_unlock();
+       shmem_unlock_mapping(shm_file->f_mapping);
+
+       fput(shm_file);
+       return err;
+
+out_unlock0:
+       ipc_unlock_object(&shp->shm_perm);
+out_unlock1:
        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;
+       struct shmid64_ds sem64;
 
        if (cmd < 0 || shmid < 0)
                return -EINVAL;
@@ -997,92 +1041,222 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
        ns = current->nsproxy->ipc_ns;
 
        switch (cmd) {
-       case IPC_INFO:
-       case SHM_INFO:
+       case IPC_INFO: {
+               struct shminfo64 shminfo;
+               err = shmctl_ipc_info(ns, &shminfo);
+               if (err < 0)
+                       return err;
+               if (copy_shminfo_to_user(buf, &shminfo, version))
+                       err = -EFAULT;
+               return err;
+       }
+       case SHM_INFO: {
+               struct shm_info shm_info;
+               err = shmctl_shm_info(ns, &shm_info);
+               if (err < 0)
+                       return err;
+               if (copy_to_user(buf, &shm_info, sizeof(shm_info)))
+                       err = -EFAULT;
+               return err;
+       }
        case SHM_STAT:
-       case IPC_STAT:
-               return shmctl_nolock(ns, shmid, cmd, version, buf);
-       case IPC_RMID:
+       case IPC_STAT: {
+               err = shmctl_stat(ns, shmid, cmd, &sem64);
+               if (err < 0)
+                       return err;
+               if (copy_shmid_to_user(buf, &sem64, version))
+                       err = -EFAULT;
+               return err;
+       }
        case IPC_SET:
-               return shmctl_down(ns, shmid, cmd, buf, version);
+               if (copy_shmid_from_user(&sem64, buf, version))
+                       return -EFAULT;
+               /* fallthru */
+       case IPC_RMID:
+               return shmctl_down(ns, shmid, cmd, &sem64);
        case SHM_LOCK:
        case SHM_UNLOCK:
-       {
-               struct file *shm_file;
+               return shmctl_do_lock(ns, shmid, cmd);
+       default:
+               return -EINVAL;
+       }
+}
 
-               rcu_read_lock();
-               shp = shm_obtain_object_check(ns, shmid);
-               if (IS_ERR(shp)) {
-                       err = PTR_ERR(shp);
-                       goto out_unlock1;
-               }
+#ifdef CONFIG_COMPAT
+
+struct compat_shmid_ds {
+       struct compat_ipc_perm shm_perm;
+       int shm_segsz;
+       compat_time_t shm_atime;
+       compat_time_t shm_dtime;
+       compat_time_t shm_ctime;
+       compat_ipc_pid_t shm_cpid;
+       compat_ipc_pid_t shm_lpid;
+       unsigned short shm_nattch;
+       unsigned short shm_unused;
+       compat_uptr_t shm_unused2;
+       compat_uptr_t shm_unused3;
+};
 
-               audit_ipc_obj(&(shp->shm_perm));
-               err = security_shm_shmctl(shp, cmd);
-               if (err)
-                       goto out_unlock1;
+struct compat_shminfo64 {
+       compat_ulong_t shmmax;
+       compat_ulong_t shmmin;
+       compat_ulong_t shmmni;
+       compat_ulong_t shmseg;
+       compat_ulong_t shmall;
+       compat_ulong_t __unused1;
+       compat_ulong_t __unused2;
+       compat_ulong_t __unused3;
+       compat_ulong_t __unused4;
+};
 
-               ipc_lock_object(&shp->shm_perm);
+struct compat_shm_info {
+       compat_int_t used_ids;
+       compat_ulong_t shm_tot, shm_rss, shm_swp;
+       compat_ulong_t swap_attempts, swap_successes;
+};
 
-               /* check if shm_destroy() is tearing down shp */
-               if (!ipc_valid_object(&shp->shm_perm)) {
-                       err = -EIDRM;
-                       goto out_unlock0;
-               }
+static int copy_compat_shminfo_to_user(void __user *buf, struct shminfo64 *in,
+                                       int version)
+{
+       if (in->shmmax > INT_MAX)
+               in->shmmax = INT_MAX;
+       if (version == IPC_64) {
+               struct compat_shminfo64 info;
+               memset(&info, 0, sizeof(info));
+               info.shmmax = in->shmmax;
+               info.shmmin = in->shmmin;
+               info.shmmni = in->shmmni;
+               info.shmseg = in->shmseg;
+               info.shmall = in->shmall;
+               return copy_to_user(buf, &info, sizeof(info));
+       } else {
+               struct shminfo info;
+               memset(&info, 0, sizeof(info));
+               info.shmmax = in->shmmax;
+               info.shmmin = in->shmmin;
+               info.shmmni = in->shmmni;
+               info.shmseg = in->shmseg;
+               info.shmall = in->shmall;
+               return copy_to_user(buf, &info, sizeof(info));
+       }
+}
 
-               if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) {
-                       kuid_t euid = current_euid();
-
-                       if (!uid_eq(euid, shp->shm_perm.uid) &&
-                           !uid_eq(euid, shp->shm_perm.cuid)) {
-                               err = -EPERM;
-                               goto out_unlock0;
-                       }
-                       if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK)) {
-                               err = -EPERM;
-                               goto out_unlock0;
-                       }
-               }
+static int put_compat_shm_info(struct shm_info *ip,
+                               struct compat_shm_info __user *uip)
+{
+       struct compat_shm_info info;
+
+       memset(&info, 0, sizeof(info));
+       info.used_ids = ip->used_ids;
+       info.shm_tot = ip->shm_tot;
+       info.shm_rss = ip->shm_rss;
+       info.shm_swp = ip->shm_swp;
+       info.swap_attempts = ip->swap_attempts;
+       info.swap_successes = ip->swap_successes;
+       return copy_to_user(up, &info, sizeof(info));
+}
 
-               shm_file = shp->shm_file;
-               if (is_file_hugepages(shm_file))
-                       goto out_unlock0;
+static int copy_compat_shmid_to_user(void __user *buf, struct shmid64_ds *in,
+                                       int version)
+{
+       if (version == IPC_64) {
+               struct compat_shmid64_ds v;
+               memset(&v, 0, sizeof(v));
+               to_compat_ipc64_perm(&v.shm_perm, &in->shm_perm);
+               v.shm_atime = in->shm_atime;
+               v.shm_dtime = in->shm_dtime;
+               v.shm_ctime = in->shm_ctime;
+               v.shm_segsz = in->shm_segsz;
+               v.shm_nattch = in->shm_nattch;
+               v.shm_cpid = in->shm_cpid;
+               v.shm_lpid = in->shm_lpid;
+               return copy_to_user(buf, &v, sizeof(v));
+       } else {
+               struct compat_shmid_ds v;
+               memset(&v, 0, sizeof(v));
+               to_compat_ipc_perm(&v.shm_perm, &in->shm_perm);
+               v.shm_perm.key = in->shm_perm.key;
+               v.shm_atime = in->shm_atime;
+               v.shm_dtime = in->shm_dtime;
+               v.shm_ctime = in->shm_ctime;
+               v.shm_segsz = in->shm_segsz;
+               v.shm_nattch = in->shm_nattch;
+               v.shm_cpid = in->shm_cpid;
+               v.shm_lpid = in->shm_lpid;
+               return copy_to_user(buf, &v, sizeof(v));
+       }
+}
 
-               if (cmd == SHM_LOCK) {
-                       struct user_struct *user = current_user();
+static int copy_compat_shmid_from_user(struct shmid64_ds *out, void __user *buf,
+                                       int version)
+{
+       memset(out, 0, sizeof(*out));
+       if (version == IPC_64) {
+               struct compat_shmid64_ds *p = buf;
+               return get_compat_ipc64_perm(&out->shm_perm, &p->shm_perm);
+       } else {
+               struct compat_shmid_ds *p = buf;
+               return get_compat_ipc_perm(&out->shm_perm, &p->shm_perm);
+       }
+}
 
-                       err = shmem_lock(shm_file, 1, user);
-                       if (!err && !(shp->shm_perm.mode & SHM_LOCKED)) {
-                               shp->shm_perm.mode |= SHM_LOCKED;
-                               shp->mlock_user = user;
-                       }
-                       goto out_unlock0;
-               }
+COMPAT_SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, void __user *, uptr)
+{
+       struct ipc_namespace *ns;
+       struct shmid64_ds sem64;
+       int version = compat_ipc_parse_version(&cmd);
+       int err;
 
-               /* SHM_UNLOCK */
-               if (!(shp->shm_perm.mode & SHM_LOCKED))
-                       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);
-               ipc_unlock_object(&shp->shm_perm);
-               rcu_read_unlock();
-               shmem_unlock_mapping(shm_file->f_mapping);
+       ns = current->nsproxy->ipc_ns;
+
+       if (cmd < 0 || shmid < 0)
+               return -EINVAL;
 
-               fput(shm_file);
+       switch (cmd) {
+       case IPC_INFO: {
+               struct shminfo64 shminfo;
+               err = shmctl_ipc_info(ns, &shminfo);
+               if (err < 0)
+                       return err;
+               if (copy_compat_shminfo_to_user(uptr, &shminfo, version))
+                       err = -EFAULT;
+               return err;
+       }
+       case SHM_INFO: {
+               struct shm_info shm_info;
+               err = shmctl_shm_info(ns, &shm_info);
+               if (err < 0)
+                       return err;
+               if (put_compat_shm_info(&shm_info, uptr))
+                       err = -EFAULT;
                return err;
        }
+       case IPC_STAT:
+       case SHM_STAT:
+               err = shmctl_stat(ns, shmid, cmd, &sem64);
+               if (err < 0)
+                       return err;
+               if (copy_compat_shmid_to_user(&sem64, uptr, version))
+                       err = -EFAULT;
+               return err;
+
+       case IPC_SET:
+               if (copy_compat_shmid_from_user(&sem64, uptr, version))
+                       return -EFAULT;
+               /* fallthru */
+       case IPC_RMID:
+               return shmctl_down(ns, shmid, cmd, &sem64);
+       case SHM_LOCK:
+       case SHM_UNLOCK:
+               return shmctl_do_lock(ns, shmid, cmd);
+               break;
        default:
                return -EINVAL;
        }
-
-out_unlock0:
-       ipc_unlock_object(&shp->shm_perm);
-out_unlock1:
-       rcu_read_unlock();
        return err;
 }
+#endif
 
 /*
  * Fix shmaddr, allocate descriptor, map shm, add attach descriptor to lists.
@@ -1267,6 +1441,25 @@ SYSCALL_DEFINE3(shmat, int, shmid, char __user *, shmaddr, int, shmflg)
        return (long)ret;
 }
 
+#ifdef CONFIG_COMPAT
+
+#ifndef COMPAT_SHMLBA
+#define COMPAT_SHMLBA  SHMLBA
+#endif
+
+COMPAT_SYSCALL_DEFINE3(shmat, int, shmid, compat_uptr_t, shmaddr, int, shmflg)
+{
+       unsigned long ret;
+       long err;
+
+       err = do_shmat(shmid, compat_ptr(shmaddr), shmflg, &ret, COMPAT_SHMLBA);
+       if (err)
+               return err;
+       force_successful_syscall_return();
+       return (long)ret;
+}
+#endif
+
 /*
  * detach and kill segment if marked destroyed.
  * The work is done in shm_close.
@@ -1397,7 +1590,7 @@ static int sysvipc_shm_proc_show(struct seq_file *s, void *it)
 
        seq_printf(s,
                   "%10d %10d  %4o " SIZE_SPEC " %5u %5u  "
-                  "%5lu %5u %5u %5u %5u %10lu %10lu %10lu "
+                  "%5lu %5u %5u %5u %5u %10llu %10llu %10llu "
                   SIZE_SPEC " " SIZE_SPEC "\n",
                   shp->shm_perm.key,
                   shp->shm_perm.id,