Skip to contentSkip to conversation

mm: fix lockdep build in rcu-protected get_mm_exe_file()

PATCH In review Konstantin Khlebnikov 4 participants 14 messages Mar 20, 2015 – Mar 25, 2015

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 mm: fix lockdep build in rcu-protected get_mm_exe_file()

---
 kernel/fork.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/fork.c +1 -1
index a7c596517bd6..aa2ba1a34ce8 100644
@@ -696,7 +696,7 @@ 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 ||
-			lock_is_held(&mm->mmap_sem));
+			lockdep_is_held(&mm->mmap_sem));
 
 	if (new_exe_file)
 		get_file(new_exe_file);

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#1 lore raw

Actually, here's a patch that gets rid of that condition, per Oleg.

What do you think?

8<------------------------------------------------
Subject: [PATCH -next] prctl: avoid using mmap_sem for exe_file serialization

Oleg cleverly suggested using xchg() to set the new mm->exe_file instead of calling set_mm_exe_file() which requires some form of serialization -- mmap_sem in this case. For archs that do not have atomic rmw instructions we still fallback to a spinlock alternative, so this should always be safe. As such, we only need the mmap_sem for looking up the backing vm_file, which can be done sharing the lock. Naturally, this means we need to manually deal with both the new and old file reference counting, and we need not worry about the MMF_EXE_FILE_CHANGED bits, which can probably be deleted in the future anyway.

---
 kernel/fork.c | 11 ++++++-----
 kernel/sys.c  | 43 ++++++++++++++++++++++++++-----------------
 2 files changed, 32 insertions(+), 22 deletions(-)
kernel/fork.c +6 -5
index bea5f36..98858b5 100644
@@ -688,15 +688,16 @@ EXPORT_SYMBOL_GPL(mmput);
  *
  * This changes mm's executale 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 ||
-			lock_is_held(&mm->mmap_sem));
+			!atomic_read(&mm->mm_users) || current->in_execve);
 
 	if (new_exe_file)
 		get_file(new_exe_file);
kernel/sys.c +26 -17

87 lines not on this page · raw message

Signature · 9 lines
2.1.4



--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#2 lore raw

kernel/fork.c EXPORT_SYMBOL_GPL(mmput); · line 700

Quoted 7 lines · Davidlohr Bueso

void set_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file) {

kernel/fork.c +1 -2
 	struct file *old_exe_file = rcu_dereference_protected(mm->exe_file,
-			!atomic_read(&mm->mm_users) || current->in_execve ||
-			lock_is_held(&mm->mmap_sem));
+			!atomic_read(&mm->mm_users) || current->in_execve);

Thanks, looks correct at first glance...

But can't we remove the ->in_execve check above? and check

			atomic_read(&mm->mm_users) <= 1

instead. OK, this is subjective, I won't insist. Just current->in_execve looks a bit confusing, it means "I swear, the caller is flush_old_exec() and this mm is actualy bprm->mm".

"atomic_read(&mm->mm_users) <= 1" looks a bit more "safe". But again, I won't insist.

Oleg.

#3 lore raw
Quoted 2 lines · Oleg Nesterov

"atomic_read(&mm->mm_users) <= 1" looks a bit more "safe". But again, I won't insist.

Agreed, it is nicer to do that check, but I have no strong preference either, perhaps Konstantin or akpm do. Anyway, here's the change that can be folded in if you guys want to.

Thanks.

8<-------------------------------------------------

kernel/fork.c +1 -1
index 98858b5..0c3de2b 100644
@@ -697,7 +697,7 @@ EXPORT_SYMBOL_GPL(mmput);
 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);
+			atomic_read(&mm->mm_users) <= 1);
 
 	if (new_exe_file)
 		get_file(new_exe_file);

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#4 lore raw
In reply to Oleg Nesterov
Quoted 13 lines · Oleg Nesterov

kernel/fork.c EXPORT_SYMBOL_GPL(mmput); · line 700

Quoted 7 lines · Davidlohr Bueso

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,

kernel/fork.c +1 -2
-			!atomic_read(&mm->mm_users) || current->in_execve ||
-			lock_is_held(&mm->mmap_sem));
+			!atomic_read(&mm->mm_users) || current->in_execve);

Thanks, looks correct at first glance...

But can't we remove the ->in_execve check above? and check

Quoted 8 lines
			atomic_read(&mm->mm_users) <= 1

instead. OK, this is subjective, I won't insist. Just current->in_execve looks a bit confusing, it means "I swear, the caller is flush_old_exec() and this mm is actualy bprm->mm".

"atomic_read(&mm->mm_users) <= 1" looks a bit more "safe". But again, I won't insist.

Not so safe: this will race with get_task_mm(). A lot of proc files grab temporary reference to task mm. But this just a debug -- we can place here "true".

Quoted 3 lines

Oleg.

Signature · 7 lines
Konstantin

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#5 lore raw
Quoted 17 lines · Konstantin Khlebnikov
Quoted 15 lines · Oleg Nesterov

kernel/fork.c EXPORT_SYMBOL_GPL(mmput); · line 700

Quoted 7 lines · Davidlohr Bueso

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,

kernel/fork.c +1 -2
-			!atomic_read(&mm->mm_users) || current->in_execve ||
-			lock_is_held(&mm->mmap_sem));
+			!atomic_read(&mm->mm_users) || current->in_execve);

Thanks, looks correct at first glance...

But can't we remove the ->in_execve check above? and check

			atomic_read(&mm->mm_users) <= 1
Quoted 8 lines
Quoted 6 lines

instead. OK, this is subjective, I won't insist. Just current->in_execve looks a bit confusing, it means "I swear, the caller is flush_old_exec() and this mm is actualy bprm->mm".

"atomic_read(&mm->mm_users) <= 1" looks a bit more "safe". But again, I won't insist.

Not so safe: this will race with get_task_mm().

How?

If set_mm_exe_file() can race with get_task_mm() then we have a bug. And it will be reported ;)

Quoted 2 lines

A lot of proc files grab temporary reference to task mm. But this just a debug -- we can place here "true".

Yeees, probably rcu_dereference_raw() would be even better. set_mm_exe_file() must be called only if nobody but us can access this mm.

Oleg.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#6 lore raw
Quoted 22 lines · Oleg Nesterov
Quoted 21 lines · Konstantin Khlebnikov
Quoted 19 lines · Oleg Nesterov

kernel/fork.c EXPORT_SYMBOL_GPL(mmput); · line 700

Quoted 7 lines · Davidlohr Bueso

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,
kernel/fork.c +1 -2
-                   !atomic_read(&mm->mm_users) || current->in_execve ||
-                   lock_is_held(&mm->mmap_sem));
+                   !atomic_read(&mm->mm_users) || current->in_execve);

Thanks, looks correct at first glance...

But can't we remove the ->in_execve check above? and check

                     atomic_read(&mm->mm_users) <= 1

instead. OK, this is subjective, I won't insist. Just current->in_execve looks a bit confusing, it means "I swear, the caller is flush_old_exec() and this mm is actualy bprm->mm".

Quoted 6 lines
Quoted 4 lines
Quoted 2 lines

"atomic_read(&mm->mm_users) <= 1" looks a bit more "safe". But again, I won't insist.

Not so safe: this will race with get_task_mm().

How?

I mean rcu/lockdep debug migh race with get_task_mm() and generate false-positive warning about non-protected rcu_dereference.

Quoted 9 lines

If set_mm_exe_file() can race with get_task_mm() then we have a bug. And it will be reported ;)

Quoted 2 lines

A lot of proc files grab temporary reference to task mm. But this just a debug -- we can place here "true".

Yeees, probably rcu_dereference_raw() would be even better. set_mm_exe_file() must be called only if nobody but us can access this mm.

Yep.

Quoted 8 lines

Oleg.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#7 lore raw
Quoted 6 lines · Konstantin Khlebnikov
Quoted 4 lines · Oleg Nesterov
Quoted 4 lines
Quoted 3 lines

"atomic_read(&mm->mm_users) <= 1" looks a bit more "safe". But again, I won't insist.

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

Not so safe: this will race with get_task_mm().

How?

I mean rcu/lockdep debug migh race with get_task_mm() and generate false-positive warning about non-protected rcu_dereference.

Still can't understand, I think it can't... and if it could, then this warning would not be false positive.

Anut this doesn't matter because we seem to agree this check should go away.

Quoted 4 lines
Quoted 2 lines

Yeees, probably rcu_dereference_raw() would be even better. set_mm_exe_file() must be called only if nobody but us can access this mm.

Yep.

Great. Davidlohr will you agree?

Oleg.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#8 lore raw

PATCH v2 1/1 prctl: avoid using mmap_sem for exe_file serialization

Oleg cleverly suggested using xchg() to set the new mm->exe_file instead of calling set_mm_exe_file() which requires some form of serialization -- mmap_sem in this case. For archs that do not have atomic rmw instructions we still fallback to a spinlock alternative, so this should always be safe. As such, we only need the mmap_sem for looking up the backing vm_file, which can be done sharing the lock. Naturally, this means we need to manually deal with both the new and old file reference counting, and we need not worry about the MMF_EXE_FILE_CHANGED bits, which can probably be deleted in the future anyway.

---
Changes from v1:
- use rcu_dereference_raw()
- comment lockless use in flush_old_exec()

 fs/exec.c     |  6 ++++++
 kernel/fork.c | 19 +++++++++++++------
 kernel/sys.c  | 43 ++++++++++++++++++++++++++-----------------
 3 files changed, 45 insertions(+), 23 deletions(-)
fs/exec.c +6 -0
index 314e8d8..02bfd98 100644
@@ -1082,7 +1082,13 @@ int flush_old_exec(struct linux_binprm * bprm)
 	if (retval)
 		goto out;
 
+	/*
+	 * Must be called _before_ exec_mmap() as bprm->mm is
+	 * not visible until then. This also enables the update
+	 * to be lockless.
+	 */
 	set_mm_exe_file(bprm->mm, bprm->file);
+
 	/*
 	 * Release all of the old mmap stuff
 	 */
kernel/fork.c +13 -6
index 3847f34..347f69c 100644
@@ -688,15 +688,22 @@ EXPORT_SYMBOL_GPL(mmput);
  *
  * This changes mm's executale 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);
kernel/sys.c +26 -17

87 lines not on this page · raw message

Signature · 9 lines
2.1.4



--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#9 lore raw

Re: [PATCH v2] prctl: avoid using mmap_sem for exe_file serialization

Quoted 98 lines · Davidlohr Bueso

Oleg cleverly suggested using xchg() to set the new mm->exe_file instead of calling set_mm_exe_file() which requires some form of serialization -- mmap_sem in this case. For archs that do not have atomic rmw instructions we still fallback to a spinlock alternative, so this should always be safe. As such, we only need the mmap_sem for looking up the backing vm_file, which can be done sharing the lock. Naturally, this means we need to manually deal with both the new and old file reference counting, and we need not worry about the MMF_EXE_FILE_CHANGED bits, which can probably be deleted in the future anyway.

---
Changes from v1:
- use rcu_dereference_raw()
- comment lockless use in flush_old_exec()

  fs/exec.c     |  6 ++++++
  kernel/fork.c | 19 +++++++++++++------
  kernel/sys.c  | 43 ++++++++++++++++++++++++++-----------------
  3 files changed, 45 insertions(+), 23 deletions(-)
fs/exec.c +6 -0
index 314e8d8..02bfd98 100644
@@ -1082,7 +1082,13 @@ int flush_old_exec(struct linux_binprm * bprm)
  	if (retval)
  		goto out;
 
+	/*
+	 * Must be called _before_ exec_mmap() as bprm->mm is
+	 * not visible until then. This also enables the update
+	 * to be lockless.
+	 */
  	set_mm_exe_file(bprm->mm, bprm->file);
+
  	/*
  	 * Release all of the old mmap stuff
  	 */
kernel/fork.c +13 -6
index 3847f34..347f69c 100644
@@ -688,15 +688,22 @@ EXPORT_SYMBOL_GPL(mmput);
   *
   * This changes mm's executale 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);
kernel/sys.c +2 -3
index 3be3449..1da6b17 100644
@@ -1649,14 +1649,13 @@ SYSCALL_DEFINE1(umask, int, mask)
  	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;

kernel/sys.c static int prctl_set_mm_exe_file_locked(struct mm_struct *mm, unsigned int fd) · line 1684

Quoted 6 lines
kernel/sys.c +1 -0
@@ -1680,15 +1679,22 @@ static int prctl_set_mm_exe_file_locked(struct mm_struct *mm, unsigned int fd)
  	/*
  	 * Forbid mm->exe_file change if old file still mapped.
  	 */
+	exe_file = get_mm_exe_file(mm);
  	err = -EBUSY;
  	if (mm->exe_file) {

exe_file not mm->exe_file

kernel/sys.c static int prctl_set_mm_exe_file_locked(struct mm_struct *mm, unsigned int fd) · line 1692

Quoted 11 lines
kernel/sys.c +5 -3
  		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,
+		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,
  				       &mm->exe_file->f_path))

same here

Quoted 58 lines
kernel/sys.c +18 -11
-				goto exit;
+				goto exit_err;
+		}
+
+		up_read(&mm->mmap_sem);
+		fput(exe_file);
  	}
 
  	/*
@@ -1702,10 +1708,18 @@ static int prctl_set_mm_exe_file_locked(struct mm_struct *mm, unsigned int fd)
  		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
@@ -1840,10 +1854,9 @@ static int prctl_set_mm_map(int opt, const void __user *addr, unsigned long data
  		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;
 
@@ -1909,12 +1922,8 @@ static int prctl_set_mm(int opt, unsigned long addr,
  	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;
Signature · 7 lines
Konstantin

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#10 lore raw

PATCH v3 1/1 prctl: avoid using mmap_sem for exe_file serialization

Oleg cleverly suggested using xchg() to set the new mm->exe_file instead of calling set_mm_exe_file() which requires some form of serialization -- mmap_sem in this case. For archs that do not have atomic rmw instructions we still fallback to a spinlock alternative, so this should always be safe. As such, we only need the mmap_sem for looking up the backing vm_file, which can be done sharing the lock. Naturally, this means we need to manually deal with both the new and old file reference counting, and we need not worry about the MMF_EXE_FILE_CHANGED bits, which can probably be deleted in the future anyway.

---

Changes from v2: use correct exe_file (sigh), per Konstantin.

 fs/exec.c     |  6 ++++++
 kernel/fork.c | 19 +++++++++++++------
 kernel/sys.c  | 47 ++++++++++++++++++++++++++++-------------------
 3 files changed, 47 insertions(+), 25 deletions(-)
fs/exec.c +6 -0
index 314e8d8..02bfd98 100644
@@ -1082,7 +1082,13 @@ int flush_old_exec(struct linux_binprm * bprm)
 	if (retval)
 		goto out;
 
+	/*
+	 * Must be called _before_ exec_mmap() as bprm->mm is
+	 * not visibile until then. This also enables the update
+	 * to be lockless.
+	 */
 	set_mm_exe_file(bprm->mm, bprm->file);
+
 	/*
 	 * Release all of the old mmap stuff
 	 */
kernel/fork.c +13 -6
index 3847f34..347f69c 100644
@@ -688,15 +688,22 @@ EXPORT_SYMBOL_GPL(mmput);
  *
  * This changes mm's executale 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);
kernel/sys.c +28 -19

89 lines not on this page · raw message

Signature · 9 lines
2.1.4



--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
author Reviewed-by on v3 1/1
#11 lore raw

Re: [PATCH v3] prctl: avoid using mmap_sem for exe_file serialization

Quoted 12 lines · Davidlohr Bueso

Oleg cleverly suggested using xchg() to set the new mm->exe_file instead of calling set_mm_exe_file() which requires some form of serialization -- mmap_sem in this case. For archs that do not have atomic rmw instructions we still fallback to a spinlock alternative, so this should always be safe. As such, we only need the mmap_sem for looking up the backing vm_file, which can be done sharing the lock. Naturally, this means we need to manually deal with both the new and old file reference counting, and we need not worry about the MMF_EXE_FILE_CHANGED bits, which can probably be deleted in the future anyway.

Quoted 3 lines

If this is preparation for future rework of mmap_sem maybe we could postpone committing this patch.

Quoted 159 lines

---

Changes from v2: use correct exe_file (sigh), per Konstantin.

  fs/exec.c     |  6 ++++++
  kernel/fork.c | 19 +++++++++++++------
  kernel/sys.c  | 47 ++++++++++++++++++++++++++++-------------------
  3 files changed, 47 insertions(+), 25 deletions(-)
fs/exec.c +6 -0
index 314e8d8..02bfd98 100644
@@ -1082,7 +1082,13 @@ int flush_old_exec(struct linux_binprm * bprm)
  	if (retval)
  		goto out;
 
+	/*
+	 * Must be called _before_ exec_mmap() as bprm->mm is
+	 * not visibile until then. This also enables the update
+	 * to be lockless.
+	 */
  	set_mm_exe_file(bprm->mm, bprm->file);
+
  	/*
  	 * Release all of the old mmap stuff
  	 */
kernel/fork.c +13 -6
index 3847f34..347f69c 100644
@@ -688,15 +688,22 @@ EXPORT_SYMBOL_GPL(mmput);
   *
   * This changes mm's executale 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);
kernel/sys.c +28 -19
index 3be3449..a4e372b 100644
@@ -1649,14 +1649,13 @@ SYSCALL_DEFINE1(umask, int, mask)
  	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;
@@ -1680,15 +1679,22 @@ static int prctl_set_mm_exe_file_locked(struct mm_struct *mm, unsigned int fd)
  	/*
  	 * 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);
  	}
 
  	/*
@@ -1702,10 +1708,18 @@ static int prctl_set_mm_exe_file_locked(struct mm_struct *mm, unsigned int fd)
  		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
@@ -1840,10 +1854,9 @@ static int prctl_set_mm_map(int opt, const void __user *addr, unsigned long data
  		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;
 
@@ -1909,12 +1922,8 @@ static int prctl_set_mm(int opt, unsigned long addr,
  	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;
Signature · 7 lines
Konstantin

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#12 lore raw

Re: [PATCH v3] prctl: avoid using mmap_sem for exe_file serialization

Quoted 1 line · Konstantin Khlebnikov

Thanks.

Quoted 2 lines

If this is preparation for future rework of mmap_sem maybe we could postpone committing this patch.

Personally, I think this patch is fine to commit for v4.1, along with your rcu one, regardless of my mmap_sem work (which is still a wip). Furthermore we've already got all the exe_file users updated now in next, so I'd like to wrap this exe_file thing up if nobody has any objections.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

Acked-by on v3 1/1
#13 lore raw

Re: [PATCH v3] prctl: avoid using mmap_sem for exe_file serialization

In reply to Davidlohr Bueso
Quoted 2 lines · Davidlohr Bueso

Changes from v2: use correct exe_file (sigh), per Konstantin.

Looks good, thanks!

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>