All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] x86: ptrace debugreg checks rewrite
@ 2009-05-04  0:16 Alexey Dobriyan
  2009-05-04  0:24 ` Alexey Dobriyan
       [not found] ` <20090504001601.GL16631-2ev+ksY9ol182hYKe6nXyg@public.gmane.org>
  0 siblings, 2 replies; 16+ messages in thread
From: Alexey Dobriyan @ 2009-05-04  0:16 UTC (permalink / raw)
  To: akpm, mingo; +Cc: linux-kernel, containers

This is a mess.

Pre unified-x86 code did check for breakpoint addr
to be "< TASK_SIZE - 3 (or 7)". This was fine from security POV,
but banned valid breakpoint usage when address is close to TASK_SIZE.
E. g. 1-byte breakpoint at TASK_SIZE - 1 should be allowed, but it wasn't.

Then came commit 84929801e14d968caeb84795bfbb88f04283fbd9
("[PATCH] x86_64: TASK_SIZE fixes for compatibility mode processes")
which for some reason touched ptrace as well and made effective
TASK_SIZE of 32-bit process depending on IA32_PAGE_OFFSET
which is not a constant!:

	#define IA32_PAGE_OFFSET ((current->personality & ADDR_LIMIT_3GB) ? 0xc0000000 : 0xFFFFe000)
				   ^^^^^^^
Maximum addr for breakpoint became dependent on personality of ptracer.

Commit also relaxed danger zone for 32-bit processes from 8 bytes to 4
not taking into account that 8-byte wide breakpoints are possible even
for 32-bit processes. This was fine, however, because 64-bit kernel
addresses are too far from 32-bit ones.

Then came utrace with commit 2047b08be67b70875d8765fc81d34ce28041bec3
("x86: x86 ptrace getreg/putreg merge") which copy-pasted and ifdeffed 32-bit
part of TASK_SIZE_OF() leaving 8-byte issue as-is.

So, what patch fixes?
1) Too strict logic near TASK_SIZE boundary -- as long as we don't cross
   TASK_SIZE_MAX, we're fine.
2) Too smart logic of using breakpoints over non-existent kernel
   boundary -- we should only protect against setting up after
   TASK_SIZE_MAX, the rest is none of kernel business. This fixes
   IA32_PAGE_OFFSET beartrap as well.

As a bonus, remove uberhack and big comment determining DR7 validness,
rewrite with clear algorithm when it's obvious what's going on.

Make DR validness checker suitable for C/R. On restart DR registers
must be checked the same way they are checked on PTRACE_POKEUSR.

Question 1: TIF_DEBUG can set even if none of breakpoints is turned on,
should this be optimized?

Question 2: Breakpoints are allowed to be globally enabled, is this a
security risk?

Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
---
 arch/x86/kernel/ptrace.c |  175 +++++++++++++++++++++++++++-------------------
 1 files changed, 103 insertions(+), 72 deletions(-)

diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c
index 23b7c8f..7d0af2e 100644
--- a/arch/x86/kernel/ptrace.c
+++ b/arch/x86/kernel/ptrace.c
@@ -135,11 +135,6 @@ static int set_segment_reg(struct task_struct *task,
 	return 0;
 }
 
-static unsigned long debugreg_addr_limit(struct task_struct *task)
-{
-	return TASK_SIZE - 3;
-}
-
 #else  /* CONFIG_X86_64 */
 
 #define FLAG_MASK		(FLAG_MASK_32 | X86_EFLAGS_NT)
@@ -263,16 +258,6 @@ static int set_segment_reg(struct task_struct *task,
 
 	return 0;
 }
-
-static unsigned long debugreg_addr_limit(struct task_struct *task)
-{
-#ifdef CONFIG_IA32_EMULATION
-	if (test_tsk_thread_flag(task, TIF_IA32))
-		return IA32_PAGE_OFFSET - 3;
-#endif
-	return TASK_SIZE_MAX - 7;
-}
-
 #endif	/* CONFIG_X86_32 */
 
 static unsigned long get_flags(struct task_struct *task)
@@ -480,77 +465,123 @@ static unsigned long ptrace_get_debugreg(struct task_struct *child, int n)
 	return 0;
 }
 
+static int ptrace_check_debugreg(int _32bit,
+				 unsigned long dr0, unsigned long dr1,
+				 unsigned long dr2, unsigned long dr3,
+				 unsigned long dr6, unsigned long dr7)
+{
+	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
+	unsigned int rw[4];
+	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
+	unsigned int len[4];
+	int n;
+
+	if (dr0 >= TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr1 >= TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr2 >= TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr3 >= TASK_SIZE_MAX)
+		return -EINVAL;
+
+	for (n = 0; n < 4; n++) {
+		rw[n] = (dr7 >> (16 + n * 4)) & 0x3;
+		len[n] = (dr7 >> (16 + n * 4 + 2)) & 0x3;
+
+		if (rw[n] == 0x2)
+			return -EINVAL;
+		if (rw[n] == 0x0 && len[n] != 0x0)
+			return -EINVAL;
+		if (_32bit && len[n] == 0x2)
+			return -EINVAL;
+
+		if (len[n] == 0x0)
+			len[n] = 1;
+		else if (len[n] == 0x1)
+			len[n] = 2;
+		else if (len[n] == 0x2)
+			len[n] = 8;
+		else if (len[n] == 0x3)
+			len[n] = 4;
+		/* From now breakpoint length is in bytes. */
+	}
+
+	if (dr6 & ~0xFFFFFFFFUL)
+		return -EINVAL;
+	if (dr7 & ~0xFFFFFFFFUL)
+		return -EINVAL;
+
+	if (dr7 == 0)
+		return 0;
+
+	if (dr0 + len[0] > TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr1 + len[1] > TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr2 + len[2] > TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr3 + len[3] > TASK_SIZE_MAX)
+		return -EINVAL;
+
+	return 0;
+}
+
 static int ptrace_set_debugreg(struct task_struct *child,
 			       int n, unsigned long data)
 {
-	int i;
+	unsigned long dr0, dr1, dr2, dr3, dr6, dr7;
+	int _32bit;
 
 	if (unlikely(n == 4 || n == 5))
 		return -EIO;
 
-	if (n < 4 && unlikely(data >= debugreg_addr_limit(child)))
-		return -EIO;
-
+	dr0 = child->thread.debugreg0;
+	dr1 = child->thread.debugreg1;
+	dr2 = child->thread.debugreg2;
+	dr3 = child->thread.debugreg3;
+	dr6 = child->thread.debugreg6;
+	dr7 = child->thread.debugreg7;
 	switch (n) {
-	case 0:		child->thread.debugreg0 = data; break;
-	case 1:		child->thread.debugreg1 = data; break;
-	case 2:		child->thread.debugreg2 = data; break;
-	case 3:		child->thread.debugreg3 = data; break;
-
+	case 0:
+		dr0 = data;
+		break;
+	case 1:
+		dr1 = data;
+		break;
+	case 2:
+		dr2 = data;
+		break;
+	case 3:
+		dr3 = data;
+		break;
 	case 6:
-		if ((data & ~0xffffffffUL) != 0)
-			return -EIO;
-		child->thread.debugreg6 = data;
+		dr6 = data;
 		break;
-
 	case 7:
-		/*
-		 * Sanity-check data. Take one half-byte at once with
-		 * check = (val >> (16 + 4*i)) & 0xf. It contains the
-		 * R/Wi and LENi bits; bits 0 and 1 are R/Wi, and bits
-		 * 2 and 3 are LENi. Given a list of invalid values,
-		 * we do mask |= 1 << invalid_value, so that
-		 * (mask >> check) & 1 is a correct test for invalid
-		 * values.
-		 *
-		 * R/Wi contains the type of the breakpoint /
-		 * watchpoint, LENi contains the length of the watched
-		 * data in the watchpoint case.
-		 *
-		 * The invalid values are:
-		 * - LENi == 0x10 (undefined), so mask |= 0x0f00.	[32-bit]
-		 * - R/Wi == 0x10 (break on I/O reads or writes), so
-		 *   mask |= 0x4444.
-		 * - R/Wi == 0x00 && LENi != 0x00, so we have mask |=
-		 *   0x1110.
-		 *
-		 * Finally, mask = 0x0f00 | 0x4444 | 0x1110 == 0x5f54.
-		 *
-		 * See the Intel Manual "System Programming Guide",
-		 * 15.2.4
-		 *
-		 * Note that LENi == 0x10 is defined on x86_64 in long
-		 * mode (i.e. even for 32-bit userspace software, but
-		 * 64-bit kernel), so the x86_64 mask value is 0x5454.
-		 * See the AMD manual no. 24593 (AMD64 System Programming)
-		 */
-#ifdef CONFIG_X86_32
-#define	DR7_MASK	0x5f54
-#else
-#define	DR7_MASK	0x5554
-#endif
-		data &= ~DR_CONTROL_RESERVED;
-		for (i = 0; i < 4; i++)
-			if ((DR7_MASK >> ((data >> (16 + 4*i)) & 0xf)) & 1)
-				return -EIO;
-		child->thread.debugreg7 = data;
-		if (data)
-			set_tsk_thread_flag(child, TIF_DEBUG);
-		else
-			clear_tsk_thread_flag(child, TIF_DEBUG);
+		dr7 = data & ~DR_CONTROL_RESERVED;
 		break;
 	}
 
+	_32bit = (sizeof(unsigned long) == 4);
+#ifdef CONFIG_COMPAT
+	if (test_tsk_thread_flag(child, TIF_IA32))
+		_32bit = 1;
+#endif
+	if (ptrace_check_debugreg(_32bit, dr0, dr1, dr2, dr3, dr6, dr7))
+		return -EIO;
+
+	child->thread.debugreg0 = dr0;
+	child->thread.debugreg1 = dr1;
+	child->thread.debugreg2 = dr2;
+	child->thread.debugreg3 = dr3;
+	child->thread.debugreg6 = dr6;
+	child->thread.debugreg7 = dr7;
+	if (dr7)
+		set_tsk_thread_flag(child, TIF_DEBUG);
+	else
+		clear_tsk_thread_flag(child, TIF_DEBUG);
+
 	return 0;
 }
 
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
       [not found] ` <20090504001601.GL16631-2ev+ksY9ol182hYKe6nXyg@public.gmane.org>
@ 2009-05-04  0:24   ` Alexey Dobriyan
  0 siblings, 0 replies; 16+ messages in thread
From: Alexey Dobriyan @ 2009-05-04  0:24 UTC (permalink / raw)
  To: akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, mingo-X9Un+BFzKDI
  Cc: containers-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> +static int ptrace_check_debugreg(int _32bit,
> +				 unsigned long dr0, unsigned long dr1,
> +				 unsigned long dr2, unsigned long dr3,
> +				 unsigned long dr6, unsigned long dr7)
> +{
> +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> +	unsigned int rw[4];
> +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
								        4 bytes, of course

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
  2009-05-04  0:16 [PATCH] x86: ptrace debugreg checks rewrite Alexey Dobriyan
@ 2009-05-04  0:24 ` Alexey Dobriyan
       [not found]   ` <20090504002431.GA17556-2ev+ksY9ol182hYKe6nXyg@public.gmane.org>
  2009-05-06  9:56   ` Ingo Molnar
       [not found] ` <20090504001601.GL16631-2ev+ksY9ol182hYKe6nXyg@public.gmane.org>
  1 sibling, 2 replies; 16+ messages in thread
From: Alexey Dobriyan @ 2009-05-04  0:24 UTC (permalink / raw)
  To: akpm, mingo; +Cc: linux-kernel, containers

On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> +static int ptrace_check_debugreg(int _32bit,
> +				 unsigned long dr0, unsigned long dr1,
> +				 unsigned long dr2, unsigned long dr3,
> +				 unsigned long dr6, unsigned long dr7)
> +{
> +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> +	unsigned int rw[4];
> +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
								        4 bytes, of course

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
       [not found]   ` <20090504002431.GA17556-2ev+ksY9ol182hYKe6nXyg@public.gmane.org>
@ 2009-05-06  9:56     ` Ingo Molnar
  0 siblings, 0 replies; 16+ messages in thread
From: Ingo Molnar @ 2009-05-06  9:56 UTC (permalink / raw)
  To: Alexey Dobriyan, Roland McGrath, Oleg Nesterov
  Cc: containers-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA,
	akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA


* Alexey Dobriyan <adobriyan-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:

> On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> > +static int ptrace_check_debugreg(int _32bit,
> > +				 unsigned long dr0, unsigned long dr1,
> > +				 unsigned long dr2, unsigned long dr3,
> > +				 unsigned long dr6, unsigned long dr7)
> > +{
> > +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> > +	unsigned int rw[4];
> > +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
> 								        4 bytes, of course

Roland, Oleg, do you have any pending/planned changes in this area?

	Ingo

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
  2009-05-04  0:24 ` Alexey Dobriyan
       [not found]   ` <20090504002431.GA17556-2ev+ksY9ol182hYKe6nXyg@public.gmane.org>
@ 2009-05-06  9:56   ` Ingo Molnar
       [not found]     ` <20090506095629.GC15323-X9Un+BFzKDI@public.gmane.org>
  2009-05-07  0:24     ` Oleg Nesterov
  1 sibling, 2 replies; 16+ messages in thread
From: Ingo Molnar @ 2009-05-06  9:56 UTC (permalink / raw)
  To: Alexey Dobriyan, Roland McGrath, Oleg Nesterov
  Cc: akpm, linux-kernel, containers


* Alexey Dobriyan <adobriyan@gmail.com> wrote:

> On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> > +static int ptrace_check_debugreg(int _32bit,
> > +				 unsigned long dr0, unsigned long dr1,
> > +				 unsigned long dr2, unsigned long dr3,
> > +				 unsigned long dr6, unsigned long dr7)
> > +{
> > +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> > +	unsigned int rw[4];
> > +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
> 								        4 bytes, of course

Roland, Oleg, do you have any pending/planned changes in this area?

	Ingo

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
  2009-05-06  9:56   ` Ingo Molnar
@ 2009-05-06 20:38         ` Roland McGrath
  2009-05-07  0:24     ` Oleg Nesterov
  1 sibling, 0 replies; 16+ messages in thread
From: Roland McGrath @ 2009-05-06 20:38 UTC (permalink / raw)
  To: Ingo Molnar
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b,
	containers-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA,
	Alexey Dobriyan, Oleg Nesterov

> > On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> > > +static int ptrace_check_debugreg(int _32bit,
> > > +				 unsigned long dr0, unsigned long dr1,
> > > +				 unsigned long dr2, unsigned long dr3,
> > > +				 unsigned long dr6, unsigned long dr7)
> > > +{
> > > +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> > > +	unsigned int rw[4];
> > > +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
> > 								        4 bytes, of course
> 
> Roland, Oleg, do you have any pending/planned changes in this area?

I don't.  I expect the only future work on this arch debugreg stuff to be
via the hw_breakpoint work, extending that and/or building new things on it.


Thanks,
Roland

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
@ 2009-05-06 20:38         ` Roland McGrath
  0 siblings, 0 replies; 16+ messages in thread
From: Roland McGrath @ 2009-05-06 20:38 UTC (permalink / raw)
  To: Ingo Molnar
  Cc: Alexey Dobriyan, Oleg Nesterov, akpm, linux-kernel, containers

> > On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> > > +static int ptrace_check_debugreg(int _32bit,
> > > +				 unsigned long dr0, unsigned long dr1,
> > > +				 unsigned long dr2, unsigned long dr3,
> > > +				 unsigned long dr6, unsigned long dr7)
> > > +{
> > > +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> > > +	unsigned int rw[4];
> > > +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
> > 								        4 bytes, of course
> 
> Roland, Oleg, do you have any pending/planned changes in this area?

I don't.  I expect the only future work on this arch debugreg stuff to be
via the hw_breakpoint work, extending that and/or building new things on it.


Thanks,
Roland

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
       [not found]     ` <20090506095629.GC15323-X9Un+BFzKDI@public.gmane.org>
  2009-05-06 20:38         ` Roland McGrath
@ 2009-05-07  0:24       ` Oleg Nesterov
  1 sibling, 0 replies; 16+ messages in thread
From: Oleg Nesterov @ 2009-05-07  0:24 UTC (permalink / raw)
  To: Ingo Molnar
  Cc: containers-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA,
	akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, Alexey Dobriyan,
	Roland McGrath, linux-kernel-u79uwXL29TY76Z2rM5mHXA

On 05/06, Ingo Molnar wrote:
>
> * Alexey Dobriyan <adobriyan-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
>
> > On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> > > +static int ptrace_check_debugreg(int _32bit,
> > > +				 unsigned long dr0, unsigned long dr1,
> > > +				 unsigned long dr2, unsigned long dr3,
> > > +				 unsigned long dr6, unsigned long dr7)
> > > +{
> > > +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> > > +	unsigned int rw[4];
> > > +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
> > 								        4 bytes, of course
>
> Roland, Oleg, do you have any pending/planned changes in this area?

No, I don't (and couldn't since I don't understand this code ;)

Oleg.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
  2009-05-06  9:56   ` Ingo Molnar
       [not found]     ` <20090506095629.GC15323-X9Un+BFzKDI@public.gmane.org>
@ 2009-05-07  0:24     ` Oleg Nesterov
  1 sibling, 0 replies; 16+ messages in thread
From: Oleg Nesterov @ 2009-05-07  0:24 UTC (permalink / raw)
  To: Ingo Molnar
  Cc: Alexey Dobriyan, Roland McGrath, akpm, linux-kernel, containers

On 05/06, Ingo Molnar wrote:
>
> * Alexey Dobriyan <adobriyan@gmail.com> wrote:
>
> > On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> > > +static int ptrace_check_debugreg(int _32bit,
> > > +				 unsigned long dr0, unsigned long dr1,
> > > +				 unsigned long dr2, unsigned long dr3,
> > > +				 unsigned long dr6, unsigned long dr7)
> > > +{
> > > +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> > > +	unsigned int rw[4];
> > > +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
> > 								        4 bytes, of course
>
> Roland, Oleg, do you have any pending/planned changes in this area?

No, I don't (and couldn't since I don't understand this code ;)

Oleg.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
       [not found]         ` <20090506203852.D9AC9FC39E-nL1rrgvulkc2UH6IwYuUx0EOCMrvLtNR@public.gmane.org>
@ 2009-05-07  9:13           ` Ingo Molnar
  0 siblings, 0 replies; 16+ messages in thread
From: Ingo Molnar @ 2009-05-07  9:13 UTC (permalink / raw)
  To: Roland McGrath, K.Prasad, Alan Stern
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b,
	containers-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA,
	Alexey Dobriyan, Oleg Nesterov


* Roland McGrath <roland-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:

> > > On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> > > > +static int ptrace_check_debugreg(int _32bit,
> > > > +				 unsigned long dr0, unsigned long dr1,
> > > > +				 unsigned long dr2, unsigned long dr3,
> > > > +				 unsigned long dr6, unsigned long dr7)
> > > > +{
> > > > +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> > > > +	unsigned int rw[4];
> > > > +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
> > > 								        4 bytes, of course
> > 
> > Roland, Oleg, do you have any pending/planned changes in this area?
> 
> I don't.  I expect the only future work on this arch debugreg 
> stuff to be via the hw_breakpoint work, extending that and/or 
> building new things on it.

Yes. That work, despite being in its upteenth iteration, is taking a 
frustratingly long amount of time to get even to a minimal "review 
passed, lets commit and test this in a tree a bit" intermediate 
stage though.

I've Cc:-ed K.Prasad and Alan Stern. Roland, Oleg, would you be 
interested in having a look at that thread, to help drive it 
forward?

The last iteration was posted to lkml on Apr 24:

  [Patch 00/12] Hardware Breakpoint Interfaces

and it has a good deal of ptrace, signal handling and other impact 
as well.

	Ingo

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
  2009-05-06 20:38         ` Roland McGrath
  (?)
@ 2009-05-07  9:13         ` Ingo Molnar
  -1 siblings, 0 replies; 16+ messages in thread
From: Ingo Molnar @ 2009-05-07  9:13 UTC (permalink / raw)
  To: Roland McGrath, K.Prasad, Alan Stern
  Cc: Alexey Dobriyan, Oleg Nesterov, akpm, linux-kernel, containers


* Roland McGrath <roland@redhat.com> wrote:

> > > On Mon, May 04, 2009 at 04:16:01AM +0400, Alexey Dobriyan wrote:
> > > > +static int ptrace_check_debugreg(int _32bit,
> > > > +				 unsigned long dr0, unsigned long dr1,
> > > > +				 unsigned long dr2, unsigned long dr3,
> > > > +				 unsigned long dr6, unsigned long dr7)
> > > > +{
> > > > +	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
> > > > +	unsigned int rw[4];
> > > > +	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
> > > 								        4 bytes, of course
> > 
> > Roland, Oleg, do you have any pending/planned changes in this area?
> 
> I don't.  I expect the only future work on this arch debugreg 
> stuff to be via the hw_breakpoint work, extending that and/or 
> building new things on it.

Yes. That work, despite being in its upteenth iteration, is taking a 
frustratingly long amount of time to get even to a minimal "review 
passed, lets commit and test this in a tree a bit" intermediate 
stage though.

I've Cc:-ed K.Prasad and Alan Stern. Roland, Oleg, would you be 
interested in having a look at that thread, to help drive it 
forward?

The last iteration was posted to lkml on Apr 24:

  [Patch 00/12] Hardware Breakpoint Interfaces

and it has a good deal of ptrace, signal handling and other impact 
as well.

	Ingo

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
  2009-06-23  9:47   ` Alexey Dobriyan
@ 2009-06-30 21:48     ` Ingo Molnar
  0 siblings, 0 replies; 16+ messages in thread
From: Ingo Molnar @ 2009-06-30 21:48 UTC (permalink / raw)
  To: Alexey Dobriyan
  Cc: Frédéric Weisbecker, mingo, tglx, hpa, x86, linux-kernel


* Alexey Dobriyan <adobriyan@gmail.com> wrote:

> On Tue, Jun 23, 2009 at 10:55:50AM +0200, Ingo Molnar wrote:
> > 
> > * Alexey Dobriyan <adobriyan@gmail.com> wrote:
> > 
> > > This is a mess.
> > > 
> > > Pre unified-x86 code did check for breakpoint addr
> > > to be "< TASK_SIZE - 3 (or 7)". This was fine from security POV,
> > > but banned valid breakpoint usage when address is close to TASK_SIZE.
> > > E. g. 1-byte breakpoint at TASK_SIZE - 1 should be allowed, but it wasn't.
> > > 
> > > Then came commit 84929801e14d968caeb84795bfbb88f04283fbd9
> > > ("[PATCH] x86_64: TASK_SIZE fixes for compatibility mode processes")
> > > which for some reason touched ptrace as well and made effective
> > > TASK_SIZE of 32-bit process depending on IA32_PAGE_OFFSET
> > > which is not a constant!:
> > > 
> > > 	#define IA32_PAGE_OFFSET ((current->personality & ADDR_LIMIT_3GB) ? 0xc0000000 : 0xFFFFe000)
> > > 				   ^^^^^^^
> > > Maximum addr for breakpoint became dependent on personality of ptracer.
> > > 
> > > Commit also relaxed danger zone for 32-bit processes from 8 bytes to 4
> > > not taking into account that 8-byte wide breakpoints are possible even
> > > for 32-bit processes. This was fine, however, because 64-bit kernel
> > > addresses are too far from 32-bit ones.
> > > 
> > > Then came utrace with commit 2047b08be67b70875d8765fc81d34ce28041bec3
> > > ("x86: x86 ptrace getreg/putreg merge") which copy-pasted and ifdeffed 32-bit
> > > part of TASK_SIZE_OF() leaving 8-byte issue as-is.
> > > 
> > > So, what patch fixes?
> > > 1) Too strict logic near TASK_SIZE boundary -- as long as we don't cross
> > >    TASK_SIZE_MAX, we're fine.
> > > 2) Too smart logic of using breakpoints over non-existent kernel
> > >    boundary -- we should only protect against setting up after
> > >    TASK_SIZE_MAX, the rest is none of kernel business. This fixes
> > >    IA32_PAGE_OFFSET beartrap as well.
> > > 
> > > As a bonus, remove uberhack and big comment determining DR7 validness,
> > > rewrite with clear algorithm when it's obvious what's going on.
> > > 
> > > Make DR validness checker suitable for C/R. On restart DR registers
> > > must be checked the same way they are checked on PTRACE_POKEUSR.
> > > 
> > > Question 1: TIF_DEBUG can set even if none of breakpoints is turned on,
> > > should this be optimized?
> > > 
> > > Question 2: Breakpoints are allowed to be globally enabled, is this a
> > > security risk?
> > > 
> > > Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
> > 
> > Please base this on the latest x86 tree:
> > 
> >   http://people.redhat.com/mingo/tip.git/README
> > 
> > which has the hw-debug rework with debug register ops abstracted out 
> > already - making your patch not apply at all.
> 
> Why haven't you applied this patch 1.5 months ago when it was 
> ready? Patch hasn't changes since then except just one typo in 
> comment.

The debug register abstraction patches have been in the works for a 
year.

	Ingo

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
  2009-06-23  8:55 ` Ingo Molnar
@ 2009-06-23  9:47   ` Alexey Dobriyan
  2009-06-30 21:48     ` Ingo Molnar
  0 siblings, 1 reply; 16+ messages in thread
From: Alexey Dobriyan @ 2009-06-23  9:47 UTC (permalink / raw)
  To: Ingo Molnar
  Cc: Frédéric Weisbecker, mingo, tglx, hpa, x86, linux-kernel

On Tue, Jun 23, 2009 at 10:55:50AM +0200, Ingo Molnar wrote:
> 
> * Alexey Dobriyan <adobriyan@gmail.com> wrote:
> 
> > This is a mess.
> > 
> > Pre unified-x86 code did check for breakpoint addr
> > to be "< TASK_SIZE - 3 (or 7)". This was fine from security POV,
> > but banned valid breakpoint usage when address is close to TASK_SIZE.
> > E. g. 1-byte breakpoint at TASK_SIZE - 1 should be allowed, but it wasn't.
> > 
> > Then came commit 84929801e14d968caeb84795bfbb88f04283fbd9
> > ("[PATCH] x86_64: TASK_SIZE fixes for compatibility mode processes")
> > which for some reason touched ptrace as well and made effective
> > TASK_SIZE of 32-bit process depending on IA32_PAGE_OFFSET
> > which is not a constant!:
> > 
> > 	#define IA32_PAGE_OFFSET ((current->personality & ADDR_LIMIT_3GB) ? 0xc0000000 : 0xFFFFe000)
> > 				   ^^^^^^^
> > Maximum addr for breakpoint became dependent on personality of ptracer.
> > 
> > Commit also relaxed danger zone for 32-bit processes from 8 bytes to 4
> > not taking into account that 8-byte wide breakpoints are possible even
> > for 32-bit processes. This was fine, however, because 64-bit kernel
> > addresses are too far from 32-bit ones.
> > 
> > Then came utrace with commit 2047b08be67b70875d8765fc81d34ce28041bec3
> > ("x86: x86 ptrace getreg/putreg merge") which copy-pasted and ifdeffed 32-bit
> > part of TASK_SIZE_OF() leaving 8-byte issue as-is.
> > 
> > So, what patch fixes?
> > 1) Too strict logic near TASK_SIZE boundary -- as long as we don't cross
> >    TASK_SIZE_MAX, we're fine.
> > 2) Too smart logic of using breakpoints over non-existent kernel
> >    boundary -- we should only protect against setting up after
> >    TASK_SIZE_MAX, the rest is none of kernel business. This fixes
> >    IA32_PAGE_OFFSET beartrap as well.
> > 
> > As a bonus, remove uberhack and big comment determining DR7 validness,
> > rewrite with clear algorithm when it's obvious what's going on.
> > 
> > Make DR validness checker suitable for C/R. On restart DR registers
> > must be checked the same way they are checked on PTRACE_POKEUSR.
> > 
> > Question 1: TIF_DEBUG can set even if none of breakpoints is turned on,
> > should this be optimized?
> > 
> > Question 2: Breakpoints are allowed to be globally enabled, is this a
> > security risk?
> > 
> > Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
> 
> Please base this on the latest x86 tree:
> 
>   http://people.redhat.com/mingo/tip.git/README
> 
> which has the hw-debug rework with debug register ops abstracted out 
> already - making your patch not apply at all.

Why haven't you applied this patch 1.5 months ago when it was ready?
Patch hasn't changes since then except just one typo in comment.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] x86: ptrace debugreg checks rewrite
  2009-06-22 21:09 Alexey Dobriyan
@ 2009-06-23  8:55 ` Ingo Molnar
  2009-06-23  9:47   ` Alexey Dobriyan
  0 siblings, 1 reply; 16+ messages in thread
From: Ingo Molnar @ 2009-06-23  8:55 UTC (permalink / raw)
  To: Alexey Dobriyan, Frédéric Weisbecker
  Cc: mingo, tglx, hpa, x86, linux-kernel


* Alexey Dobriyan <adobriyan@gmail.com> wrote:

> This is a mess.
> 
> Pre unified-x86 code did check for breakpoint addr
> to be "< TASK_SIZE - 3 (or 7)". This was fine from security POV,
> but banned valid breakpoint usage when address is close to TASK_SIZE.
> E. g. 1-byte breakpoint at TASK_SIZE - 1 should be allowed, but it wasn't.
> 
> Then came commit 84929801e14d968caeb84795bfbb88f04283fbd9
> ("[PATCH] x86_64: TASK_SIZE fixes for compatibility mode processes")
> which for some reason touched ptrace as well and made effective
> TASK_SIZE of 32-bit process depending on IA32_PAGE_OFFSET
> which is not a constant!:
> 
> 	#define IA32_PAGE_OFFSET ((current->personality & ADDR_LIMIT_3GB) ? 0xc0000000 : 0xFFFFe000)
> 				   ^^^^^^^
> Maximum addr for breakpoint became dependent on personality of ptracer.
> 
> Commit also relaxed danger zone for 32-bit processes from 8 bytes to 4
> not taking into account that 8-byte wide breakpoints are possible even
> for 32-bit processes. This was fine, however, because 64-bit kernel
> addresses are too far from 32-bit ones.
> 
> Then came utrace with commit 2047b08be67b70875d8765fc81d34ce28041bec3
> ("x86: x86 ptrace getreg/putreg merge") which copy-pasted and ifdeffed 32-bit
> part of TASK_SIZE_OF() leaving 8-byte issue as-is.
> 
> So, what patch fixes?
> 1) Too strict logic near TASK_SIZE boundary -- as long as we don't cross
>    TASK_SIZE_MAX, we're fine.
> 2) Too smart logic of using breakpoints over non-existent kernel
>    boundary -- we should only protect against setting up after
>    TASK_SIZE_MAX, the rest is none of kernel business. This fixes
>    IA32_PAGE_OFFSET beartrap as well.
> 
> As a bonus, remove uberhack and big comment determining DR7 validness,
> rewrite with clear algorithm when it's obvious what's going on.
> 
> Make DR validness checker suitable for C/R. On restart DR registers
> must be checked the same way they are checked on PTRACE_POKEUSR.
> 
> Question 1: TIF_DEBUG can set even if none of breakpoints is turned on,
> should this be optimized?
> 
> Question 2: Breakpoints are allowed to be globally enabled, is this a
> security risk?
> 
> Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>

Please base this on the latest x86 tree:

  http://people.redhat.com/mingo/tip.git/README

which has the hw-debug rework with debug register ops abstracted out 
already - making your patch not apply at all.

Thanks,

	Ingo

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH] x86: ptrace debugreg checks rewrite
@ 2009-06-22 21:09 Alexey Dobriyan
  2009-06-23  8:55 ` Ingo Molnar
  0 siblings, 1 reply; 16+ messages in thread
From: Alexey Dobriyan @ 2009-06-22 21:09 UTC (permalink / raw)
  To: mingo, tglx, hpa; +Cc: x86, linux-kernel

This is a mess.

Pre unified-x86 code did check for breakpoint addr
to be "< TASK_SIZE - 3 (or 7)". This was fine from security POV,
but banned valid breakpoint usage when address is close to TASK_SIZE.
E. g. 1-byte breakpoint at TASK_SIZE - 1 should be allowed, but it wasn't.

Then came commit 84929801e14d968caeb84795bfbb88f04283fbd9
("[PATCH] x86_64: TASK_SIZE fixes for compatibility mode processes")
which for some reason touched ptrace as well and made effective
TASK_SIZE of 32-bit process depending on IA32_PAGE_OFFSET
which is not a constant!:

	#define IA32_PAGE_OFFSET ((current->personality & ADDR_LIMIT_3GB) ? 0xc0000000 : 0xFFFFe000)
				   ^^^^^^^
Maximum addr for breakpoint became dependent on personality of ptracer.

Commit also relaxed danger zone for 32-bit processes from 8 bytes to 4
not taking into account that 8-byte wide breakpoints are possible even
for 32-bit processes. This was fine, however, because 64-bit kernel
addresses are too far from 32-bit ones.

Then came utrace with commit 2047b08be67b70875d8765fc81d34ce28041bec3
("x86: x86 ptrace getreg/putreg merge") which copy-pasted and ifdeffed 32-bit
part of TASK_SIZE_OF() leaving 8-byte issue as-is.

So, what patch fixes?
1) Too strict logic near TASK_SIZE boundary -- as long as we don't cross
   TASK_SIZE_MAX, we're fine.
2) Too smart logic of using breakpoints over non-existent kernel
   boundary -- we should only protect against setting up after
   TASK_SIZE_MAX, the rest is none of kernel business. This fixes
   IA32_PAGE_OFFSET beartrap as well.

As a bonus, remove uberhack and big comment determining DR7 validness,
rewrite with clear algorithm when it's obvious what's going on.

Make DR validness checker suitable for C/R. On restart DR registers
must be checked the same way they are checked on PTRACE_POKEUSR.

Question 1: TIF_DEBUG can set even if none of breakpoints is turned on,
should this be optimized?

Question 2: Breakpoints are allowed to be globally enabled, is this a
security risk?

Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
---
 arch/x86/kernel/ptrace.c |  175 +++++++++++++++++++++++++++-------------------
 1 files changed, 103 insertions(+), 72 deletions(-)

diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c
index 09ecbde..9b4cacf 100644
--- a/arch/x86/kernel/ptrace.c
+++ b/arch/x86/kernel/ptrace.c
@@ -136,11 +136,6 @@ static int set_segment_reg(struct task_struct *task,
 	return 0;
 }
 
-static unsigned long debugreg_addr_limit(struct task_struct *task)
-{
-	return TASK_SIZE - 3;
-}
-
 #else  /* CONFIG_X86_64 */
 
 #define FLAG_MASK		(FLAG_MASK_32 | X86_EFLAGS_NT)
@@ -264,16 +259,6 @@ static int set_segment_reg(struct task_struct *task,
 
 	return 0;
 }
-
-static unsigned long debugreg_addr_limit(struct task_struct *task)
-{
-#ifdef CONFIG_IA32_EMULATION
-	if (test_tsk_thread_flag(task, TIF_IA32))
-		return IA32_PAGE_OFFSET - 3;
-#endif
-	return TASK_SIZE_MAX - 7;
-}
-
 #endif	/* CONFIG_X86_32 */
 
 static unsigned long get_flags(struct task_struct *task)
@@ -481,77 +466,123 @@ static unsigned long ptrace_get_debugreg(struct task_struct *child, int n)
 	return 0;
 }
 
+static int ptrace_check_debugreg(int _32bit,
+				 unsigned long dr0, unsigned long dr1,
+				 unsigned long dr2, unsigned long dr3,
+				 unsigned long dr6, unsigned long dr7)
+{
+	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
+	unsigned int rw[4];
+	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: 4 bytes */
+	unsigned int len[4];
+	int n;
+
+	if (dr0 >= TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr1 >= TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr2 >= TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr3 >= TASK_SIZE_MAX)
+		return -EINVAL;
+
+	for (n = 0; n < 4; n++) {
+		rw[n] = (dr7 >> (16 + n * 4)) & 0x3;
+		len[n] = (dr7 >> (16 + n * 4 + 2)) & 0x3;
+
+		if (rw[n] == 0x2)
+			return -EINVAL;
+		if (rw[n] == 0x0 && len[n] != 0x0)
+			return -EINVAL;
+		if (_32bit && len[n] == 0x2)
+			return -EINVAL;
+
+		if (len[n] == 0x0)
+			len[n] = 1;
+		else if (len[n] == 0x1)
+			len[n] = 2;
+		else if (len[n] == 0x2)
+			len[n] = 8;
+		else if (len[n] == 0x3)
+			len[n] = 4;
+		/* From now breakpoint length is in bytes. */
+	}
+
+	if (dr6 & ~0xFFFFFFFFUL)
+		return -EINVAL;
+	if (dr7 & ~0xFFFFFFFFUL)
+		return -EINVAL;
+
+	if (dr7 == 0)
+		return 0;
+
+	if (dr0 + len[0] > TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr1 + len[1] > TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr2 + len[2] > TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr3 + len[3] > TASK_SIZE_MAX)
+		return -EINVAL;
+
+	return 0;
+}
+
 static int ptrace_set_debugreg(struct task_struct *child,
 			       int n, unsigned long data)
 {
-	int i;
+	unsigned long dr0, dr1, dr2, dr3, dr6, dr7;
+	int _32bit;
 
 	if (unlikely(n == 4 || n == 5))
 		return -EIO;
 
-	if (n < 4 && unlikely(data >= debugreg_addr_limit(child)))
-		return -EIO;
-
+	dr0 = child->thread.debugreg0;
+	dr1 = child->thread.debugreg1;
+	dr2 = child->thread.debugreg2;
+	dr3 = child->thread.debugreg3;
+	dr6 = child->thread.debugreg6;
+	dr7 = child->thread.debugreg7;
 	switch (n) {
-	case 0:		child->thread.debugreg0 = data; break;
-	case 1:		child->thread.debugreg1 = data; break;
-	case 2:		child->thread.debugreg2 = data; break;
-	case 3:		child->thread.debugreg3 = data; break;
-
+	case 0:
+		dr0 = data;
+		break;
+	case 1:
+		dr1 = data;
+		break;
+	case 2:
+		dr2 = data;
+		break;
+	case 3:
+		dr3 = data;
+		break;
 	case 6:
-		if ((data & ~0xffffffffUL) != 0)
-			return -EIO;
-		child->thread.debugreg6 = data;
+		dr6 = data;
 		break;
-
 	case 7:
-		/*
-		 * Sanity-check data. Take one half-byte at once with
-		 * check = (val >> (16 + 4*i)) & 0xf. It contains the
-		 * R/Wi and LENi bits; bits 0 and 1 are R/Wi, and bits
-		 * 2 and 3 are LENi. Given a list of invalid values,
-		 * we do mask |= 1 << invalid_value, so that
-		 * (mask >> check) & 1 is a correct test for invalid
-		 * values.
-		 *
-		 * R/Wi contains the type of the breakpoint /
-		 * watchpoint, LENi contains the length of the watched
-		 * data in the watchpoint case.
-		 *
-		 * The invalid values are:
-		 * - LENi == 0x10 (undefined), so mask |= 0x0f00.	[32-bit]
-		 * - R/Wi == 0x10 (break on I/O reads or writes), so
-		 *   mask |= 0x4444.
-		 * - R/Wi == 0x00 && LENi != 0x00, so we have mask |=
-		 *   0x1110.
-		 *
-		 * Finally, mask = 0x0f00 | 0x4444 | 0x1110 == 0x5f54.
-		 *
-		 * See the Intel Manual "System Programming Guide",
-		 * 15.2.4
-		 *
-		 * Note that LENi == 0x10 is defined on x86_64 in long
-		 * mode (i.e. even for 32-bit userspace software, but
-		 * 64-bit kernel), so the x86_64 mask value is 0x5454.
-		 * See the AMD manual no. 24593 (AMD64 System Programming)
-		 */
-#ifdef CONFIG_X86_32
-#define	DR7_MASK	0x5f54
-#else
-#define	DR7_MASK	0x5554
-#endif
-		data &= ~DR_CONTROL_RESERVED;
-		for (i = 0; i < 4; i++)
-			if ((DR7_MASK >> ((data >> (16 + 4*i)) & 0xf)) & 1)
-				return -EIO;
-		child->thread.debugreg7 = data;
-		if (data)
-			set_tsk_thread_flag(child, TIF_DEBUG);
-		else
-			clear_tsk_thread_flag(child, TIF_DEBUG);
+		dr7 = data & ~DR_CONTROL_RESERVED;
 		break;
 	}
 
+	_32bit = (sizeof(unsigned long) == 4);
+#ifdef CONFIG_COMPAT
+	if (test_tsk_thread_flag(child, TIF_IA32))
+		_32bit = 1;
+#endif
+	if (ptrace_check_debugreg(_32bit, dr0, dr1, dr2, dr3, dr6, dr7))
+		return -EIO;
+
+	child->thread.debugreg0 = dr0;
+	child->thread.debugreg1 = dr1;
+	child->thread.debugreg2 = dr2;
+	child->thread.debugreg3 = dr3;
+	child->thread.debugreg6 = dr6;
+	child->thread.debugreg7 = dr7;
+	if (dr7)
+		set_tsk_thread_flag(child, TIF_DEBUG);
+	else
+		clear_tsk_thread_flag(child, TIF_DEBUG);
+
 	return 0;
 }
 

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH] x86: ptrace debugreg checks rewrite
@ 2009-05-04  0:16 Alexey Dobriyan
  0 siblings, 0 replies; 16+ messages in thread
From: Alexey Dobriyan @ 2009-05-04  0:16 UTC (permalink / raw)
  To: akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, mingo-X9Un+BFzKDI
  Cc: containers-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

This is a mess.

Pre unified-x86 code did check for breakpoint addr
to be "< TASK_SIZE - 3 (or 7)". This was fine from security POV,
but banned valid breakpoint usage when address is close to TASK_SIZE.
E. g. 1-byte breakpoint at TASK_SIZE - 1 should be allowed, but it wasn't.

Then came commit 84929801e14d968caeb84795bfbb88f04283fbd9
("[PATCH] x86_64: TASK_SIZE fixes for compatibility mode processes")
which for some reason touched ptrace as well and made effective
TASK_SIZE of 32-bit process depending on IA32_PAGE_OFFSET
which is not a constant!:

	#define IA32_PAGE_OFFSET ((current->personality & ADDR_LIMIT_3GB) ? 0xc0000000 : 0xFFFFe000)
				   ^^^^^^^
Maximum addr for breakpoint became dependent on personality of ptracer.

Commit also relaxed danger zone for 32-bit processes from 8 bytes to 4
not taking into account that 8-byte wide breakpoints are possible even
for 32-bit processes. This was fine, however, because 64-bit kernel
addresses are too far from 32-bit ones.

Then came utrace with commit 2047b08be67b70875d8765fc81d34ce28041bec3
("x86: x86 ptrace getreg/putreg merge") which copy-pasted and ifdeffed 32-bit
part of TASK_SIZE_OF() leaving 8-byte issue as-is.

So, what patch fixes?
1) Too strict logic near TASK_SIZE boundary -- as long as we don't cross
   TASK_SIZE_MAX, we're fine.
2) Too smart logic of using breakpoints over non-existent kernel
   boundary -- we should only protect against setting up after
   TASK_SIZE_MAX, the rest is none of kernel business. This fixes
   IA32_PAGE_OFFSET beartrap as well.

As a bonus, remove uberhack and big comment determining DR7 validness,
rewrite with clear algorithm when it's obvious what's going on.

Make DR validness checker suitable for C/R. On restart DR registers
must be checked the same way they are checked on PTRACE_POKEUSR.

Question 1: TIF_DEBUG can set even if none of breakpoints is turned on,
should this be optimized?

Question 2: Breakpoints are allowed to be globally enabled, is this a
security risk?

Signed-off-by: Alexey Dobriyan <adobriyan-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
 arch/x86/kernel/ptrace.c |  175 +++++++++++++++++++++++++++-------------------
 1 files changed, 103 insertions(+), 72 deletions(-)

diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c
index 23b7c8f..7d0af2e 100644
--- a/arch/x86/kernel/ptrace.c
+++ b/arch/x86/kernel/ptrace.c
@@ -135,11 +135,6 @@ static int set_segment_reg(struct task_struct *task,
 	return 0;
 }
 
-static unsigned long debugreg_addr_limit(struct task_struct *task)
-{
-	return TASK_SIZE - 3;
-}
-
 #else  /* CONFIG_X86_64 */
 
 #define FLAG_MASK		(FLAG_MASK_32 | X86_EFLAGS_NT)
@@ -263,16 +258,6 @@ static int set_segment_reg(struct task_struct *task,
 
 	return 0;
 }
-
-static unsigned long debugreg_addr_limit(struct task_struct *task)
-{
-#ifdef CONFIG_IA32_EMULATION
-	if (test_tsk_thread_flag(task, TIF_IA32))
-		return IA32_PAGE_OFFSET - 3;
-#endif
-	return TASK_SIZE_MAX - 7;
-}
-
 #endif	/* CONFIG_X86_32 */
 
 static unsigned long get_flags(struct task_struct *task)
@@ -480,77 +465,123 @@ static unsigned long ptrace_get_debugreg(struct task_struct *child, int n)
 	return 0;
 }
 
+static int ptrace_check_debugreg(int _32bit,
+				 unsigned long dr0, unsigned long dr1,
+				 unsigned long dr2, unsigned long dr3,
+				 unsigned long dr6, unsigned long dr7)
+{
+	/* Breakpoint type: 00: --x, 01: -w-, 10: undefined, 11: rw- */
+	unsigned int rw[4];
+	/* Breakpoint length: 00: 1 byte, 01: 2 bytes, 10: 8 bytes, 11: bytes */
+	unsigned int len[4];
+	int n;
+
+	if (dr0 >= TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr1 >= TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr2 >= TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr3 >= TASK_SIZE_MAX)
+		return -EINVAL;
+
+	for (n = 0; n < 4; n++) {
+		rw[n] = (dr7 >> (16 + n * 4)) & 0x3;
+		len[n] = (dr7 >> (16 + n * 4 + 2)) & 0x3;
+
+		if (rw[n] == 0x2)
+			return -EINVAL;
+		if (rw[n] == 0x0 && len[n] != 0x0)
+			return -EINVAL;
+		if (_32bit && len[n] == 0x2)
+			return -EINVAL;
+
+		if (len[n] == 0x0)
+			len[n] = 1;
+		else if (len[n] == 0x1)
+			len[n] = 2;
+		else if (len[n] == 0x2)
+			len[n] = 8;
+		else if (len[n] == 0x3)
+			len[n] = 4;
+		/* From now breakpoint length is in bytes. */
+	}
+
+	if (dr6 & ~0xFFFFFFFFUL)
+		return -EINVAL;
+	if (dr7 & ~0xFFFFFFFFUL)
+		return -EINVAL;
+
+	if (dr7 == 0)
+		return 0;
+
+	if (dr0 + len[0] > TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr1 + len[1] > TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr2 + len[2] > TASK_SIZE_MAX)
+		return -EINVAL;
+	if (dr3 + len[3] > TASK_SIZE_MAX)
+		return -EINVAL;
+
+	return 0;
+}
+
 static int ptrace_set_debugreg(struct task_struct *child,
 			       int n, unsigned long data)
 {
-	int i;
+	unsigned long dr0, dr1, dr2, dr3, dr6, dr7;
+	int _32bit;
 
 	if (unlikely(n == 4 || n == 5))
 		return -EIO;
 
-	if (n < 4 && unlikely(data >= debugreg_addr_limit(child)))
-		return -EIO;
-
+	dr0 = child->thread.debugreg0;
+	dr1 = child->thread.debugreg1;
+	dr2 = child->thread.debugreg2;
+	dr3 = child->thread.debugreg3;
+	dr6 = child->thread.debugreg6;
+	dr7 = child->thread.debugreg7;
 	switch (n) {
-	case 0:		child->thread.debugreg0 = data; break;
-	case 1:		child->thread.debugreg1 = data; break;
-	case 2:		child->thread.debugreg2 = data; break;
-	case 3:		child->thread.debugreg3 = data; break;
-
+	case 0:
+		dr0 = data;
+		break;
+	case 1:
+		dr1 = data;
+		break;
+	case 2:
+		dr2 = data;
+		break;
+	case 3:
+		dr3 = data;
+		break;
 	case 6:
-		if ((data & ~0xffffffffUL) != 0)
-			return -EIO;
-		child->thread.debugreg6 = data;
+		dr6 = data;
 		break;
-
 	case 7:
-		/*
-		 * Sanity-check data. Take one half-byte at once with
-		 * check = (val >> (16 + 4*i)) & 0xf. It contains the
-		 * R/Wi and LENi bits; bits 0 and 1 are R/Wi, and bits
-		 * 2 and 3 are LENi. Given a list of invalid values,
-		 * we do mask |= 1 << invalid_value, so that
-		 * (mask >> check) & 1 is a correct test for invalid
-		 * values.
-		 *
-		 * R/Wi contains the type of the breakpoint /
-		 * watchpoint, LENi contains the length of the watched
-		 * data in the watchpoint case.
-		 *
-		 * The invalid values are:
-		 * - LENi == 0x10 (undefined), so mask |= 0x0f00.	[32-bit]
-		 * - R/Wi == 0x10 (break on I/O reads or writes), so
-		 *   mask |= 0x4444.
-		 * - R/Wi == 0x00 && LENi != 0x00, so we have mask |=
-		 *   0x1110.
-		 *
-		 * Finally, mask = 0x0f00 | 0x4444 | 0x1110 == 0x5f54.
-		 *
-		 * See the Intel Manual "System Programming Guide",
-		 * 15.2.4
-		 *
-		 * Note that LENi == 0x10 is defined on x86_64 in long
-		 * mode (i.e. even for 32-bit userspace software, but
-		 * 64-bit kernel), so the x86_64 mask value is 0x5454.
-		 * See the AMD manual no. 24593 (AMD64 System Programming)
-		 */
-#ifdef CONFIG_X86_32
-#define	DR7_MASK	0x5f54
-#else
-#define	DR7_MASK	0x5554
-#endif
-		data &= ~DR_CONTROL_RESERVED;
-		for (i = 0; i < 4; i++)
-			if ((DR7_MASK >> ((data >> (16 + 4*i)) & 0xf)) & 1)
-				return -EIO;
-		child->thread.debugreg7 = data;
-		if (data)
-			set_tsk_thread_flag(child, TIF_DEBUG);
-		else
-			clear_tsk_thread_flag(child, TIF_DEBUG);
+		dr7 = data & ~DR_CONTROL_RESERVED;
 		break;
 	}
 
+	_32bit = (sizeof(unsigned long) == 4);
+#ifdef CONFIG_COMPAT
+	if (test_tsk_thread_flag(child, TIF_IA32))
+		_32bit = 1;
+#endif
+	if (ptrace_check_debugreg(_32bit, dr0, dr1, dr2, dr3, dr6, dr7))
+		return -EIO;
+
+	child->thread.debugreg0 = dr0;
+	child->thread.debugreg1 = dr1;
+	child->thread.debugreg2 = dr2;
+	child->thread.debugreg3 = dr3;
+	child->thread.debugreg6 = dr6;
+	child->thread.debugreg7 = dr7;
+	if (dr7)
+		set_tsk_thread_flag(child, TIF_DEBUG);
+	else
+		clear_tsk_thread_flag(child, TIF_DEBUG);
+
 	return 0;
 }
 
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2009-06-30 21:48 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-05-04  0:16 [PATCH] x86: ptrace debugreg checks rewrite Alexey Dobriyan
2009-05-04  0:24 ` Alexey Dobriyan
     [not found]   ` <20090504002431.GA17556-2ev+ksY9ol182hYKe6nXyg@public.gmane.org>
2009-05-06  9:56     ` Ingo Molnar
2009-05-06  9:56   ` Ingo Molnar
     [not found]     ` <20090506095629.GC15323-X9Un+BFzKDI@public.gmane.org>
2009-05-06 20:38       ` Roland McGrath
2009-05-06 20:38         ` Roland McGrath
2009-05-07  9:13         ` Ingo Molnar
     [not found]         ` <20090506203852.D9AC9FC39E-nL1rrgvulkc2UH6IwYuUx0EOCMrvLtNR@public.gmane.org>
2009-05-07  9:13           ` Ingo Molnar
2009-05-07  0:24       ` Oleg Nesterov
2009-05-07  0:24     ` Oleg Nesterov
     [not found] ` <20090504001601.GL16631-2ev+ksY9ol182hYKe6nXyg@public.gmane.org>
2009-05-04  0:24   ` Alexey Dobriyan
2009-05-04  0:16 Alexey Dobriyan
2009-06-22 21:09 Alexey Dobriyan
2009-06-23  8:55 ` Ingo Molnar
2009-06-23  9:47   ` Alexey Dobriyan
2009-06-30 21:48     ` Ingo Molnar

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.