Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1718653 > unrolled thread
| Started by | Eric Biggers <ebiggers3@gmail.com> |
|---|---|
| First post | 2017-08-23 23:20 +0200 |
| Last post | 2017-08-25 13:00 +0200 |
| Articles | 5 — 3 participants |
Back to article view | Back to linux.kernel
[PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free Eric Biggers <ebiggers3@gmail.com> - 2017-08-23 23:20 +0200
Re: [PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free Oleg Nesterov <oleg@redhat.com> - 2017-08-24 15:30 +0200
Re: [PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free Eric Biggers <ebiggers3@gmail.com> - 2017-08-24 19:00 +0200
Re: [PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free Mark Rutland <mark.rutland@arm.com> - 2017-08-24 17:10 +0200
Re: [PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free Mark Rutland <mark.rutland@arm.com> - 2017-08-25 13:00 +0200
| From | Eric Biggers <ebiggers3@gmail.com> |
|---|---|
| Date | 2017-08-23 23:20 +0200 |
| Subject | [PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free |
| Message-ID | <uhLea-5Jw-19@gated-at.bofh.it> |
From: Eric Biggers <ebiggers@google.com>
Commit 7c051267931a ("mm, fork: make dup_mmap wait for mmap_sem for
write killable") made it possible to kill a forking task while it is
waiting to acquire its ->mmap_sem for write, in dup_mmap(). However, it
was overlooked that this introduced an new error path before a reference
is taken on the mm_struct's ->exe_file. Since the ->exe_file of the new
mm_struct was already set to the old ->exe_file by the memcpy() in
dup_mm(), it was possible for the mmput() in the error path of dup_mm()
to drop a reference to ->exe_file which was never taken. This caused
the struct file to later be freed prematurely.
Fix it by updating mm_init() to NULL out the ->exe_file, in the same
place it clears other things like the list of mmaps.
This bug was found by syzkaller. It can be reproduced using the
following C program:
#define _GNU_SOURCE
#include <pthread.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
static void *mmap_thread(void *_arg)
{
for (;;) {
mmap(NULL, 0x1000000, PROT_READ,
MAP_POPULATE|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
}
}
static void *fork_thread(void *_arg)
{
usleep(rand() % 10000);
fork();
}
int main(void)
{
fork();
fork();
fork();
for (;;) {
if (fork() == 0) {
pthread_t t;
pthread_create(&t, NULL, mmap_thread, NULL);
pthread_create(&t, NULL, fork_thread, NULL);
usleep(rand() % 10000);
syscall(__NR_exit_group, 0);
}
wait(NULL);
}
}
No special kernel config options are needed. It usually causes a NULL
pointer dereference in __remove_shared_vm_struct() during exit, or in
dup_mmap() (which is usually inlined into copy_process()) during fork.
Both are due to a vm_area_struct's ->vm_file being used after it's
already been freed.
Fixes: 7c051267931a ("mm, fork: make dup_mmap wait for mmap_sem for write killable")
Google-Bug-Id: 64772007
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Konstantin Khlebnikov <koct9i@gmail.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: stable@vger.kernel.org # v4.7+
Signed-off-by: Eric Biggers <ebiggers@google.com>
---
kernel/fork.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/kernel/fork.c b/kernel/fork.c
index e075b7780421..cbbea277b3fb 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -806,6 +806,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
mm_init_cpumask(mm);
mm_init_aio(mm);
mm_init_owner(mm, p);
+ RCU_INIT_POINTER(mm->exe_file, NULL);
mmu_notifier_mm_init(mm);
init_tlb_flush_pending(mm);
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
--
2.14.1.342.g6490525c54-goog
[toc] | [next] | [standalone]
| From | Oleg Nesterov <oleg@redhat.com> |
|---|---|
| Date | 2017-08-24 15:30 +0200 |
| Subject | Re: [PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free |
| Message-ID | <ui0mS-78Q-19@gated-at.bofh.it> |
| In reply to | #1718653 |
On 08/23, Eric Biggers wrote:
>
> From: Eric Biggers <ebiggers@google.com>
>
> Commit 7c051267931a ("mm, fork: make dup_mmap wait for mmap_sem for
> write killable") made it possible to kill a forking task while it is
> waiting to acquire its ->mmap_sem for write, in dup_mmap(). However, it
> was overlooked that this introduced an new error path before a reference
> is taken on the mm_struct's ->exe_file.
Hmm. Unless I am totally confused, the same problem with mm->exol_area?
I'll recheck....
> --- a/kernel/fork.c
> +++ b/kernel/fork.c
> @@ -806,6 +806,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
> mm_init_cpumask(mm);
> mm_init_aio(mm);
> mm_init_owner(mm, p);
> + RCU_INIT_POINTER(mm->exe_file, NULL);
Can't we simply move
RCU_INIT_POINTER(mm->exe_file, get_mm_exe_file(oldmm));
from dup_mmap() here? Afaics this doesn't need mmap_sem.
Good catch!
Oleg.
[toc] | [prev] | [next] | [standalone]
| From | Eric Biggers <ebiggers3@gmail.com> |
|---|---|
| Date | 2017-08-24 19:00 +0200 |
| Subject | Re: [PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free |
| Message-ID | <ui3E5-Db-1@gated-at.bofh.it> |
| In reply to | #1719236 |
On Thu, Aug 24, 2017 at 03:20:41PM +0200, Oleg Nesterov wrote:
> On 08/23, Eric Biggers wrote:
> >
> > From: Eric Biggers <ebiggers@google.com>
> >
> > Commit 7c051267931a ("mm, fork: make dup_mmap wait for mmap_sem for
> > write killable") made it possible to kill a forking task while it is
> > waiting to acquire its ->mmap_sem for write, in dup_mmap(). However, it
> > was overlooked that this introduced an new error path before a reference
> > is taken on the mm_struct's ->exe_file.
>
> Hmm. Unless I am totally confused, the same problem with mm->exol_area?
> I'll recheck....
I'm not sure what you mean by ->exol_area.
>
> > --- a/kernel/fork.c
> > +++ b/kernel/fork.c
> > @@ -806,6 +806,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
> > mm_init_cpumask(mm);
> > mm_init_aio(mm);
> > mm_init_owner(mm, p);
> > + RCU_INIT_POINTER(mm->exe_file, NULL);
>
> Can't we simply move
>
> RCU_INIT_POINTER(mm->exe_file, get_mm_exe_file(oldmm));
>
> from dup_mmap() here? Afaics this doesn't need mmap_sem.
>
Two problems, even assuming that get_mm_exe_file() doesn't require mmap_sem:
- If mm_alloc_pgd() or init_new_context() in mm_init() fails, mm_init() doesn't
do the full mmput(), so the file reference would not be dropped. So it would
need to be changed to drop the file reference too.
- The file would also be set when called from mm_alloc() which is used when
exec'ing a new task. *Maybe* it would be safe to do temporarily, but it's
pointless because ->exe_file will be set later by flush_old_exec().
Eric
[toc] | [prev] | [next] | [standalone]
| From | Mark Rutland <mark.rutland@arm.com> |
|---|---|
| Date | 2017-08-24 17:10 +0200 |
| Subject | Re: [PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free |
| Message-ID | <ui1VF-8db-33@gated-at.bofh.it> |
| In reply to | #1718653 |
On Wed, Aug 23, 2017 at 02:14:08PM -0700, Eric Biggers wrote:
> From: Eric Biggers <ebiggers@google.com>
>
> Commit 7c051267931a ("mm, fork: make dup_mmap wait for mmap_sem for
> write killable") made it possible to kill a forking task while it is
> waiting to acquire its ->mmap_sem for write, in dup_mmap(). However, it
> was overlooked that this introduced an new error path before a reference
> is taken on the mm_struct's ->exe_file. Since the ->exe_file of the new
> mm_struct was already set to the old ->exe_file by the memcpy() in
> dup_mm(), it was possible for the mmput() in the error path of dup_mm()
> to drop a reference to ->exe_file which was never taken. This caused
> the struct file to later be freed prematurely.
>
> Fix it by updating mm_init() to NULL out the ->exe_file, in the same
> place it clears other things like the list of mmaps.
> diff --git a/kernel/fork.c b/kernel/fork.c
> index e075b7780421..cbbea277b3fb 100644
> --- a/kernel/fork.c
> +++ b/kernel/fork.c
> @@ -806,6 +806,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
> mm_init_cpumask(mm);
> mm_init_aio(mm);
> mm_init_owner(mm, p);
> + RCU_INIT_POINTER(mm->exe_file, NULL);
> mmu_notifier_mm_init(mm);
> init_tlb_flush_pending(mm);
> #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
I've been seeing similar issues on arm64 with use-after-free of a file
and other memory corruption [1].
This patch seems to fix that; a test that normally fired in a few
minutes has been happily running for hours with this applied.
Thanks,
Mark.
[1] https://lkml.kernel.org/r/20170824113743.GA14737@leverpostej
[toc] | [prev] | [next] | [standalone]
| From | Mark Rutland <mark.rutland@arm.com> |
|---|---|
| Date | 2017-08-25 13:00 +0200 |
| Subject | Re: [PATCH] fork: fix incorrect fput of ->exe_file causing use-after-free |
| Message-ID | <uikvh-2YQ-35@gated-at.bofh.it> |
| In reply to | #1719333 |
On Thu, Aug 24, 2017 at 04:02:49PM +0100, Mark Rutland wrote:
> On Wed, Aug 23, 2017 at 02:14:08PM -0700, Eric Biggers wrote:
> > From: Eric Biggers <ebiggers@google.com>
> >
> > Commit 7c051267931a ("mm, fork: make dup_mmap wait for mmap_sem for
> > write killable") made it possible to kill a forking task while it is
> > waiting to acquire its ->mmap_sem for write, in dup_mmap(). However, it
> > was overlooked that this introduced an new error path before a reference
> > is taken on the mm_struct's ->exe_file. Since the ->exe_file of the new
> > mm_struct was already set to the old ->exe_file by the memcpy() in
> > dup_mm(), it was possible for the mmput() in the error path of dup_mm()
> > to drop a reference to ->exe_file which was never taken. This caused
> > the struct file to later be freed prematurely.
> >
> > Fix it by updating mm_init() to NULL out the ->exe_file, in the same
> > place it clears other things like the list of mmaps.
>
> > diff --git a/kernel/fork.c b/kernel/fork.c
> > index e075b7780421..cbbea277b3fb 100644
> > --- a/kernel/fork.c
> > +++ b/kernel/fork.c
> > @@ -806,6 +806,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
> > mm_init_cpumask(mm);
> > mm_init_aio(mm);
> > mm_init_owner(mm, p);
> > + RCU_INIT_POINTER(mm->exe_file, NULL);
> > mmu_notifier_mm_init(mm);
> > init_tlb_flush_pending(mm);
> > #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
>
> I've been seeing similar issues on arm64 with use-after-free of a file
> and other memory corruption [1].
>
> This patch seems to fix that; a test that normally fired in a few
> minutes has been happily running for hours with this applied.
Those haven't triggered after 24 hours, and in 16+ hours of fuzzing with
this applied, I haven't seen new issues. FWIW:
Tested-by: Mark Rutland <mark.rutland@arm.com>
Thanks,
Mark.
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web