diff mbox

[RFC] mm: enable sanitizing via CONFIG

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

Commit Message

Kees Cook Feb. 25, 2017, 12:32 a.m. UTC
This enables page and slab poisoning by default under CONFIG_MEMORY_SANITIZE.
Based on work by Kaiwan N Billimoria.

Signed-off-by: Kees Cook <keescook@chromium.org>
---
This is more what I had in mind. This is based on the latest patch, but
handles slab and slub. I tested slub with lkdtm's READ_AFTER_FREE and
READ_BUDDY_AFTER_FREE which both trip the poisoning tests.

While doing this, I noticed one major issue with slub/slab poisoning:
it performs poisoning both on alloc and free, which is a rather large
performance hit, so the next step is likely to find a way to split the
poisoning into alloc and free halves so that CONFIG_MEMORY_SANITIZE
poisoning will only happen on the free.
---
 init/main.c      |  5 +++++
 mm/Kconfig.debug | 23 +++++++++++++++++++++++
 mm/page_poison.c |  3 ++-
 mm/slab.c        |  4 ++++
 mm/slub.c        |  7 +++++++
 5 files changed, 41 insertions(+), 1 deletion(-)

Comments

Daniel Micay Feb. 25, 2017, 8:36 a.m. UTC | #1
On Fri, 2017-02-24 at 16:32 -0800, Kees Cook wrote:
> This enables page and slab poisoning by default under
> CONFIG_MEMORY_SANITIZE.
> Based on work by Kaiwan N Billimoria.
> 
> Signed-off-by: Kees Cook <keescook@chromium.org>
> ---
> This is more what I had in mind. This is based on the latest patch,
> but
> handles slab and slub. I tested slub with lkdtm's READ_AFTER_FREE and
> READ_BUDDY_AFTER_FREE which both trip the poisoning tests.
> 
> While doing this, I noticed one major issue with slub/slab poisoning:
> it performs poisoning both on alloc and free, which is a rather large
> performance hit, so the next step is likely to find a way to split the
> poisoning into alloc and free halves so that CONFIG_MEMORY_SANITIZE
> poisoning will only happen on the free.

It also still fills with a value that will make pointers point to
userspace, right? Rather than a better value or zeroing (and relying on
mmap_min_addr, although that's pretty small and easy to index past).

It also verifies the poisoning is still there, which can be wanted but
is more expensive. It increases allocations by the size of a pointer
which PaX doesn't do anymore, and there's the whole losing the fast path
which seems to mean losing CPU caching too since it's just hitting the
global stuff. The debug infrastructure could also be a risk, that would
not be present in normal builds up to this point.

I would like to be able to use upstream features but I think case it
seems that anyone not willing to unnecessarily lose performance and
security properties will need to keep carrying ~15 line of code out of
tree or using the PaX features.

Is there a path to doing it properly from here? I really don't see the
point in it being upstreaming if it's such a hack. It's frustrating...

Not to mention that real allocator hardening like not having inline free
lists and being able to reliably detect double-free would be nice. Maybe
there should just be a security-oriented slab allocator, freeing it from
needing to avoid stepping on other people's toes. It just needs to pick
a performance target and then the design can shift away from the current
one within that target over time. *shrug*
Kees Cook March 1, 2017, 7:16 p.m. UTC | #2
On Sat, Feb 25, 2017 at 12:36 AM, Daniel Micay <danielmicay@gmail.com> wrote:
> On Fri, 2017-02-24 at 16:32 -0800, Kees Cook wrote:
>> This enables page and slab poisoning by default under
>> CONFIG_MEMORY_SANITIZE.
>> Based on work by Kaiwan N Billimoria.
>>
>> Signed-off-by: Kees Cook <keescook@chromium.org>
>> ---
>> This is more what I had in mind. This is based on the latest patch,
>> but
>> handles slab and slub. I tested slub with lkdtm's READ_AFTER_FREE and
>> READ_BUDDY_AFTER_FREE which both trip the poisoning tests.
>>
>> While doing this, I noticed one major issue with slub/slab poisoning:
>> it performs poisoning both on alloc and free, which is a rather large
>> performance hit, so the next step is likely to find a way to split the
>> poisoning into alloc and free halves so that CONFIG_MEMORY_SANITIZE
>> poisoning will only happen on the free.
>
> It also still fills with a value that will make pointers point to
> userspace, right? Rather than a better value or zeroing (and relying on
> mmap_min_addr, although that's pretty small and easy to index past).

Correct. We have to get there in pieces, though I continue to think it
would still be easier to make the sanitization feature separate from
debugging.

In PaX, the poison value on x86_64 is 0xfe (to make addresses aim into
the noncanonical range), otherwise 0xff (to point at the top of
memory).

> It also verifies the poisoning is still there, which can be wanted but
> is more expensive. It increases allocations by the size of a pointer
> which PaX doesn't do anymore, and there's the whole losing the fast path
> which seems to mean losing CPU caching too since it's just hitting the
> global stuff. The debug infrastructure could also be a risk, that would
> not be present in normal builds up to this point.

Gathering the reasons against this being part of the debug path would
be a nice thing to collect; it might be convincing to the allocator
maintainers.

> I would like to be able to use upstream features but I think case it
> seems that anyone not willing to unnecessarily lose performance and
> security properties will need to keep carrying ~15 line of code out of
> tree or using the PaX features.

The simple form (without the SLAB_NO_SANITIZE) is alarmingly small. :)

> Is there a path to doing it properly from here? I really don't see the
> point in it being upstreaming if it's such a hack. It's frustrating...
>
> Not to mention that real allocator hardening like not having inline free
> lists and being able to reliably detect double-free would be nice. Maybe
> there should just be a security-oriented slab allocator, freeing it from
> needing to avoid stepping on other people's toes. It just needs to pick
> a performance target and then the design can shift away from the current
> one within that target over time. *shrug*

I'd love to see someone step up and create this for upstream. I think
it'd make a lot of sense instead of trying to shoe-horn things into
SLUB...

-Kees
Kaiwan N Billimoria March 27, 2017, 10:54 a.m. UTC | #3
Hi Kees,

> I'd love to see someone step up and create this for upstream. I think
> it'd make a lot of sense instead of trying to shoe-horn things into
> SLUB...
>
Ok, am unsure if I clearly understand all the issues involved; but of
course it's always better to make a start and then evolve. So, how
exactly can this be tackled? Do we go down the "new SLUB for security"
path? And, if yes, then how exactly does one get started? I'll need
some pointers pl...

Regards,
Kaiwan.

On Thu, Mar 2, 2017 at 12:46 AM, Kees Cook <keescook@chromium.org> wrote:
> On Sat, Feb 25, 2017 at 12:36 AM, Daniel Micay <danielmicay@gmail.com> wrote:
>> On Fri, 2017-02-24 at 16:32 -0800, Kees Cook wrote:
>>> This enables page and slab poisoning by default under
>>> CONFIG_MEMORY_SANITIZE.
>>> Based on work by Kaiwan N Billimoria.
>>>
>>> Signed-off-by: Kees Cook <keescook@chromium.org>
>>> ---
>>> This is more what I had in mind. This is based on the latest patch,
>>> but
>>> handles slab and slub. I tested slub with lkdtm's READ_AFTER_FREE and
>>> READ_BUDDY_AFTER_FREE which both trip the poisoning tests.
>>>
>>> While doing this, I noticed one major issue with slub/slab poisoning:
>>> it performs poisoning both on alloc and free, which is a rather large
>>> performance hit, so the next step is likely to find a way to split the
>>> poisoning into alloc and free halves so that CONFIG_MEMORY_SANITIZE
>>> poisoning will only happen on the free.
>>
>> It also still fills with a value that will make pointers point to
>> userspace, right? Rather than a better value or zeroing (and relying on
>> mmap_min_addr, although that's pretty small and easy to index past).
>
> Correct. We have to get there in pieces, though I continue to think it
> would still be easier to make the sanitization feature separate from
> debugging.
>
> In PaX, the poison value on x86_64 is 0xfe (to make addresses aim into
> the noncanonical range), otherwise 0xff (to point at the top of
> memory).
>
>> It also verifies the poisoning is still there, which can be wanted but
>> is more expensive. It increases allocations by the size of a pointer
>> which PaX doesn't do anymore, and there's the whole losing the fast path
>> which seems to mean losing CPU caching too since it's just hitting the
>> global stuff. The debug infrastructure could also be a risk, that would
>> not be present in normal builds up to this point.
>
> Gathering the reasons against this being part of the debug path would
> be a nice thing to collect; it might be convincing to the allocator
> maintainers.
>
>> I would like to be able to use upstream features but I think case it
>> seems that anyone not willing to unnecessarily lose performance and
>> security properties will need to keep carrying ~15 line of code out of
>> tree or using the PaX features.
>
> The simple form (without the SLAB_NO_SANITIZE) is alarmingly small. :)
>
>> Is there a path to doing it properly from here? I really don't see the
>> point in it being upstreaming if it's such a hack. It's frustrating...
>>
>> Not to mention that real allocator hardening like not having inline free
>> lists and being able to reliably detect double-free would be nice. Maybe
>> there should just be a security-oriented slab allocator, freeing it from
>> needing to avoid stepping on other people's toes. It just needs to pick
>> a performance target and then the design can shift away from the current
>> one within that target over time. *shrug*
>
> I'd love to see someone step up and create this for upstream. I think
> it'd make a lot of sense instead of trying to shoe-horn things into
> SLUB...
>
> -Kees
>
> --
> Kees Cook
> Pixel Security
>
Kees Cook April 20, 2017, 8:58 p.m. UTC | #4
On Mon, Mar 27, 2017 at 3:54 AM, Kaiwan N Billimoria
<kaiwan@kaiwantech.com> wrote:
> On Thu, Mar 2, 2017 at 12:46 AM, Kees Cook <keescook@chromium.org> wrote:
>> I'd love to see someone step up and create this for upstream. I think
>> it'd make a lot of sense instead of trying to shoe-horn things into
>> SLUB...
>>
> Ok, am unsure if I clearly understand all the issues involved; but of
> course it's always better to make a start and then evolve. So, how
> exactly can this be tackled? Do we go down the "new SLUB for security"
> path? And, if yes, then how exactly does one get started? I'll need
> some pointers pl...

Well, mainly it would need someone dedicated to creating a whole new
slab allocator for the kernel, and prioritizing security for it.
Daniel has a bunch of ideas on this, but I don't know enough currently
to make suggestions for what the design should look like. Making
sanity-checks fast would be a driving principle, though. :)

-Kees
Kees Cook April 20, 2017, 9:02 p.m. UTC | #5
On Fri, Feb 24, 2017 at 4:32 PM, Kees Cook <keescook@chromium.org> wrote:
> This enables page and slab poisoning by default under CONFIG_MEMORY_SANITIZE.
> Based on work by Kaiwan N Billimoria.
>
> Signed-off-by: Kees Cook <keescook@chromium.org>
> ---
> This is more what I had in mind. This is based on the latest patch, but
> handles slab and slub. I tested slub with lkdtm's READ_AFTER_FREE and
> READ_BUDDY_AFTER_FREE which both trip the poisoning tests.
>
> While doing this, I noticed one major issue with slub/slab poisoning:
> it performs poisoning both on alloc and free, which is a rather large
> performance hit, so the next step is likely to find a way to split the
> poisoning into alloc and free halves so that CONFIG_MEMORY_SANITIZE
> poisoning will only happen on the free.

While this has performance flaws (to say the least), what do people
think of this general approach to dealing with having a single CONFIG
to control this at least? Should I (or someone) see what the slab
cache maintainers think of this?

-Kees

> ---
>  init/main.c      |  5 +++++
>  mm/Kconfig.debug | 23 +++++++++++++++++++++++
>  mm/page_poison.c |  3 ++-
>  mm/slab.c        |  4 ++++
>  mm/slub.c        |  7 +++++++
>  5 files changed, 41 insertions(+), 1 deletion(-)
>
> diff --git a/init/main.c b/init/main.c
> index 24ea48745061..e5f571bfe56f 100644
> --- a/init/main.c
> +++ b/init/main.c
> @@ -1030,6 +1030,11 @@ static noinline void __init kernel_init_freeable(void)
>
>         do_basic_setup();
>
> +#ifdef CONFIG_MEMORY_SANITIZE
> +       pr_debug("[CONFIG_MEMORY_SANITIZE]: page_poisoning_enabled? %s\n",
> +                page_poisoning_enabled() ? "yes" : "no");
> +#endif
> +
>         /* Open the /dev/console on the rootfs, this should never fail */
>         if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
>                 pr_err("Warning: unable to open an initial console.\n");
> diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug
> index afcc550877ff..910a7a359b96 100644
> --- a/mm/Kconfig.debug
> +++ b/mm/Kconfig.debug
> @@ -90,3 +90,26 @@ config DEBUG_PAGE_REF
>           careful when enabling this feature because it adds about 30 KB to the
>           kernel code.  However the runtime performance overhead is virtually
>           nil until the tracepoints are actually enabled.
> +
> +config MEMORY_SANITIZE
> +       bool "Enable memory sanitization features"
> +       depends on SLUB || SLAB
> +       select SLUB_DEBUG if SLUB
> +       select PAGE_POISONING
> +       select PAGE_POISONING_NO_SANITY
> +       help
> +         This option enables memory sanitization features. Particularly,
> +         when you turn on this option, it auto-enables:
> +         - page poisoning (without sanity checking)
> +         - kmem_cache poisoning
> +
> +         Implication: turning this option on will implicitly enable:
> +         - the SLUB_DEBUG switch to the equivalent of the kernel command-line
> +           'slub_debug=p' (where p=SLAB_POISON).
> +         - page poisoning, equivalent to passing the kernel command-line
> +           option 'page_poison=on'.
> +
> +         Of course, kernel command-line options 'page_poison' and 'slub_debug'
> +         are still honored.
> +
> +         If unsure, say N.
> diff --git a/mm/page_poison.c b/mm/page_poison.c
> index 2e647c65916b..6f7e37c8ac2f 100644
> --- a/mm/page_poison.c
> +++ b/mm/page_poison.c
> @@ -7,7 +7,8 @@
>  #include <linux/ratelimit.h>
>
>  static bool __page_poisoning_enabled __read_mostly;
> -static bool want_page_poisoning __read_mostly;
> +static bool want_page_poisoning __read_mostly =
> +                                       IS_ENABLED(CONFIG_MEMORY_SANITIZE);
>
>  static int early_page_poison_param(char *buf)
>  {
> diff --git a/mm/slab.c b/mm/slab.c
> index bd63450a9b16..1462b0b8b0a0 100644
> --- a/mm/slab.c
> +++ b/mm/slab.c
> @@ -1894,6 +1894,10 @@ unsigned long kmem_cache_flags(unsigned long object_size,
>         unsigned long flags, const char *name,
>         void (*ctor)(void *))
>  {
> +#ifdef CONFIG_MEMORY_SANITIZE
> +       flags |= SLAB_POISON;
> +#endif
> +
>         return flags;
>  }
>
> diff --git a/mm/slub.c b/mm/slub.c
> index 7f4bc7027ed5..5041f42c942b 100644
> --- a/mm/slub.c
> +++ b/mm/slub.c
> @@ -452,6 +452,8 @@ static inline void *restore_red_left(struct kmem_cache *s, void *p)
>   */
>  #if defined(CONFIG_SLUB_DEBUG_ON)
>  static int slub_debug = DEBUG_DEFAULT_FLAGS;
> +#elif defined(CONFIG_MEMORY_SANITIZE)
> +static int slub_debug = SLAB_POISON;
>  #else
>  static int slub_debug;
>  #endif
> @@ -5755,6 +5757,11 @@ static int __init slab_sysfs_init(void)
>         struct kmem_cache *s;
>         int err;
>
> +#ifdef CONFIG_MEMORY_SANITIZE
> +       pr_info("[CONFIG_MEMORY_SANITIZE]: slub_debug = P? %s [0x%x]\n",
> +               slub_debug & SLAB_POISON ? "yes" : "no", slub_debug);
> +#endif
> +
>         mutex_lock(&slab_mutex);
>
>         slab_kset = kset_create_and_add("slab", &slab_uevent_ops, kernel_kobj);
> --
> 2.7.4
>
>
> --
> Kees Cook
> Pixel Security
diff mbox

Patch

diff --git a/init/main.c b/init/main.c
index 24ea48745061..e5f571bfe56f 100644
--- a/init/main.c
+++ b/init/main.c
@@ -1030,6 +1030,11 @@  static noinline void __init kernel_init_freeable(void)
 
 	do_basic_setup();
 
+#ifdef CONFIG_MEMORY_SANITIZE
+	pr_debug("[CONFIG_MEMORY_SANITIZE]: page_poisoning_enabled? %s\n",
+		 page_poisoning_enabled() ? "yes" : "no");
+#endif
+
 	/* Open the /dev/console on the rootfs, this should never fail */
 	if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
 		pr_err("Warning: unable to open an initial console.\n");
diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug
index afcc550877ff..910a7a359b96 100644
--- a/mm/Kconfig.debug
+++ b/mm/Kconfig.debug
@@ -90,3 +90,26 @@  config DEBUG_PAGE_REF
 	  careful when enabling this feature because it adds about 30 KB to the
 	  kernel code.  However the runtime performance overhead is virtually
 	  nil until the tracepoints are actually enabled.
+
+config MEMORY_SANITIZE
+	bool "Enable memory sanitization features"
+	depends on SLUB || SLAB
+	select SLUB_DEBUG if SLUB
+	select PAGE_POISONING
+	select PAGE_POISONING_NO_SANITY
+	help
+	  This option enables memory sanitization features. Particularly,
+	  when you turn on this option, it auto-enables:
+	  - page poisoning (without sanity checking)
+	  - kmem_cache poisoning
+
+	  Implication: turning this option on will implicitly enable:
+	  - the SLUB_DEBUG switch to the equivalent of the kernel command-line
+	    'slub_debug=p' (where p=SLAB_POISON).
+	  - page poisoning, equivalent to passing the kernel command-line
+	    option 'page_poison=on'.
+
+	  Of course, kernel command-line options 'page_poison' and 'slub_debug'
+	  are still honored.
+
+	  If unsure, say N.
diff --git a/mm/page_poison.c b/mm/page_poison.c
index 2e647c65916b..6f7e37c8ac2f 100644
--- a/mm/page_poison.c
+++ b/mm/page_poison.c
@@ -7,7 +7,8 @@ 
 #include <linux/ratelimit.h>
 
 static bool __page_poisoning_enabled __read_mostly;
-static bool want_page_poisoning __read_mostly;
+static bool want_page_poisoning __read_mostly =
+					IS_ENABLED(CONFIG_MEMORY_SANITIZE);
 
 static int early_page_poison_param(char *buf)
 {
diff --git a/mm/slab.c b/mm/slab.c
index bd63450a9b16..1462b0b8b0a0 100644
--- a/mm/slab.c
+++ b/mm/slab.c
@@ -1894,6 +1894,10 @@  unsigned long kmem_cache_flags(unsigned long object_size,
 	unsigned long flags, const char *name,
 	void (*ctor)(void *))
 {
+#ifdef CONFIG_MEMORY_SANITIZE
+	flags |= SLAB_POISON;
+#endif
+
 	return flags;
 }
 
diff --git a/mm/slub.c b/mm/slub.c
index 7f4bc7027ed5..5041f42c942b 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -452,6 +452,8 @@  static inline void *restore_red_left(struct kmem_cache *s, void *p)
  */
 #if defined(CONFIG_SLUB_DEBUG_ON)
 static int slub_debug = DEBUG_DEFAULT_FLAGS;
+#elif defined(CONFIG_MEMORY_SANITIZE)
+static int slub_debug = SLAB_POISON;
 #else
 static int slub_debug;
 #endif
@@ -5755,6 +5757,11 @@  static int __init slab_sysfs_init(void)
 	struct kmem_cache *s;
 	int err;
 
+#ifdef CONFIG_MEMORY_SANITIZE
+	pr_info("[CONFIG_MEMORY_SANITIZE]: slub_debug = P? %s [0x%x]\n",
+		slub_debug & SLAB_POISON ? "yes" : "no", slub_debug);
+#endif
+
 	mutex_lock(&slab_mutex);
 
 	slab_kset = kset_create_and_add("slab", &slab_uevent_ops, kernel_kobj);