diff options
author | Oleg Nesterov <oleg@redhat.com> | 2014-01-23 18:55:31 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-01-23 19:37:01 -0500 |
commit | abacd2fe3ca10b3ade57f3634053241a660002c2 (patch) | |
tree | e3d8d77e9b12d2e57a90a026a225313ccca9ddb1 /fs/exec.c | |
parent | f3c73a99a1fac2db992b6879b8a78a3ae2fcc06e (diff) |
coredump: 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.
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Acked-by: Kees Cook <keescook@chromium.org>
Cc: Alex Kelly <alex.page.kelly@gmail.com>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: Josh Triplett <josh@joshtriplett.org>
Cc: Petr Matousek <pmatouse@redhat.com>
Cc: Vasily Kulikov <segoon@openwall.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/exec.c')
-rw-r--r-- | fs/exec.c | 49 |
1 files changed, 15 insertions, 34 deletions
@@ -1614,43 +1614,24 @@ EXPORT_SYMBOL(set_binfmt); | |||
1614 | 1614 | ||
1615 | /* | 1615 | /* |
1616 | * set_dumpable converts traditional three-value dumpable to two flags and | 1616 | * set_dumpable converts traditional three-value dumpable to two flags and |
1617 | * stores them into mm->flags. It modifies lower two bits of mm->flags, but | 1617 | * stores them into mm->flags. |
1618 | * these bits are not changed atomically. So get_dumpable can observe the | ||
1619 | * intermediate state. To avoid doing unexpected behavior, get get_dumpable | ||
1620 | * return either old dumpable or new one by paying attention to the order of | ||
1621 | * modifying the bits. | ||
1622 | * | ||
1623 | * dumpable | mm->flags (binary) | ||
1624 | * old new | initial interim final | ||
1625 | * ---------+----------------------- | ||
1626 | * 0 1 | 00 01 01 | ||
1627 | * 0 2 | 00 10(*) 11 | ||
1628 | * 1 0 | 01 00 00 | ||
1629 | * 1 2 | 01 11 11 | ||
1630 | * 2 0 | 11 10(*) 00 | ||
1631 | * 2 1 | 11 11 01 | ||
1632 | * | ||
1633 | * (*) get_dumpable regards interim value of 10 as 11. | ||
1634 | */ | 1618 | */ |
1635 | void set_dumpable(struct mm_struct *mm, int value) | 1619 | void set_dumpable(struct mm_struct *mm, int value) |
1636 | { | 1620 | { |
1637 | switch (value) { | 1621 | unsigned long old, new; |
1638 | case SUID_DUMP_DISABLE: | 1622 | |
1639 | clear_bit(MMF_DUMPABLE, &mm->flags); | 1623 | do { |
1640 | smp_wmb(); | 1624 | old = ACCESS_ONCE(mm->flags); |
1641 | clear_bit(MMF_DUMP_SECURELY, &mm->flags); | 1625 | new = old & ~MMF_DUMPABLE_MASK; |
1642 | break; | 1626 | |
1643 | case SUID_DUMP_USER: | 1627 | switch (value) { |
1644 | set_bit(MMF_DUMPABLE, &mm->flags); | 1628 | case SUID_DUMP_ROOT: |
1645 | smp_wmb(); | 1629 | new |= (1 << MMF_DUMP_SECURELY); |
1646 | clear_bit(MMF_DUMP_SECURELY, &mm->flags); | 1630 | case SUID_DUMP_USER: |
1647 | break; | 1631 | new |= (1<< MMF_DUMPABLE); |
1648 | case SUID_DUMP_ROOT: | 1632 | } |
1649 | set_bit(MMF_DUMP_SECURELY, &mm->flags); | 1633 | |
1650 | smp_wmb(); | 1634 | } while (cmpxchg(&mm->flags, old, new) != old); |
1651 | set_bit(MMF_DUMPABLE, &mm->flags); | ||
1652 | break; | ||
1653 | } | ||
1654 | } | 1635 | } |
1655 | 1636 | ||
1656 | int __get_dumpable(unsigned long mm_flags) | 1637 | int __get_dumpable(unsigned long mm_flags) |