diff mbox

lkdtm: add bad USER_DS test

Message ID 20170323203419.GA62859@beast (mailing list archive)
State New, archived
Headers show

Commit Message

Kees Cook March 23, 2017, 8:34 p.m. UTC
This adds CORRUPT_USER_DS to check that the get_fs() test on syscall return
still sees USER_DS during the new VERIFY_PRE_USERMODE_STATE checks.

Signed-off-by: Kees Cook <keescook@chromium.org>
---
 drivers/misc/lkdtm.h      |  1 +
 drivers/misc/lkdtm_bugs.c | 20 ++++++++++++++++++++
 drivers/misc/lkdtm_core.c |  1 +
 3 files changed, 22 insertions(+)

Comments

Heiko Carstens March 24, 2017, 8:14 a.m. UTC | #1
On Thu, Mar 23, 2017 at 01:34:19PM -0700, Kees Cook wrote:
> This adds CORRUPT_USER_DS to check that the get_fs() test on syscall return
> still sees USER_DS during the new VERIFY_PRE_USERMODE_STATE checks.
> 
> Signed-off-by: Kees Cook <keescook@chromium.org>

...

> +void lkdtm_CORRUPT_USER_DS(void)
> +{
> +	/*
> +	 * Test that USER_DS has been set correctly on exiting a syscall.
> +	 * Since setting this higher than USER_DS (TASK_SIZE) would introduce
> +	 * an exploitable condition, we lower it instead, since that should
> +	 * not create as large a problem on an unprotected system.
> +	 */
> +	mm_segment_t lowfs;
> +#ifdef MAKE_MM_SEG
> +	lowfs = MAKE_MM_SEG(TASK_SIZE - PAGE_SIZE);
> +#else
> +	lowfs = TASK_SIZE - PAGE_SIZE;
> +#endif
> +
> +	pr_info("setting bad task size limit\n");
> +	set_fs(lowfs);
> +}

This won't work on architectures where the set_fs() argument does not
contain an address but an address space identifier. This is true e.g. for
s390 and as far as I know also for sparc.
On s390 we have complete distinct address spaces for kernel and user space
that each start at address zero.
Thomas Garnier March 24, 2017, 3:17 p.m. UTC | #2
On Fri, Mar 24, 2017 at 1:14 AM, Heiko Carstens
<heiko.carstens@de.ibm.com> wrote:
> On Thu, Mar 23, 2017 at 01:34:19PM -0700, Kees Cook wrote:
>> This adds CORRUPT_USER_DS to check that the get_fs() test on syscall return
>> still sees USER_DS during the new VERIFY_PRE_USERMODE_STATE checks.
>>
>> Signed-off-by: Kees Cook <keescook@chromium.org>
>
> ...
>
>> +void lkdtm_CORRUPT_USER_DS(void)
>> +{
>> +     /*
>> +      * Test that USER_DS has been set correctly on exiting a syscall.
>> +      * Since setting this higher than USER_DS (TASK_SIZE) would introduce
>> +      * an exploitable condition, we lower it instead, since that should
>> +      * not create as large a problem on an unprotected system.
>> +      */
>> +     mm_segment_t lowfs;
>> +#ifdef MAKE_MM_SEG
>> +     lowfs = MAKE_MM_SEG(TASK_SIZE - PAGE_SIZE);
>> +#else
>> +     lowfs = TASK_SIZE - PAGE_SIZE;
>> +#endif
>> +
>> +     pr_info("setting bad task size limit\n");
>> +     set_fs(lowfs);
>> +}
>
> This won't work on architectures where the set_fs() argument does not
> contain an address but an address space identifier. This is true e.g. for
> s390 and as far as I know also for sparc.
> On s390 we have complete distinct address spaces for kernel and user space
> that each start at address zero.
>

The patch that enforce USER_DS is disabled on s390 anyway. I guess, we
can just do a set_fs(KERNEL_DS) for the others.
Christian Borntraeger March 24, 2017, 3:24 p.m. UTC | #3
On 03/24/2017 04:17 PM, Thomas Garnier wrote:
> On Fri, Mar 24, 2017 at 1:14 AM, Heiko Carstens
> <heiko.carstens@de.ibm.com> wrote:
>> On Thu, Mar 23, 2017 at 01:34:19PM -0700, Kees Cook wrote:
>>> This adds CORRUPT_USER_DS to check that the get_fs() test on syscall return
>>> still sees USER_DS during the new VERIFY_PRE_USERMODE_STATE checks.
>>>
>>> Signed-off-by: Kees Cook <keescook@chromium.org>
>>
>> ...
>>
>>> +void lkdtm_CORRUPT_USER_DS(void)
>>> +{
>>> +     /*
>>> +      * Test that USER_DS has been set correctly on exiting a syscall.
>>> +      * Since setting this higher than USER_DS (TASK_SIZE) would introduce
>>> +      * an exploitable condition, we lower it instead, since that should
>>> +      * not create as large a problem on an unprotected system.
>>> +      */
>>> +     mm_segment_t lowfs;
>>> +#ifdef MAKE_MM_SEG
>>> +     lowfs = MAKE_MM_SEG(TASK_SIZE - PAGE_SIZE);
>>> +#else
>>> +     lowfs = TASK_SIZE - PAGE_SIZE;
>>> +#endif
>>> +
>>> +     pr_info("setting bad task size limit\n");
>>> +     set_fs(lowfs);
>>> +}
>>
>> This won't work on architectures where the set_fs() argument does not
>> contain an address but an address space identifier. This is true e.g. for
>> s390 and as far as I know also for sparc.
>> On s390 we have complete distinct address spaces for kernel and user space
>> that each start at address zero.
>>
> 
> The patch that enforce USER_DS is disabled on s390 anyway. I guess, we
> can just do a set_fs(KERNEL_DS) for the others.

that would enable the test, but it would also mean that lkdtm can be used by
a program to escalate its rights. I think that is the reason why Kees did this
lowfs things.
Thomas Garnier March 24, 2017, 4:11 p.m. UTC | #4
On Fri, Mar 24, 2017 at 8:24 AM, Christian Borntraeger
<borntraeger@de.ibm.com> wrote:
> On 03/24/2017 04:17 PM, Thomas Garnier wrote:
>> On Fri, Mar 24, 2017 at 1:14 AM, Heiko Carstens
>> <heiko.carstens@de.ibm.com> wrote:
>>> On Thu, Mar 23, 2017 at 01:34:19PM -0700, Kees Cook wrote:
>>>> This adds CORRUPT_USER_DS to check that the get_fs() test on syscall return
>>>> still sees USER_DS during the new VERIFY_PRE_USERMODE_STATE checks.
>>>>
>>>> Signed-off-by: Kees Cook <keescook@chromium.org>
>>>
>>> ...
>>>
>>>> +void lkdtm_CORRUPT_USER_DS(void)
>>>> +{
>>>> +     /*
>>>> +      * Test that USER_DS has been set correctly on exiting a syscall.
>>>> +      * Since setting this higher than USER_DS (TASK_SIZE) would introduce
>>>> +      * an exploitable condition, we lower it instead, since that should
>>>> +      * not create as large a problem on an unprotected system.
>>>> +      */
>>>> +     mm_segment_t lowfs;
>>>> +#ifdef MAKE_MM_SEG
>>>> +     lowfs = MAKE_MM_SEG(TASK_SIZE - PAGE_SIZE);
>>>> +#else
>>>> +     lowfs = TASK_SIZE - PAGE_SIZE;
>>>> +#endif
>>>> +
>>>> +     pr_info("setting bad task size limit\n");
>>>> +     set_fs(lowfs);
>>>> +}
>>>
>>> This won't work on architectures where the set_fs() argument does not
>>> contain an address but an address space identifier. This is true e.g. for
>>> s390 and as far as I know also for sparc.
>>> On s390 we have complete distinct address spaces for kernel and user space
>>> that each start at address zero.
>>>
>>
>> The patch that enforce USER_DS is disabled on s390 anyway. I guess, we
>> can just do a set_fs(KERNEL_DS) for the others.
>
> that would enable the test, but it would also mean that lkdtm can be used by
> a program to escalate its rights. I think that is the reason why Kees did this
> lowfs things.
>

I see, I need to look at how lkdtm works exactly.
Kees Cook March 24, 2017, 5:46 p.m. UTC | #5
On Fri, Mar 24, 2017 at 8:24 AM, Christian Borntraeger
<borntraeger@de.ibm.com> wrote:
> On 03/24/2017 04:17 PM, Thomas Garnier wrote:
>> On Fri, Mar 24, 2017 at 1:14 AM, Heiko Carstens
>> <heiko.carstens@de.ibm.com> wrote:
>>> On Thu, Mar 23, 2017 at 01:34:19PM -0700, Kees Cook wrote:
>>>> This adds CORRUPT_USER_DS to check that the get_fs() test on syscall return
>>>> still sees USER_DS during the new VERIFY_PRE_USERMODE_STATE checks.
>>>>
>>>> Signed-off-by: Kees Cook <keescook@chromium.org>
>>>
>>> ...
>>>
>>>> +void lkdtm_CORRUPT_USER_DS(void)
>>>> +{
>>>> +     /*
>>>> +      * Test that USER_DS has been set correctly on exiting a syscall.
>>>> +      * Since setting this higher than USER_DS (TASK_SIZE) would introduce
>>>> +      * an exploitable condition, we lower it instead, since that should
>>>> +      * not create as large a problem on an unprotected system.
>>>> +      */
>>>> +     mm_segment_t lowfs;
>>>> +#ifdef MAKE_MM_SEG
>>>> +     lowfs = MAKE_MM_SEG(TASK_SIZE - PAGE_SIZE);
>>>> +#else
>>>> +     lowfs = TASK_SIZE - PAGE_SIZE;
>>>> +#endif
>>>> +
>>>> +     pr_info("setting bad task size limit\n");
>>>> +     set_fs(lowfs);
>>>> +}
>>>
>>> This won't work on architectures where the set_fs() argument does not
>>> contain an address but an address space identifier. This is true e.g. for
>>> s390 and as far as I know also for sparc.
>>> On s390 we have complete distinct address spaces for kernel and user space
>>> that each start at address zero.
>>>
>>
>> The patch that enforce USER_DS is disabled on s390 anyway. I guess, we
>> can just do a set_fs(KERNEL_DS) for the others.
>
> that would enable the test, but it would also mean that lkdtm can be used by
> a program to escalate its rights. I think that is the reason why Kees did this
> lowfs things.

Yeah, but it seems like getting this right for all architectures isn't
sane. I'm going to change it to use KERNEL_DS but also post a SIGKILL
to the process. That way it'll still trigger the syscall return
checking, but will be unable to continue running with a potentially
uncaught KERNEL_DS.

-Kees
diff mbox

Patch

diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h
index 67d27be60405..3b4976396ec4 100644
--- a/drivers/misc/lkdtm.h
+++ b/drivers/misc/lkdtm.h
@@ -27,6 +27,7 @@  void lkdtm_REFCOUNT_ZERO_SUB(void);
 void lkdtm_REFCOUNT_ZERO_ADD(void);
 void lkdtm_CORRUPT_LIST_ADD(void);
 void lkdtm_CORRUPT_LIST_DEL(void);
+void lkdtm_CORRUPT_USER_DS(void);
 
 /* lkdtm_heap.c */
 void lkdtm_OVERWRITE_ALLOCATION(void);
diff --git a/drivers/misc/lkdtm_bugs.c b/drivers/misc/lkdtm_bugs.c
index e3f4cd8876b5..4906e53a6df3 100644
--- a/drivers/misc/lkdtm_bugs.c
+++ b/drivers/misc/lkdtm_bugs.c
@@ -8,6 +8,7 @@ 
 #include <linux/list.h>
 #include <linux/refcount.h>
 #include <linux/sched.h>
+#include <linux/uaccess.h>
 
 struct lkdtm_list {
 	struct list_head node;
@@ -279,3 +280,22 @@  void lkdtm_CORRUPT_LIST_DEL(void)
 	else
 		pr_err("list_del() corruption not detected!\n");
 }
+
+void lkdtm_CORRUPT_USER_DS(void)
+{
+	/*
+	 * Test that USER_DS has been set correctly on exiting a syscall.
+	 * Since setting this higher than USER_DS (TASK_SIZE) would introduce
+	 * an exploitable condition, we lower it instead, since that should
+	 * not create as large a problem on an unprotected system.
+	 */
+	mm_segment_t lowfs;
+#ifdef MAKE_MM_SEG
+	lowfs = MAKE_MM_SEG(TASK_SIZE - PAGE_SIZE);
+#else
+	lowfs = TASK_SIZE - PAGE_SIZE;
+#endif
+
+	pr_info("setting bad task size limit\n");
+	set_fs(lowfs);
+}
diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c
index b9a4cd4a9b68..42d2b8e31e6b 100644
--- a/drivers/misc/lkdtm_core.c
+++ b/drivers/misc/lkdtm_core.c
@@ -199,6 +199,7 @@  struct crashtype crashtypes[] = {
 	CRASHTYPE(OVERFLOW),
 	CRASHTYPE(CORRUPT_LIST_ADD),
 	CRASHTYPE(CORRUPT_LIST_DEL),
+	CRASHTYPE(CORRUPT_USER_DS),
 	CRASHTYPE(CORRUPT_STACK),
 	CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE),
 	CRASHTYPE(OVERWRITE_ALLOCATION),