get/set_dumpable() cleanups and theoretical fix
Subsystems (4)
v2 cover get/set_dumpable() cleanups and theoretical fix
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(-)
PATCH v2 1/3 set_dumpable: fix the theoretical race with itself
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.
--- fs/exec.c | 49 +++++++++++++++---------------------------------- 1 files changed, 15 insertions(+), 34 deletions(-)
fs/exec.c +15 -34
@@ -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)
Signature · 1 line
1.5.5.1
Re: [PATCH v2 1/3] set_dumpable: fix the theoretical race with itself
Quoted 9 lines · On Tue, 19 Nov 2013 15:43:15 +0100 Oleg Nesterov <[email protected]> wrote:
set_dumpable()updatesMMF_DUMPABLE_MASKin a non-trivial way to ensure thatget_dumpable()can't observe the intermediate state, but this all can't help if multiple threads callset_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.
Quoted 6 lines
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 (likearch/parisc/lib/bitops.cdoes), 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..
PATCH v2 2/3 kill MMF_DUMPABLE and MMF_DUMP_SECURELY
In reply to Oleg NesterovNobody 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.
--- fs/exec.c | 21 ++++++--------------- include/linux/sched.h | 4 +--- 2 files changed, 7 insertions(+), 18 deletions(-)
fs/exec.c +6 -15
@@ -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 ; }/*
include/linux/sched.h +1 -3
@@ -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)
Signature · 1 line
1.5.5.1
PATCH v2 3/3 make __get_dumpable/get_dumpable inline, kill fs/coredump.h
In reply to Oleg Nesterov1. 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.
--- 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
fs/coredump.c +0 -1
@@ -40,7 +40,6 @@#include <trace/events/task.h> #include "internal.h" -#include "coredump.h" #include <trace/events/sched.h>
fs/coredump.h deleted +0 -6
@@ -1,6 +0,0 @@ -#ifndef _FS_COREDUMP_H -#define _FS_COREDUMP_H - -extern int __get_dumpable (unsigned long mm_flags); - -#endif
fs/exec.c +0 -18
@@ -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,
include/linux/sched.h +17 -4
@@ -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
Signature · 1 line
1.5.5.1