*
  * This changes mm's executable file (shown as symlink /proc/[pid]/exe).
  *
- * Main users are mmput(), sys_execve() and sys_prctl(PR_SET_MM_MAP/EXE_FILE).
- * Callers prevent concurrent invocations: in mmput() nobody alive left,
- * in execve task is single-threaded, prctl holds mmap_sem exclusively.
+ * Main users are mmput() and sys_execve(). Callers prevent concurrent
+ * invocations: in mmput() nobody alive left, in execve task is single
+ * threaded. sys_prctl(PR_SET_MM_MAP/EXE_FILE) also needs to set the
+ * mm->exe_file, but does so without using set_mm_exe_file() in order
+ * to do avoid the need for any locks.
  */
 void set_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file)
 {
-       struct file *old_exe_file = rcu_dereference_protected(mm->exe_file,
-                       !atomic_read(&mm->mm_users) || current->in_execve ||
-                       lockdep_is_held(&mm->mmap_sem));
+       struct file *old_exe_file;
+
+       /*
+        * It is safe to dereference the exe_file without RCU as
+        * this function is only called if nobody else can access
+        * this mm -- see comment above for justification.
+        */
+       old_exe_file = rcu_dereference_raw(mm->exe_file);
 
        if (new_exe_file)
                get_file(new_exe_file);
 
        return mask;
 }
 
-static int prctl_set_mm_exe_file_locked(struct mm_struct *mm, unsigned int fd)
+static int prctl_set_mm_exe_file(struct mm_struct *mm, unsigned int fd)
 {
        struct fd exe;
+       struct file *old_exe, *exe_file;
        struct inode *inode;
        int err;
 
-       VM_BUG_ON_MM(!rwsem_is_locked(&mm->mmap_sem), mm);
-
        exe = fdget(fd);
        if (!exe.file)
                return -EBADF;
        /*
         * Forbid mm->exe_file change if old file still mapped.
         */
+       exe_file = get_mm_exe_file(mm);
        err = -EBUSY;
-       if (mm->exe_file) {
+       if (exe_file) {
                struct vm_area_struct *vma;
 
-               for (vma = mm->mmap; vma; vma = vma->vm_next)
-                       if (vma->vm_file &&
-                           path_equal(&vma->vm_file->f_path,
-                                      &mm->exe_file->f_path))
-                               goto exit;
+               down_read(&mm->mmap_sem);
+               for (vma = mm->mmap; vma; vma = vma->vm_next) {
+                       if (!vma->vm_file)
+                               continue;
+                       if (path_equal(&vma->vm_file->f_path,
+                                      &exe_file->f_path))
+                               goto exit_err;
+               }
+
+               up_read(&mm->mmap_sem);
+               fput(exe_file);
        }
 
        /*
                goto exit;
 
        err = 0;
-       set_mm_exe_file(mm, exe.file);  /* this grabs a reference to exe.file */
+       /* set the new file, lockless */
+       get_file(exe.file);
+       old_exe = xchg(&mm->exe_file, exe.file);
+       if (old_exe)
+               fput(old_exe);
 exit:
        fdput(exe);
        return err;
+exit_err:
+       up_read(&mm->mmap_sem);
+       fput(exe_file);
+       goto exit;
 }
 
 #ifdef CONFIG_CHECKPOINT_RESTORE
                user_auxv[AT_VECTOR_SIZE - 1] = AT_NULL;
        }
 
-       down_write(&mm->mmap_sem);
        if (prctl_map.exe_fd != (u32)-1)
-               error = prctl_set_mm_exe_file_locked(mm, prctl_map.exe_fd);
-       downgrade_write(&mm->mmap_sem);
+               error = prctl_set_mm_exe_file(mm, prctl_map.exe_fd);
+       down_read(&mm->mmap_sem);
        if (error)
                goto out;
 
        if (!capable(CAP_SYS_RESOURCE))
                return -EPERM;
 
-       if (opt == PR_SET_MM_EXE_FILE) {
-               down_write(&mm->mmap_sem);
-               error = prctl_set_mm_exe_file_locked(mm, (unsigned int)addr);
-               up_write(&mm->mmap_sem);
-               return error;
-       }
+       if (opt == PR_SET_MM_EXE_FILE)
+               return prctl_set_mm_exe_file(mm, (unsigned int)addr);
 
        if (addr >= TASK_SIZE || addr < mmap_min_addr)
                return -EINVAL;