Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[PATCH 1/3] set_dumpable: fix the theoretical race with itself

6 views
Skip to first unread message

Oleg Nesterov

unread,
Nov 16, 2013, 2:10:01 PM11/16/13
to
set_dumpable() updates MMF_DUMPABLE_MASK in a non-trivial way to
ensure that get_dumpable() can't observe the intermediate state,
but this all can't help if multiple threads call set_dumpable()
at the same time.

And in theory commit_creds()->set_dumpable(SUID_DUMP_ROOT) racing
with sys_prctl()->set_dumpable(SUID_DUMP_DISABLE) can result in
SUID_DUMP_USER.

Change this code to update both bits atomically via cmpxchg().

Note: this assumes that it is safe to mix bitops and cmpxchg. IOW,
if, say, an architecture implements cmpxchg() using the locking
(like arch/parisc/lib/bitops.c does), then it should use the same
locks for set_bit/etc.

Signed-off-by: Oleg Nesterov <ol...@redhat.com>
---
fs/exec.c | 49 +++++++++++++++----------------------------------
1 files changed, 15 insertions(+), 34 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index bb8afc1..613c9dc 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1621,43 +1621,24 @@ EXPORT_SYMBOL(set_binfmt);

/*
* set_dumpable converts traditional three-value dumpable to two flags and
- * stores them into mm->flags. It modifies lower two bits of mm->flags, but
- * these bits are not changed atomically. So get_dumpable can observe the
- * intermediate state. To avoid doing unexpected behavior, get get_dumpable
- * return either old dumpable or new one by paying attention to the order of
- * modifying the bits.
- *
- * dumpable | mm->flags (binary)
- * old new | initial interim final
- * ---------+-----------------------
- * 0 1 | 00 01 01
- * 0 2 | 00 10(*) 11
- * 1 0 | 01 00 00
- * 1 2 | 01 11 11
- * 2 0 | 11 10(*) 00
- * 2 1 | 11 11 01
- *
- * (*) get_dumpable regards interim value of 10 as 11.
+ * stores them into mm->flags.
*/
void set_dumpable(struct mm_struct *mm, int value)
{
- switch (value) {
- case SUID_DUMP_DISABLE:
- clear_bit(MMF_DUMPABLE, &mm->flags);
- smp_wmb();
- clear_bit(MMF_DUMP_SECURELY, &mm->flags);
- break;
- case SUID_DUMP_USER:
- set_bit(MMF_DUMPABLE, &mm->flags);
- smp_wmb();
- clear_bit(MMF_DUMP_SECURELY, &mm->flags);
- break;
- case SUID_DUMP_ROOT:
- set_bit(MMF_DUMP_SECURELY, &mm->flags);
- smp_wmb();
- set_bit(MMF_DUMPABLE, &mm->flags);
- break;
- }
+ unsigned long old, new;
+
+ do {
+ old = ACCESS_ONCE(mm->flags);
+ new = old & ~MMF_DUMPABLE_MASK;
+
+ switch (value) {
+ case SUID_DUMP_ROOT:
+ new |= (1 << MMF_DUMP_SECURELY);
+ case SUID_DUMP_USER:
+ new |= (1<< MMF_DUMPABLE);
+ }
+
+ } while (cmpxchg(&mm->flags, old, new) != old);
}

int __get_dumpable(unsigned long mm_flags)
--
1.5.5.1


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majo...@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

Oleg Nesterov

unread,
Nov 16, 2013, 2:10:01 PM11/16/13
to
On 11/15, Kees Cook wrote:
>
> On Fri, Nov 15, 2013 at 12:36 PM, Oleg Nesterov <ol...@redhat.com> wrote:
> >
> > unless I missed something, this is the fix, not cleanup ?
> >
> > If commit_creds()->set_dumpable(SUID_DUMP_ROOT) races with
> > sys_prctl()->set_dumpable(SUID_DUMP_DISABLE), we can get
> > SUID_DUMP_USER afaics.
> >
> > Yes, yes, even if I am right this race is very unlikely.
>
> Hm, yes, that is a fix then. I struggle to imagine it being
> exploitable (i.e. control over both threads, one at least already with
> a different cred, etc), but it certainly does look like a bug.

Yes, yes, sure, this is only theoretical.

OK, I am sending the patches to lkml. I didn't dare to keep your ack,
please review v2 (v1 confused bit-mask with bit-number, and in theory
we need ACCESS_ONCE).

It would be really nice if someone can ack the "it is safe to mix
bitops and cmpxchg" assumption mentioned in the changelog.


Alex, Josh, this also partly reverts 179899fd5dc780fe "coredump:
update coredump-related headers", I think fs/coredump.h buys nothing.

Oleg.

fs/coredump.c | 1 -
fs/coredump.h | 6 -----
fs/exec.c | 61 +++++--------------------------------------------
include/linux/sched.h | 25 ++++++++++++++-----
4 files changed, 24 insertions(+), 69 deletions(-)

Oleg Nesterov

unread,
Nov 16, 2013, 2:10:01 PM11/16/13
to
Nobody actually needs MMF_DUMPABLE/MMF_DUMP_SECURELY, there
are only used to enforce the encoding of SUID_DUMP_* enum in
mm->flags & MMF_DUMPABLE_MASK.

Now that set_dumpable() updates both bits atomically we can
kill them and simply store the value "as is" in 2 lower bits.

Signed-off-by: Oleg Nesterov <ol...@redhat.com>
---
fs/exec.c | 18 +++---------------
include/linux/sched.h | 4 +---
2 files changed, 4 insertions(+), 18 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index 613c9dc..5303005 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1620,8 +1620,7 @@ void set_binfmt(struct linux_binfmt *new)
EXPORT_SYMBOL(set_binfmt);

/*
- * set_dumpable converts traditional three-value dumpable to two flags and
- * stores them into mm->flags.
+ * set_dumpable stores three-value SUID_DUMP_* into mm->flags.
*/
void set_dumpable(struct mm_struct *mm, int value)
{
@@ -1629,24 +1628,13 @@ void set_dumpable(struct mm_struct *mm, int value)

do {
old = ACCESS_ONCE(mm->flags);
- new = old & ~MMF_DUMPABLE_MASK;
-
- switch (value) {
- case SUID_DUMP_ROOT:
- new |= (1 << MMF_DUMP_SECURELY);
- case SUID_DUMP_USER:
- new |= (1<< MMF_DUMPABLE);
- }
-
+ new = (old & ~MMF_DUMPABLE_MASK) | value;
} while (cmpxchg(&mm->flags, old, new) != old);
}

int __get_dumpable(unsigned long mm_flags)
{
- int ret;
-
- ret = mm_flags & MMF_DUMPABLE_MASK;
- return (ret > SUID_DUMP_USER) ? SUID_DUMP_ROOT : ret;
+ return mm_flags & MMF_DUMPABLE_MASK;
}

/*
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 838a3d9..828c00d 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -326,10 +326,8 @@ extern int get_dumpable(struct mm_struct *mm);
#define SUID_DUMP_ROOT 2 /* Dump as root */

/* mm flags */
-/* dumpable bits */
-#define MMF_DUMPABLE 0 /* core dump is permitted */
-#define MMF_DUMP_SECURELY 1 /* core file is readable only by root */

+/* for SUID_DUMP_* above */
#define MMF_DUMPABLE_BITS 2
#define MMF_DUMPABLE_MASK ((1 << MMF_DUMPABLE_BITS) - 1)

--
1.5.5.1

Oleg Nesterov

unread,
Nov 16, 2013, 2:10:02 PM11/16/13
to
1. Remove fs/coredump.h. It is not clear why do we need it,
it only declares __get_dumpable(), signal.c includes it
for no reason.

2. Now that get_dumpable() and __get_dumpable() are really
trivial make them inline in linux/sched.h.

Signed-off-by: Oleg Nesterov <ol...@redhat.com>
---
fs/coredump.c | 1 -
fs/coredump.h | 6 ------
fs/exec.c | 18 ------------------
include/linux/sched.h | 21 +++++++++++++++++----
4 files changed, 17 insertions(+), 29 deletions(-)
delete mode 100644 fs/coredump.h

diff --git a/fs/coredump.c b/fs/coredump.c
index 9bdeca1..4bc92c7 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -40,7 +40,6 @@

#include <trace/events/task.h>
#include "internal.h"
-#include "coredump.h"

#include <trace/events/sched.h>

diff --git a/fs/coredump.h b/fs/coredump.h
deleted file mode 100644
index e39ff07..0000000
--- a/fs/coredump.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef _FS_COREDUMP_H
-#define _FS_COREDUMP_H
-
-extern int __get_dumpable(unsigned long mm_flags);
-
-#endif
diff --git a/fs/exec.c b/fs/exec.c
index 5303005..2196ef5 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -62,7 +62,6 @@

#include <trace/events/task.h>
#include "internal.h"
-#include "coredump.h"

#include <trace/events/sched.h>

@@ -1616,7 +1615,6 @@ void set_binfmt(struct linux_binfmt *new)
if (new)
__module_get(new->module);
}
-
EXPORT_SYMBOL(set_binfmt);

/*
@@ -1632,22 +1630,6 @@ void set_dumpable(struct mm_struct *mm, int value)
} while (cmpxchg(&mm->flags, old, new) != old);
}

-int __get_dumpable(unsigned long mm_flags)
-{
- return mm_flags & MMF_DUMPABLE_MASK;
-}
-
-/*
- * This returns the actual value of the suid_dumpable flag. For things
- * that are using this for checking for privilege transitions, it must
- * test against SUID_DUMP_USER rather than treating it as a boolean
- * value.
- */
-int get_dumpable(struct mm_struct *mm)
-{
- return __get_dumpable(mm->flags);
-}
-
SYSCALL_DEFINE3(execve,
const char __user *, filename,
const char __user *const __user *, argv,
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 828c00d..34c1903 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -317,10 +317,6 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr,
static inline void arch_pick_mmap_layout(struct mm_struct *mm) {}
#endif

-
-extern void set_dumpable(struct mm_struct *mm, int value);
-extern int get_dumpable(struct mm_struct *mm);
-
#define SUID_DUMP_DISABLE 0 /* No setuid dumping */
#define SUID_DUMP_USER 1 /* Dump as user of process */
#define SUID_DUMP_ROOT 2 /* Dump as root */
@@ -331,6 +327,23 @@ extern int get_dumpable(struct mm_struct *mm);
#define MMF_DUMPABLE_BITS 2
#define MMF_DUMPABLE_MASK ((1 << MMF_DUMPABLE_BITS) - 1)

+extern void set_dumpable(struct mm_struct *mm, int value);
+/*
+ * This returns the actual value of the suid_dumpable flag. For things
+ * that are using this for checking for privilege transitions, it must
+ * test against SUID_DUMP_USER rather than treating it as a boolean
+ * value.
+ */
+static inline int __get_dumpable(unsigned long mm_flags)
+{
+ return mm_flags & MMF_DUMPABLE_MASK;
+}
+
+static inline int get_dumpable(struct mm_struct *mm)
+{
+ return __get_dumpable(mm->flags);
+}
+
/* coredump filter bits */
#define MMF_DUMP_ANON_PRIVATE 2
#define MMF_DUMP_ANON_SHARED 3
--
1.5.5.1

Kees Cook

unread,
Nov 18, 2013, 11:40:03 AM11/18/13
to
On Sat, Nov 16, 2013 at 11:01 AM, Oleg Nesterov <ol...@redhat.com> wrote:
> set_dumpable() updates MMF_DUMPABLE_MASK in a non-trivial way to
> ensure that get_dumpable() can't observe the intermediate state,
> but this all can't help if multiple threads call set_dumpable()
> at the same time.
>
> And in theory commit_creds()->set_dumpable(SUID_DUMP_ROOT) racing
> with sys_prctl()->set_dumpable(SUID_DUMP_DISABLE) can result in
> SUID_DUMP_USER.
>
> Change this code to update both bits atomically via cmpxchg().
>
> Note: this assumes that it is safe to mix bitops and cmpxchg. IOW,
> if, say, an architecture implements cmpxchg() using the locking
> (like arch/parisc/lib/bitops.c does), then it should use the same
> locks for set_bit/etc.
>
> Signed-off-by: Oleg Nesterov <ol...@redhat.com>

I can't speak to the bitops/cmpxchg, but the rest looks fine to me.

Acked-by: Kees Cook <kees...@chromium.org>

Thanks!

-Kees

--
Kees Cook
Chrome OS Security

Kees Cook

unread,
Nov 18, 2013, 1:40:02 PM11/18/13
to
On Sat, Nov 16, 2013 at 11:01 AM, Oleg Nesterov <ol...@redhat.com> wrote:
Just to make this safe against insane callers, perhaps mask the value as well?

new = (old & ~MMF_DUMPABLE_MASK) | (value & MMF_DUMPABLE_MASK);

> } while (cmpxchg(&mm->flags, old, new) != old);
> }
>
> int __get_dumpable(unsigned long mm_flags)
> {
> - int ret;
> -
> - ret = mm_flags & MMF_DUMPABLE_MASK;
> - return (ret > SUID_DUMP_USER) ? SUID_DUMP_ROOT : ret;
> + return mm_flags & MMF_DUMPABLE_MASK;
> }
>
> /*
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 838a3d9..828c00d 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -326,10 +326,8 @@ extern int get_dumpable(struct mm_struct *mm);
> #define SUID_DUMP_ROOT 2 /* Dump as root */
>
> /* mm flags */
> -/* dumpable bits */
> -#define MMF_DUMPABLE 0 /* core dump is permitted */
> -#define MMF_DUMP_SECURELY 1 /* core file is readable only by root */
>
> +/* for SUID_DUMP_* above */
> #define MMF_DUMPABLE_BITS 2
> #define MMF_DUMPABLE_MASK ((1 << MMF_DUMPABLE_BITS) - 1)

Besides that, looks great.

-Kees

--
Kees Cook
Chrome OS Security

Kees Cook

unread,
Nov 18, 2013, 1:40:03 PM11/18/13
to
On Sat, Nov 16, 2013 at 11:02 AM, Oleg Nesterov <ol...@redhat.com> wrote:
> 1. Remove fs/coredump.h. It is not clear why do we need it,
> it only declares __get_dumpable(), signal.c includes it
> for no reason.
>
> 2. Now that get_dumpable() and __get_dumpable() are really
> trivial make them inline in linux/sched.h.
>
> Signed-off-by: Oleg Nesterov <ol...@redhat.com>

Acked-by: Kees Cook <kees...@chromium.org>

--
Kees Cook
Chrome OS Security

Oleg Nesterov

unread,
Nov 18, 2013, 2:20:02 PM11/18/13
to
On 11/18, Kees Cook wrote:
>
> On Sat, Nov 16, 2013 at 11:01 AM, Oleg Nesterov <ol...@redhat.com> wrote:
> > @@ -1629,24 +1628,13 @@ void set_dumpable(struct mm_struct *mm, int value)
> >
> > do {
> > old = ACCESS_ONCE(mm->flags);
> > - new = old & ~MMF_DUMPABLE_MASK;
> > -
> > - switch (value) {
> > - case SUID_DUMP_ROOT:
> > - new |= (1 << MMF_DUMP_SECURELY);
> > - case SUID_DUMP_USER:
> > - new |= (1<< MMF_DUMPABLE);
> > - }
> > -
> > + new = (old & ~MMF_DUMPABLE_MASK) | value;
>
> Just to make this safe against insane callers, perhaps mask the value as well?

Well yes, before this patch set_dumpable() silently ignored the wrong
value, perhaps you are right but see below.

> new = (old & ~MMF_DUMPABLE_MASK) | (value & MMF_DUMPABLE_MASK);
^^^^^^^^^^^^^^^^^^^^^^^^^^

this doesn't really help, with this patch "mm->flags & MMF_DUMPABLE_MASK"
has a room for yet another SUID_DUMP == 4 we do not have yet.

And I don't really like the "silently ignore" logic, so perhaps

if (WARN_ON(value > SUID_DUMP_ROOT))
return;

at the start makes more sense?

Or perhaps we do not really need the additional check? suid_dumpable
is always sane, other callers can't use the wrong value.

But I am fine either way, please tell me what do you prefer.

Oleg.

Kees Cook

unread,
Nov 18, 2013, 2:30:02 PM11/18/13
to
Ah, good point about == 4. Yeah, I like the WARN_ON. No reason not to
be defensive as long as this code is getting changed.

-Kees

--
Kees Cook
Chrome OS Security

Oleg Nesterov

unread,
Nov 18, 2013, 2:40:02 PM11/18/13
to
On 11/18, Kees Cook wrote:
>
> On Mon, Nov 18, 2013 at 11:16 AM, Oleg Nesterov <ol...@redhat.com> wrote:
> >
> > And I don't really like the "silently ignore" logic, so perhaps
> >
> > if (WARN_ON(value > SUID_DUMP_ROOT))
> > return;
>
> Ah, good point about == 4. Yeah, I like the WARN_ON. No reason not to
> be defensive as long as this code is getting changed.

OK. I'll send v2 tomorrow.


And I just noticed I forgot to remove secu...@kernel.org, sorry for
unnecessary noise.

Oleg.

Oleg Nesterov

unread,
Nov 19, 2013, 9:50:03 AM11/19/13
to
1. Remove fs/coredump.h. It is not clear why do we need it,
it only declares __get_dumpable(), signal.c includes it
for no reason.

2. Now that get_dumpable() and __get_dumpable() are really
trivial make them inline in linux/sched.h.

Signed-off-by: Oleg Nesterov <ol...@redhat.com>
Acked-by: Kees Cook <kees...@chromium.org>
-extern int __get_dumpable(unsigned long mm_flags);
-
-#endif
diff --git a/fs/exec.c b/fs/exec.c
index 6ce1f86..1dee8ef 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -62,7 +62,6 @@

#include <trace/events/task.h>
#include "internal.h"
-#include "coredump.h"

#include <trace/events/sched.h>

@@ -1616,7 +1615,6 @@ void set_binfmt(struct linux_binfmt *new)
if (new)
__module_get(new->module);
}
-
EXPORT_SYMBOL(set_binfmt);

/*
@@ -1635,22 +1633,6 @@ void set_dumpable(struct mm_struct *mm, int value)
} while (cmpxchg(&mm->flags, old, new) != old);
}

-int __get_dumpable(unsigned long mm_flags)
-{
- return mm_flags & MMF_DUMPABLE_MASK;
-}
-
-/*
- * This returns the actual value of the suid_dumpable flag. For things
- * that are using this for checking for privilege transitions, it must
- * test against SUID_DUMP_USER rather than treating it as a boolean
- * value.
- */
-int get_dumpable(struct mm_struct *mm)
-{
- return __get_dumpable(mm->flags);
-}
-
SYSCALL_DEFINE3(execve,
const char __user *, filename,
const char __user *const __user *, argv,
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 828c00d..34c1903 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -317,10 +317,6 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr,
static inline void arch_pick_mmap_layout(struct mm_struct *mm) {}
#endif

-
-extern void set_dumpable(struct mm_struct *mm, int value);
-extern int get_dumpable(struct mm_struct *mm);
-
#define SUID_DUMP_DISABLE 0 /* No setuid dumping */
#define SUID_DUMP_USER 1 /* Dump as user of process */
#define SUID_DUMP_ROOT 2 /* Dump as root */
@@ -331,6 +327,23 @@ extern int get_dumpable(struct mm_struct *mm);
#define MMF_DUMPABLE_BITS 2
#define MMF_DUMPABLE_MASK ((1 << MMF_DUMPABLE_BITS) - 1)

+extern void set_dumpable(struct mm_struct *mm, int value);
+/*
+ * This returns the actual value of the suid_dumpable flag. For things
+ * that are using this for checking for privilege transitions, it must
+ * test against SUID_DUMP_USER rather than treating it as a boolean
+ * value.
+ */
+static inline int __get_dumpable(unsigned long mm_flags)
+{
+ return mm_flags & MMF_DUMPABLE_MASK;
+}
+
+static inline int get_dumpable(struct mm_struct *mm)
+{
+ return __get_dumpable(mm->flags);
+}
+
/* coredump filter bits */
#define MMF_DUMP_ANON_PRIVATE 2
#define MMF_DUMP_ANON_SHARED 3
--
1.5.5.1

Oleg Nesterov

unread,
Nov 19, 2013, 9:50:03 AM11/19/13
to
Nobody actually needs MMF_DUMPABLE/MMF_DUMP_SECURELY, they
are only used to enforce the encoding of SUID_DUMP_* enum
in mm->flags & MMF_DUMPABLE_MASK.

Now that set_dumpable() updates both bits atomically we can
kill them and simply store the value "as is" in 2 lower bits.

Signed-off-by: Oleg Nesterov <ol...@redhat.com>
Acked-by: Kees Cook <kees...@chromium.org>
---
fs/exec.c | 21 ++++++---------------
include/linux/sched.h | 4 +---
2 files changed, 7 insertions(+), 18 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index 613c9dc..6ce1f86 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1620,33 +1620,24 @@ void set_binfmt(struct linux_binfmt *new)
EXPORT_SYMBOL(set_binfmt);

/*
- * set_dumpable converts traditional three-value dumpable to two flags and
- * stores them into mm->flags.
+ * set_dumpable stores three-value SUID_DUMP_* into mm->flags.
*/
void set_dumpable(struct mm_struct *mm, int value)
{
unsigned long old, new;

+ if (WARN_ON((unsigned)value > SUID_DUMP_ROOT))
+ return;
+
do {
old = ACCESS_ONCE(mm->flags);
- new = old & ~MMF_DUMPABLE_MASK;
-
- switch (value) {
- case SUID_DUMP_ROOT:
- new |= (1 << MMF_DUMP_SECURELY);
- case SUID_DUMP_USER:
- new |= (1<< MMF_DUMPABLE);
- }
-
+ new = (old & ~MMF_DUMPABLE_MASK) | value;
} while (cmpxchg(&mm->flags, old, new) != old);
}

int __get_dumpable(unsigned long mm_flags)
{
- int ret;
-
- ret = mm_flags & MMF_DUMPABLE_MASK;
- return (ret > SUID_DUMP_USER) ? SUID_DUMP_ROOT : ret;
+ return mm_flags & MMF_DUMPABLE_MASK;
}

/*
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 838a3d9..828c00d 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -326,10 +326,8 @@ extern int get_dumpable(struct mm_struct *mm);
#define SUID_DUMP_ROOT 2 /* Dump as root */

/* mm flags */
-/* dumpable bits */
-#define MMF_DUMPABLE 0 /* core dump is permitted */
-#define MMF_DUMP_SECURELY 1 /* core file is readable only by root */

+/* for SUID_DUMP_* above */
#define MMF_DUMPABLE_BITS 2
#define MMF_DUMPABLE_MASK ((1 << MMF_DUMPABLE_BITS) - 1)

Oleg Nesterov

unread,
Nov 19, 2013, 9:50:02 AM11/19/13
to
set_dumpable() updates MMF_DUMPABLE_MASK in a non-trivial way to
ensure that get_dumpable() can't observe the intermediate state,
but this all can't help if multiple threads call set_dumpable()
at the same time.

And in theory commit_creds()->set_dumpable(SUID_DUMP_ROOT) racing
with sys_prctl()->set_dumpable(SUID_DUMP_DISABLE) can result in
SUID_DUMP_USER.

Change this code to update both bits atomically via cmpxchg().

Note: this assumes that it is safe to mix bitops and cmpxchg. IOW,
if, say, an architecture implements cmpxchg() using the locking
(like arch/parisc/lib/bitops.c does), then it should use the same
locks for set_bit/etc.

Signed-off-by: Oleg Nesterov <ol...@redhat.com>
Acked-by: Kees Cook <kees...@chromium.org>
---
fs/exec.c | 49 +++++++++++++++----------------------------------
1 files changed, 15 insertions(+), 34 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index bb8afc1..613c9dc 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1621,43 +1621,24 @@ EXPORT_SYMBOL(set_binfmt);

/*
* set_dumpable converts traditional three-value dumpable to two flags and
- * stores them into mm->flags. It modifies lower two bits of mm->flags, but
- * these bits are not changed atomically. So get_dumpable can observe the
- * intermediate state. To avoid doing unexpected behavior, get get_dumpable
- * return either old dumpable or new one by paying attention to the order of
- * modifying the bits.
- *
- * dumpable | mm->flags (binary)
- * old new | initial interim final
- * ---------+-----------------------
- * 0 1 | 00 01 01
- * 0 2 | 00 10(*) 11
- * 1 0 | 01 00 00
- * 1 2 | 01 11 11
- * 2 0 | 11 10(*) 00
- * 2 1 | 11 11 01
- *
- * (*) get_dumpable regards interim value of 10 as 11.
+ * stores them into mm->flags.
*/
void set_dumpable(struct mm_struct *mm, int value)
{
- switch (value) {
- case SUID_DUMP_DISABLE:
- clear_bit(MMF_DUMPABLE, &mm->flags);
- smp_wmb();
- clear_bit(MMF_DUMP_SECURELY, &mm->flags);
- break;
- case SUID_DUMP_USER:
- set_bit(MMF_DUMPABLE, &mm->flags);
- smp_wmb();
- clear_bit(MMF_DUMP_SECURELY, &mm->flags);
- break;
- case SUID_DUMP_ROOT:
- set_bit(MMF_DUMP_SECURELY, &mm->flags);
- smp_wmb();
- set_bit(MMF_DUMPABLE, &mm->flags);
- break;
- }
+ unsigned long old, new;
+
+ do {
+ old = ACCESS_ONCE(mm->flags);
+ new = old & ~MMF_DUMPABLE_MASK;
+
+ switch (value) {
+ case SUID_DUMP_ROOT:
+ new |= (1 << MMF_DUMP_SECURELY);
+ case SUID_DUMP_USER:
+ new |= (1<< MMF_DUMPABLE);
+ }
+
+ } while (cmpxchg(&mm->flags, old, new) != old);
}

int __get_dumpable(unsigned long mm_flags)

Oleg Nesterov

unread,
Nov 19, 2013, 9:50:02 AM11/19/13
to
Hello.

The only change is WARN_ON() in 2/3 to catch the insane callers,
I preserved the acks from Kees.

Oleg.

fs/coredump.c | 1 -
fs/coredump.h | 6 ----
fs/exec.c | 62 ++++++------------------------------------------
include/linux/sched.h | 25 ++++++++++++++-----
4 files changed, 26 insertions(+), 68 deletions(-)

Andrew Morton

unread,
Nov 19, 2013, 5:30:02 PM11/19/13
to
On Tue, 19 Nov 2013 15:43:15 +0100 Oleg Nesterov <ol...@redhat.com> wrote:

> set_dumpable() updates MMF_DUMPABLE_MASK in a non-trivial way to
> ensure that get_dumpable() can't observe the intermediate state,
> but this all can't help if multiple threads call set_dumpable()
> at the same time.
>
> And in theory commit_creds()->set_dumpable(SUID_DUMP_ROOT) racing
> with sys_prctl()->set_dumpable(SUID_DUMP_DISABLE) can result in
> SUID_DUMP_USER.
>
> Change this code to update both bits atomically via cmpxchg().
>
> Note: this assumes that it is safe to mix bitops and cmpxchg. IOW,
> if, say, an architecture implements cmpxchg() using the locking
> (like arch/parisc/lib/bitops.c does), then it should use the same
> locks for set_bit/etc.

I suppose that is reasonable - atomic operations on memory should be
atomic wrt other classes of atomic operations on the same memory. So
set_bit() has to "know" about concurrent cmpxchg(). It *has* to be
this way to fully emulate the hardware-based RMW operations.

parisc got that right. I wonder if all other architectures did, and
how on earth we can communicate this to future arch developers..
0 new messages