ipc/shm: fix shmat() nil address after round-down when remapping
[sfrench/cifs-2.6.git] / ipc / shm.c
index acefe44fefefa187c838c3830d944b5d3a4c047d..d73269381ec7ed831eaa65e544aa8b5ab4e504dd 100644 (file)
--- a/ipc/shm.c
+++ b/ipc/shm.c
@@ -225,6 +225,12 @@ static int __shm_open(struct vm_area_struct *vma)
        if (IS_ERR(shp))
                return PTR_ERR(shp);
 
+       if (shp->shm_file != sfd->file) {
+               /* ID was reused */
+               shm_unlock(shp);
+               return -EINVAL;
+       }
+
        shp->shm_atim = ktime_get_real_seconds();
        ipc_update_pid(&shp->shm_lprid, task_tgid(current));
        shp->shm_nattch++;
@@ -415,7 +421,7 @@ static int shm_split(struct vm_area_struct *vma, unsigned long addr)
        struct file *file = vma->vm_file;
        struct shm_file_data *sfd = shm_file_data(file);
 
-       if (sfd->vm_ops && sfd->vm_ops->split)
+       if (sfd->vm_ops->split)
                return sfd->vm_ops->split(vma, addr);
 
        return 0;
@@ -455,8 +461,9 @@ static int shm_mmap(struct file *file, struct vm_area_struct *vma)
        int ret;
 
        /*
-        * In case of remap_file_pages() emulation, the file can represent
-        * removed IPC ID: propogate shm_lock() error to caller.
+        * In case of remap_file_pages() emulation, the file can represent an
+        * IPC ID that was removed, and possibly even reused by another shm
+        * segment already.  Propagate this case as an error to caller.
         */
        ret = __shm_open(vma);
        if (ret)
@@ -480,6 +487,7 @@ static int shm_release(struct inode *ino, struct file *file)
        struct shm_file_data *sfd = shm_file_data(file);
 
        put_ipc_ns(sfd->ns);
+       fput(sfd->file);
        shm_file_data(file) = NULL;
        kfree(sfd);
        return 0;
@@ -947,14 +955,14 @@ static int shmctl_stat(struct ipc_namespace *ns, int shmid,
        memset(tbuf, 0, sizeof(*tbuf));
 
        rcu_read_lock();
-       if (cmd == SHM_STAT) {
+       if (cmd == SHM_STAT || cmd == SHM_STAT_ANY) {
                shp = shm_obtain_object(ns, shmid);
                if (IS_ERR(shp)) {
                        err = PTR_ERR(shp);
                        goto out_unlock;
                }
                id = shp->shm_perm.id;
-       } else {
+       } else { /* IPC_STAT */
                shp = shm_obtain_object_check(ns, shmid);
                if (IS_ERR(shp)) {
                        err = PTR_ERR(shp);
@@ -962,9 +970,20 @@ static int shmctl_stat(struct ipc_namespace *ns, int shmid,
                }
        }
 
-       err = -EACCES;
-       if (ipcperms(ns, &shp->shm_perm, S_IRUGO))
-               goto out_unlock;
+       /*
+        * Semantically SHM_STAT_ANY ought to be identical to
+        * that functionality provided by the /proc/sysvipc/
+        * interface. As such, only audit these calls and
+        * do not do traditional S_IRUGO permission checks on
+        * the ipc object.
+        */
+       if (cmd == SHM_STAT_ANY)
+               audit_ipc_obj(&shp->shm_perm);
+       else {
+               err = -EACCES;
+               if (ipcperms(ns, &shp->shm_perm, S_IRUGO))
+                       goto out_unlock;
+       }
 
        err = security_shm_shmctl(&shp->shm_perm, cmd);
        if (err)
@@ -1104,6 +1123,7 @@ long ksys_shmctl(int shmid, int cmd, struct shmid_ds __user *buf)
                return err;
        }
        case SHM_STAT:
+       case SHM_STAT_ANY:
        case IPC_STAT: {
                err = shmctl_stat(ns, shmid, cmd, &sem64);
                if (err < 0)
@@ -1282,6 +1302,7 @@ long compat_ksys_shmctl(int shmid, int cmd, void __user *uptr)
                return err;
        }
        case IPC_STAT:
+       case SHM_STAT_ANY:
        case SHM_STAT:
                err = shmctl_stat(ns, shmid, cmd, &sem64);
                if (err < 0)
@@ -1342,14 +1363,17 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg,
 
        if (addr) {
                if (addr & (shmlba - 1)) {
-                       /*
-                        * Round down to the nearest multiple of shmlba.
-                        * For sane do_mmap_pgoff() parameters, avoid
-                        * round downs that trigger nil-page and MAP_FIXED.
-                        */
-                       if ((shmflg & SHM_RND) && addr >= shmlba)
-                               addr &= ~(shmlba - 1);
-                       else
+                       if (shmflg & SHM_RND) {
+                               addr &= ~(shmlba - 1);  /* round down */
+
+                               /*
+                                * Ensure that the round-down is non-nil
+                                * when remapping. This can happen for
+                                * cases when addr < shmlba.
+                                */
+                               if (!addr && (shmflg & SHM_REMAP))
+                                       goto out;
+                       } else
 #ifndef __ARCH_FORCE_SHMLBA
                                if (addr & ~PAGE_MASK)
 #endif
@@ -1432,7 +1456,16 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg,
        file->f_mapping = shp->shm_file->f_mapping;
        sfd->id = shp->shm_perm.id;
        sfd->ns = get_ipc_ns(ns);
-       sfd->file = shp->shm_file;
+       /*
+        * We need to take a reference to the real shm file to prevent the
+        * pointer from becoming stale in cases where the lifetime of the outer
+        * file extends beyond that of the shm segment.  It's not usually
+        * possible, but it can happen during remap_file_pages() emulation as
+        * that unmaps the memory, then does ->mmap() via file reference only.
+        * We'll deny the ->mmap() if the shm segment was since removed, but to
+        * detect shm ID reuse we need to compare the file pointers.
+        */
+       sfd->file = get_file(shp->shm_file);
        sfd->vm_ops = NULL;
 
        err = security_mmap_file(file, prot, flags);