All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kees Cook <keescook@chromium.org>
To: laniel_francis@privacyrequired.com
Cc: linux-hardening@vger.kernel.org, Daniel Axtens <dja@axtens.net>
Subject: Re: [RFC][PATCH v2] Fortify string function strscpy.
Date: Fri, 16 Oct 2020 16:16:36 -0700	[thread overview]
Message-ID: <202010161555.A73EEE9@keescook> (raw)
In-Reply-To: <20201016123808.10007-1-laniel_francis@privacyrequired.com>

On Fri, Oct 16, 2020 at 02:38:09PM +0200, laniel_francis@privacyrequired.com wrote:
> From: Francis Laniel <laniel_francis@privacyrequired.com>
> 
> Thanks to kees advices (see:
> https://github.com/KSPP/linux/issues/96#issuecomment-709620337) I wrote a LKDTM
> test for the fortified version of strscpy I added in the v1 of this patch.
> The test panics due to write overflow.

Ah nice, thanks! I am reminded about this series as well:
https://lore.kernel.org/lkml/20200120045424.16147-1-dja@axtens.net
I think we can likely do this all at the same time, merge the
complementary pieces, etc.

Notes below...

> 
> Signed-off-by: Francis Laniel <laniel_francis@privacyrequired.com>
> ---
>  drivers/misc/lkdtm/Makefile  |  1 +
>  drivers/misc/lkdtm/core.c    |  1 +
>  drivers/misc/lkdtm/fortify.c | 37 +++++++++++++++++++++++++++++
>  drivers/misc/lkdtm/lkdtm.h   | 17 ++++++++------

Yay tests! These should, however, be a separate patch.

>  include/linux/string.h       | 45 ++++++++++++++++++++++++++++++++++++
>  5 files changed, 94 insertions(+), 7 deletions(-)
>  create mode 100644 drivers/misc/lkdtm/fortify.c
> 
> diff --git a/drivers/misc/lkdtm/Makefile b/drivers/misc/lkdtm/Makefile
> index c70b3822013f..d898f7b22045 100644
> --- a/drivers/misc/lkdtm/Makefile
> +++ b/drivers/misc/lkdtm/Makefile
> @@ -10,6 +10,7 @@ lkdtm-$(CONFIG_LKDTM)		+= rodata_objcopy.o
>  lkdtm-$(CONFIG_LKDTM)		+= usercopy.o
>  lkdtm-$(CONFIG_LKDTM)		+= stackleak.o
>  lkdtm-$(CONFIG_LKDTM)		+= cfi.o
> +lkdtm-$(CONFIG_LKDTM)		+= fortify.o
>  
>  KASAN_SANITIZE_stackleak.o	:= n
>  KCOV_INSTRUMENT_rodata.o	:= n
> diff --git a/drivers/misc/lkdtm/core.c b/drivers/misc/lkdtm/core.c
> index a5e344df9166..979f9e3feefd 100644
> --- a/drivers/misc/lkdtm/core.c
> +++ b/drivers/misc/lkdtm/core.c
> @@ -178,6 +178,7 @@ static const struct crashtype crashtypes[] = {
>  #ifdef CONFIG_X86_32
>  	CRASHTYPE(DOUBLE_FAULT),
>  #endif
> +	CRASHTYPE(FORTIFIED_STRSCPY),
>  };
>  
>  
> diff --git a/drivers/misc/lkdtm/fortify.c b/drivers/misc/lkdtm/fortify.c
> new file mode 100644
> index 000000000000..0397d2def66d
> --- /dev/null
> +++ b/drivers/misc/lkdtm/fortify.c
> @@ -0,0 +1,37 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020 Francis Laniel <laniel_francis@privacyrequired.com>
> + *
> + * Add tests related to fortified functions in this file.
> + */
> +#include <linux/string.h>
> +#include <linux/slab.h>
> +#include "lkdtm.h"
> +
> +
> +/*
> + * Calls fortified strscpy to generate a panic because there is a write
> + * overflow (i.e. src length is greater than dst length).
> + */
> +void lkdtm_FORTIFIED_STRSCPY(void)
> +{
> +#if !defined(__NO_FORTIFY) && defined(__OPTIMIZE__) && defined(CONFIG_FORTIFY_SOURCE)
> +	char *src;
> +	char dst[3];
> +
> +	src = kmalloc(7, GFP_KERNEL);
> +	src[0] = 'f';
> +	src[1] = 'o';
> +	src[2] = 'o';
> +	src[3] = 'b';
> +	src[4] = 'a';
> +	src[5] = 'r';
> +	src[6] = '\0';

Hah, yes, I guess we need to bypass the common utilities. ;) I wonder if
using __underlying_strcpy() might be easier.

> +
> +	strscpy(dst, src, 1000);
> +
> +	kfree(dst);
> +
> +	pr_info("Fail: No overflow in above strscpy call!\n");
> +#endif
> +}

One thing I'd love to see is a _compile-time_ test too: but it needs to
be a negative failure case, which Makefiles are not well suited to
dealing with. e.g. something like:

good.o: nop.c bad.c
	if $(CC) .... -o bad.o bad.c $< ; then exit 1; else $(CC) ... -o good.c nop.c ; fi

I'm not sure how to do it.

> diff --git a/drivers/misc/lkdtm/lkdtm.h b/drivers/misc/lkdtm/lkdtm.h
> index 8878538b2c13..8e5e90eb0e00 100644
> --- a/drivers/misc/lkdtm/lkdtm.h
> +++ b/drivers/misc/lkdtm/lkdtm.h
> @@ -6,7 +6,7 @@
>  
>  #include <linux/kernel.h>
>  
> -/* lkdtm_bugs.c */
> +/* bugs.c */

oops, yes. Can you split change from the others, since it's an unrelated
clean-up.

>  void __init lkdtm_bugs_init(int *recur_param);
>  void lkdtm_PANIC(void);
>  void lkdtm_BUG(void);
> @@ -34,7 +34,7 @@ void lkdtm_UNSET_SMEP(void);
>  void lkdtm_DOUBLE_FAULT(void);
>  void lkdtm_CORRUPT_PAC(void);
>  
> -/* lkdtm_heap.c */
> +/* heap.c */
>  void __init lkdtm_heap_init(void);
>  void __exit lkdtm_heap_exit(void);
>  void lkdtm_OVERWRITE_ALLOCATION(void);
> @@ -46,7 +46,7 @@ void lkdtm_SLAB_FREE_DOUBLE(void);
>  void lkdtm_SLAB_FREE_CROSS(void);
>  void lkdtm_SLAB_FREE_PAGE(void);
>  
> -/* lkdtm_perms.c */
> +/* perms.c */
>  void __init lkdtm_perms_init(void);
>  void lkdtm_WRITE_RO(void);
>  void lkdtm_WRITE_RO_AFTER_INIT(void);
> @@ -61,7 +61,7 @@ void lkdtm_EXEC_NULL(void);
>  void lkdtm_ACCESS_USERSPACE(void);
>  void lkdtm_ACCESS_NULL(void);
>  
> -/* lkdtm_refcount.c */
> +/* refcount.c */
>  void lkdtm_REFCOUNT_INC_OVERFLOW(void);
>  void lkdtm_REFCOUNT_ADD_OVERFLOW(void);
>  void lkdtm_REFCOUNT_INC_NOT_ZERO_OVERFLOW(void);
> @@ -82,10 +82,10 @@ void lkdtm_REFCOUNT_SUB_AND_TEST_SATURATED(void);
>  void lkdtm_REFCOUNT_TIMING(void);
>  void lkdtm_ATOMIC_TIMING(void);
>  
> -/* lkdtm_rodata.c */
> +/* rodata.c */
>  void lkdtm_rodata_do_nothing(void);
>  
> -/* lkdtm_usercopy.c */
> +/* usercopy.c */
>  void __init lkdtm_usercopy_init(void);
>  void __exit lkdtm_usercopy_exit(void);
>  void lkdtm_USERCOPY_HEAP_SIZE_TO(void);
> @@ -98,10 +98,13 @@ void lkdtm_USERCOPY_STACK_BEYOND(void);
>  void lkdtm_USERCOPY_KERNEL(void);
>  void lkdtm_USERCOPY_KERNEL_DS(void);
>  
> -/* lkdtm_stackleak.c */
> +/* stackleak.c */
>  void lkdtm_STACKLEAK_ERASING(void);
>  
>  /* cfi.c */
>  void lkdtm_CFI_FORWARD_PROTO(void);
>  
> +/* fortify.c */
> +void lkdtm_FORTIFIED_STRSCPY(void);
> +
>  #endif
> diff --git a/include/linux/string.h b/include/linux/string.h
> index b1f3894a0a3e..b661863619e0 100644
> --- a/include/linux/string.h
> +++ b/include/linux/string.h
> @@ -6,6 +6,8 @@
>  #include <linux/compiler.h>	/* for inline */
>  #include <linux/types.h>	/* for size_t */
>  #include <linux/stddef.h>	/* for NULL */
> +#include <linux/bug.h>		/* for WARN_ON_ONCE */
> +#include <linux/errno.h>	/* for E2BIG */
>  #include <stdarg.h>
>  #include <uapi/linux/string.h>
>  
> @@ -357,6 +359,49 @@ __FORTIFY_INLINE size_t strlcpy(char *p, const char *q, size_t size)
>  	return ret;
>  }
>  
> +/* defined after fortified strlen to reuse it */
> +extern ssize_t __real_strscpy(char *, const char *, size_t) __RENAME(strscpy);
> +__FORTIFY_INLINE ssize_t strscpy(char *p, const char *q, size_t count)

I would name "count" as "size" to match the other helpers.

> +{
> +	size_t len;
> +	size_t p_size = __builtin_object_size(p, 0);
> +	size_t q_size = __builtin_object_size(q, 0);

These can be using ", 1" instead of ", 0". And I'll grab the related
changes from the mentioned series above.

> +	/*
> +	 * If p_size and q_size cannot be known at compile time we just had to
> +	 * trust this function caller.
> +	 */
> +	if (p_size == (size_t)-1 && q_size == (size_t)-1)
> +		return __real_strscpy(p, q, count);
> +	len = strlen(q);
> +	if (count) {

This test isn't needed; it'll work itself out correctly. :P

> +		/* If count is bigger than INT_MAX, strscpy returns -E2BIG. */
> +		if (WARN_ON_ONCE(count > INT_MAX))
> +			return -E2BIG;

This is already handled in strscpy, I'd drop this here.

> +		/*
> +		 * strscpy handles read overflows by stop reading q when '\0' is
> +		 * met.
> +		 * We stick to this behavior here.
> +		 */
> +		len = (len >= count) ? count : len;
> +		/*
> +		 * If len can be known at compile time and is greater than
> +		 * p_size, generate a compile time write overflow error.
> +		 */
> +		if (__builtin_constant_p(len) && len > p_size)

This won't work (len wasn't an argument and got assigned); you need:

		if (__builtin_constant_p(size) && p_size < size)


> +			__write_overflow();
> +		/* Otherwise generate a runtime write overflow error. */
> +		if (len > p_size)
> +			fortify_panic(__func__);

I think this just needs to be:

		if (p_size < size)
			fortify_panic(__func__);


> +		/*
> +		 * Still use count as third argument to correctly compute max
> +		 * inside strscpy.
> +		 */
> +		return __real_strscpy(p, q, count);
> +	}
> +	/* If count is 0, strscpy return -E2BIG. */
> +	return -E2BIG;

I'd let __real_strscpy() handle this.

> +}
> +
>  /* defined after fortified strlen and strnlen to reuse them */
>  __FORTIFY_INLINE char *strncat(char *p, const char *q, __kernel_size_t count)
>  {
> -- 
> 2.20.1
> 

-- 
Kees Cook

  reply	other threads:[~2020-10-16 23:16 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-10-13 16:59 [RFC][PATCH v1] Fortify string function strscpy laniel_francis
2020-10-16 12:38 ` [RFC][PATCH v2] " laniel_francis
2020-10-16 23:16   ` Kees Cook [this message]
2020-10-17  9:22     ` Francis Laniel
2020-10-19 11:51       ` Daniel Axtens
2020-10-21 12:43         ` Francis Laniel
2020-10-19 23:19       ` Kees Cook
2020-10-21 14:49         ` Francis Laniel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=202010161555.A73EEE9@keescook \
    --to=keescook@chromium.org \
    --cc=dja@axtens.net \
    --cc=laniel_francis@privacyrequired.com \
    --cc=linux-hardening@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.