[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAO6TR8V22AZG2rJ9qhcKE2pTKu==QJTAOFORQF9Ky0FZuZtzGw@mail.gmail.com>
Date: Tue, 15 Dec 2015 11:37:02 -0700
From: Jeff Merkey <linux.mdb@...il.com>
To: linux-kernel@...r.kernel.org
Cc: tglx@...utronix.de, mingo@...hat.com, hpa@...or.com,
x86@...nel.org, peterz@...radead.org, luto@...nel.org
Subject: Re: [PATCH V3] Fix INT1 Recursion with unregistered breakpoints
> kgdb/kdb and the perf event system both present garbage status in dr6
> then subsequently write this status into the thread.debugreg6 variable,
> then in some cases call hw_breakpoint_restore() which writes this
> status back into the dr6 hardware register.
>
I wanted to note here that this is the kgdb perf handler. What's a
serious problem here (and other perf handlers do this too) is that
kgdb has receive ONE breakpoint exception
when this handler is called, then inside this handler he sets ALL
exception bits for any
user defined exceptions then sends this value to get stuffed back into
the dr6 register,
so for each breakpoint that happens, it will trigger a cascade of checks in
the hw_breakpoint.c handler most hitting NULL bp structures when this value gets
read back out of dr6. If one of them fires off and debugreg6 has
gotten zapped --
POW -- system crash.
> arch/x86/kernel/kgdb.c
> static void kgdb_hw_overflow_handler(struct perf_event *event,
> struct perf_sample_data *data, struct pt_regs *regs)
> {
> struct task_struct *tsk = current;
> int i;
>
> for (i = 0; i < 4; i++)
> if (breakinfo[i].enabled)
> tsk->thread.debugreg6 |= (DR_TRAP0 << i);
> }
>
> arch/x86/kernel/kgdb.c
> static void kgdb_correct_hw_break(void)
> {
> ... snip ...
>
> if (!dbg_is_early)
> hw_breakpoint_restore();
>
> ... snip ...
> }
>
> arch/x86/kernel/hw_breakpoint.c
> void hw_breakpoint_restore(void)
> {
> set_debugreg(__this_cpu_read(cpu_debugreg[0]), 0);
> set_debugreg(__this_cpu_read(cpu_debugreg[1]), 1);
> set_debugreg(__this_cpu_read(cpu_debugreg[2]), 2);
> set_debugreg(__this_cpu_read(cpu_debugreg[3]), 3);
> set_debugreg(current->thread.debugreg6, 6);
> set_debugreg(__this_cpu_read(cpu_dr7), 7);
> }
>
So it goes like this:
INT1 exception #1 -> kgdb/perf -> set status bits for ALL
exceptions I registered (1,2,3,4) -> stuff it back into dr6 ->
INT1 exception #2 -> pickup bogus status in dr6 -> call handlers for
(1,2,3,4) -> Oops someone changed thread.debugreg6
(kgdb,perf,kdb,gdb,os, current moved) -> CRASH
Jeff
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists