diff mbox series

[6/9] fortify: Split reporting and avoid passing string pointer

Message ID 20230406000212.3442647-6-keescook@chromium.org (mailing list archive)
State Superseded
Headers show
Series fortify: Add KUnit tests for runtime overflows | expand

Commit Message

Kees Cook April 6, 2023, 12:02 a.m. UTC
In preparation for KUnit testing and further improvements in fortify
failure reporting, split out the report and encode the function and
access failure (read or write overflow) into a single int argument. This
mainly ends up saving some space in the data segment. For a defconfig
with FORTIFY_SOURCE enabled:

$ size gcc/vmlinux.before gcc/vmlinux.after
   text  	  data     bss     dec    	    hex filename
26132309        9760658 2195460 38088427        2452eeb gcc/vmlinux.before
26132386        9748382 2195460 38076228        244ff44 gcc/vmlinux.after

Cc: Andy Shevchenko <andy@kernel.org>
Cc: Cezary Rojewski <cezary.rojewski@intel.com>
Cc: Puyou Lu <puyou.lu@gmail.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: linux-hardening@vger.kernel.org
Signed-off-by: Kees Cook <keescook@chromium.org>
---
 include/linux/fortify-string.h | 72 +++++++++++++++++++++++-----------
 lib/string_helpers.c           | 70 +++++++++++++++++++++++++++++++--
 tools/objtool/check.c          |  2 +-
 3 files changed, 118 insertions(+), 26 deletions(-)

Comments

Andy Shevchenko April 6, 2023, 10:20 a.m. UTC | #1
On Thu, Apr 6, 2023 at 3:02 AM Kees Cook <keescook@chromium.org> wrote:
>
> In preparation for KUnit testing and further improvements in fortify
> failure reporting, split out the report and encode the function and
> access failure (read or write overflow) into a single int argument. This
> mainly ends up saving some space in the data segment. For a defconfig
> with FORTIFY_SOURCE enabled:
>
> $ size gcc/vmlinux.before gcc/vmlinux.after
>    text           data     bss     dec              hex filename
> 26132309        9760658 2195460 38088427        2452eeb gcc/vmlinux.before
> 26132386        9748382 2195460 38076228        244ff44 gcc/vmlinux.after

...

> +       const char *name;
> +       const bool write = !!(reason & 0x1);

Perhaps define that as

FORTIFY_READ_WRITE  BIT(0)
FORTIFY_FUNC_SHIFT  1

const bool write = reason & FORTIFY_READ_WRITE; // and note no need for !! part

switch (reason >> FORTIFY_FUNC_SHIFT) {

> +       switch (reason >> 1) {
> +       case FORTIFY_FUNC_strncpy:
> +               name = "strncpy";
> +               break;
> +       case FORTIFY_FUNC_strnlen:
> +               name = "strnlen";
> +               break;
> +       case FORTIFY_FUNC_strlen:
> +               name = "strlen";
> +               break;
> +       case FORTIFY_FUNC_strlcpy:
> +               name = "strlcpy";
> +               break;
> +       case FORTIFY_FUNC_strscpy:
> +               name = "strscpy";
> +               break;
> +       case FORTIFY_FUNC_strlcat:
> +               name = "strlcat";
> +               break;
> +       case FORTIFY_FUNC_strcat:
> +               name = "strcat";
> +               break;
> +       case FORTIFY_FUNC_strncat:
> +               name = "strncat";
> +               break;
> +       case FORTIFY_FUNC_memset:
> +               name = "memset";
> +               break;
> +       case FORTIFY_FUNC_memcpy:
> +               name = "memcpy";
> +               break;
> +       case FORTIFY_FUNC_memmove:
> +               name = "memmove";
> +               break;
> +       case FORTIFY_FUNC_memscan:
> +               name = "memscan";
> +               break;
> +       case FORTIFY_FUNC_memcmp:
> +               name = "memcmp";
> +               break;
> +       case FORTIFY_FUNC_memchr:
> +               name = "memchr";
> +               break;
> +       case FORTIFY_FUNC_memchr_inv:
> +               name = "memchr_inv";
> +               break;
> +       case FORTIFY_FUNC_kmemdup:
> +               name = "kmemdup";
> +               break;
> +       case FORTIFY_FUNC_strcpy:
> +               name = "strcpy";
> +               break;
> +       default:
> +               name = "unknown";
> +       }

...

> +       WARN(1, "%s: detected buffer %s overflow\n", name, write ? "write" : "read");

Using str_read_write() ?

Dunno if it's already there or needs to be added. I have some patches
to move those str_*() to string_choices.h. We can also prepend yours
with those.
kernel test robot April 6, 2023, 11:19 a.m. UTC | #2
Hi Kees,

kernel test robot noticed the following build errors:

[auto build test ERROR on kees/for-next/hardening]
[also build test ERROR on kees/for-next/pstore kees/for-next/kspp akpm-mm/mm-everything linus/master v6.3-rc5 next-20230406]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Kees-Cook/kunit-tool-Enable-CONFIG_FORTIFY_SOURCE-under-UML/20230406-081014
base:   https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/hardening
patch link:    https://lore.kernel.org/r/20230406000212.3442647-6-keescook%40chromium.org
patch subject: [PATCH 6/9] fortify: Split reporting and avoid passing string pointer
config: arm-randconfig-r025-20230403 (https://download.01.org/0day-ci/archive/20230406/202304061930.4Au0PASm-lkp@intel.com/config)
compiler: clang version 17.0.0 (https://github.com/llvm/llvm-project 67409911353323ca5edf2049ef0df54132fa1ca7)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install arm cross compiling tool for clang build
        # apt-get install binutils-arm-linux-gnueabi
        # https://github.com/intel-lab-lkp/linux/commit/4fd520e6ee549e1ffe8859e26e57ea64b48e78ea
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Kees-Cook/kunit-tool-Enable-CONFIG_FORTIFY_SOURCE-under-UML/20230406-081014
        git checkout 4fd520e6ee549e1ffe8859e26e57ea64b48e78ea
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=arm olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=arm SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>
| Link: https://lore.kernel.org/oe-kbuild-all/202304061930.4Au0PASm-lkp@intel.com/

All errors (new ones prefixed by >>):

>> ld.lld: error: undefined symbol: __fortify_panic
   >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
   >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_count)
   >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
   >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_search)
   >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
   >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_get)
Miguel Ojeda April 6, 2023, 1:44 p.m. UTC | #3
On Thu, Apr 6, 2023 at 2:02 AM Kees Cook <keescook@chromium.org> wrote:
>
> +void __fortify_report(u8 reason);
> +void __fortify_panic(u8 reason) __cold __noreturn;

(snip)

> +void __fortify_report(u8 reason)

(snip)

> +void __fortify_panic(const u8 reason)

I am curious: for some reason (no pun intended :) the `reason`s above
are not `const` except this one, but then in a later patch they become
`const` (including the declarations).

So perhaps make everything `const` when they are introduced? Or is
there some other reason? (e.g. I saw one patch that moved a function,
so there it seemed to make sense to keep things as they are to make
the copy 1:1).

Cheers,
Miguel
Alexander Lobakin April 6, 2023, 3:23 p.m. UTC | #4
From: Kees Cook <keescook@chromium.org>
Date: Wed,  5 Apr 2023 17:02:05 -0700

> In preparation for KUnit testing and further improvements in fortify
> failure reporting, split out the report and encode the function and
> access failure (read or write overflow) into a single int argument. This
> mainly ends up saving some space in the data segment. For a defconfig
> with FORTIFY_SOURCE enabled:
> 
> $ size gcc/vmlinux.before gcc/vmlinux.after
>    text  	  data     bss     dec    	    hex filename
> 26132309        9760658 2195460 38088427        2452eeb gcc/vmlinux.before
> 26132386        9748382 2195460 38076228        244ff44 gcc/vmlinux.after
> 
> Cc: Andy Shevchenko <andy@kernel.org>
> Cc: Cezary Rojewski <cezary.rojewski@intel.com>
> Cc: Puyou Lu <puyou.lu@gmail.com>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: linux-hardening@vger.kernel.org
> Signed-off-by: Kees Cook <keescook@chromium.org>
> ---
>  include/linux/fortify-string.h | 72 +++++++++++++++++++++++-----------
>  lib/string_helpers.c           | 70 +++++++++++++++++++++++++++++++--
>  tools/objtool/check.c          |  2 +-
>  3 files changed, 118 insertions(+), 26 deletions(-)
> 
> diff --git a/include/linux/fortify-string.h b/include/linux/fortify-string.h
> index 41dbd641f55c..6db4052db459 100644
> --- a/include/linux/fortify-string.h
> +++ b/include/linux/fortify-string.h
> @@ -9,7 +9,34 @@
>  #define __FORTIFY_INLINE extern __always_inline __gnu_inline __overloadable
>  #define __RENAME(x) __asm__(#x)
>  
> -void fortify_panic(const char *name) __noreturn __cold;
> +#define fortify_reason(func, write)	(((func) << 1) | !!(write))
> +
> +#define fortify_panic(func, write)	\
> +	__fortify_panic(fortify_reason(func, write))
> +
> +#define FORTIFY_READ		 0
> +#define FORTIFY_WRITE		 1
> +
> +#define FORTIFY_FUNC_strncpy	 0
> +#define FORTIFY_FUNC_strnlen	 1
> +#define FORTIFY_FUNC_strlen	 2
> +#define FORTIFY_FUNC_strlcpy	 3
> +#define FORTIFY_FUNC_strscpy	 4
> +#define FORTIFY_FUNC_strlcat	 5
> +#define FORTIFY_FUNC_strcat	 6
> +#define FORTIFY_FUNC_strncat	 7
> +#define FORTIFY_FUNC_memset	 8
> +#define FORTIFY_FUNC_memcpy	 9
> +#define FORTIFY_FUNC_memmove	10
> +#define FORTIFY_FUNC_memscan	11
> +#define FORTIFY_FUNC_memcmp	12
> +#define FORTIFY_FUNC_memchr	13
> +#define FORTIFY_FUNC_memchr_inv	14
> +#define FORTIFY_FUNC_kmemdup	15
> +#define FORTIFY_FUNC_strcpy	16

enum?

> --- a/lib/string_helpers.c
> +++ b/lib/string_helpers.c
> @@ -1021,10 +1021,74 @@ EXPORT_SYMBOL(__read_overflow2_field);
>  void __write_overflow_field(size_t avail, size_t wanted) { }
>  EXPORT_SYMBOL(__write_overflow_field);
>  
> -void fortify_panic(const char *name)
> +void __fortify_report(u8 reason)
>  {
> -	pr_emerg("detected buffer overflow in %s\n", name);
> +	const char *name;
> +	const bool write = !!(reason & 0x1);
> +
> +	switch (reason >> 1) {

As already mentioned, I'd use bitfield helpers + couple definitions to
not miss something when changing the way it's encoded

#define FORTIFY_REASON_DIR(r)	FIELD_GET(BIT(0), r)
#define FORTIFY_REASON_FUNC(r)	FIELD_GET(GENMASK(7, 1), r)

(+ set pair)

> +	case FORTIFY_FUNC_strncpy:
> +		name = "strncpy";
> +		break;
> +	case FORTIFY_FUNC_strnlen:
> +		name = "strnlen";
> +		break;
> +	case FORTIFY_FUNC_strlen:
> +		name = "strlen";
> +		break;
> +	case FORTIFY_FUNC_strlcpy:
> +		name = "strlcpy";
> +		break;
> +	case FORTIFY_FUNC_strscpy:
> +		name = "strscpy";
> +		break;
> +	case FORTIFY_FUNC_strlcat:
> +		name = "strlcat";
> +		break;
> +	case FORTIFY_FUNC_strcat:
> +		name = "strcat";
> +		break;
> +	case FORTIFY_FUNC_strncat:
> +		name = "strncat";
> +		break;
> +	case FORTIFY_FUNC_memset:
> +		name = "memset";
> +		break;
> +	case FORTIFY_FUNC_memcpy:
> +		name = "memcpy";
> +		break;
> +	case FORTIFY_FUNC_memmove:
> +		name = "memmove";
> +		break;
> +	case FORTIFY_FUNC_memscan:
> +		name = "memscan";
> +		break;
> +	case FORTIFY_FUNC_memcmp:
> +		name = "memcmp";
> +		break;
> +	case FORTIFY_FUNC_memchr:
> +		name = "memchr";
> +		break;
> +	case FORTIFY_FUNC_memchr_inv:
> +		name = "memchr_inv";
> +		break;
> +	case FORTIFY_FUNC_kmemdup:
> +		name = "kmemdup";
> +		break;
> +	case FORTIFY_FUNC_strcpy:
> +		name = "strcpy";
> +		break;
> +	default:
> +		name = "unknown";
> +	}

I know this is far from hotpath, but could we save some object code and
do that via O(1) array lookup? Plus some macro to compress things:

#define FORTIFY_ENTRY(name)		\
	[FORTIFY_FUNC_##name]	= #name

static const char * const fortify_funcs[] = {
	FORTIFY_ENTRY(strncpy),
	...
}

	// array bounds check here if you wish, I wouldn't bother as
	// we're panicking anyway

	name = fortify_funcs[reason >> 1];

> +	WARN(1, "%s: detected buffer %s overflow\n", name, write ? "write" : "read");
> +}
> +EXPORT_SYMBOL(__fortify_report);
> +
> +void __fortify_panic(const u8 reason)
> +{
> +	__fortify_report(reason);
>  	BUG();
>  }
> -EXPORT_SYMBOL(fortify_panic);
> +EXPORT_SYMBOL(__fortify_panic);
>  #endif /* CONFIG_FORTIFY_SOURCE */
Thanks,
Olek
Kees Cook April 6, 2023, 10:54 p.m. UTC | #5
On Thu, Apr 06, 2023 at 05:23:54PM +0200, Alexander Lobakin wrote:
> From: Kees Cook <keescook@chromium.org>
> Date: Wed,  5 Apr 2023 17:02:05 -0700
> 
> > In preparation for KUnit testing and further improvements in fortify
> > failure reporting, split out the report and encode the function and
> > access failure (read or write overflow) into a single int argument. This
> > mainly ends up saving some space in the data segment. For a defconfig
> > with FORTIFY_SOURCE enabled:
> > 
> > $ size gcc/vmlinux.before gcc/vmlinux.after
> >    text  	  data     bss     dec    	    hex filename
> > 26132309        9760658 2195460 38088427        2452eeb gcc/vmlinux.before
> > 26132386        9748382 2195460 38076228        244ff44 gcc/vmlinux.after
> > 
> > Cc: Andy Shevchenko <andy@kernel.org>
> > Cc: Cezary Rojewski <cezary.rojewski@intel.com>
> > Cc: Puyou Lu <puyou.lu@gmail.com>
> > Cc: Mark Brown <broonie@kernel.org>
> > Cc: linux-hardening@vger.kernel.org
> > Signed-off-by: Kees Cook <keescook@chromium.org>
> > ---
> >  include/linux/fortify-string.h | 72 +++++++++++++++++++++++-----------
> >  lib/string_helpers.c           | 70 +++++++++++++++++++++++++++++++--
> >  tools/objtool/check.c          |  2 +-
> >  3 files changed, 118 insertions(+), 26 deletions(-)
> > 
> > diff --git a/include/linux/fortify-string.h b/include/linux/fortify-string.h
> > index 41dbd641f55c..6db4052db459 100644
> > --- a/include/linux/fortify-string.h
> > +++ b/include/linux/fortify-string.h
> > @@ -9,7 +9,34 @@
> >  #define __FORTIFY_INLINE extern __always_inline __gnu_inline __overloadable
> >  #define __RENAME(x) __asm__(#x)
> >  
> > -void fortify_panic(const char *name) __noreturn __cold;
> > +#define fortify_reason(func, write)	(((func) << 1) | !!(write))
> > +
> > +#define fortify_panic(func, write)	\
> > +	__fortify_panic(fortify_reason(func, write))
> > +
> > +#define FORTIFY_READ		 0
> > +#define FORTIFY_WRITE		 1
> > +
> > +#define FORTIFY_FUNC_strncpy	 0
> > +#define FORTIFY_FUNC_strnlen	 1
> > +#define FORTIFY_FUNC_strlen	 2
> > +#define FORTIFY_FUNC_strlcpy	 3
> > +#define FORTIFY_FUNC_strscpy	 4
> > +#define FORTIFY_FUNC_strlcat	 5
> > +#define FORTIFY_FUNC_strcat	 6
> > +#define FORTIFY_FUNC_strncat	 7
> > +#define FORTIFY_FUNC_memset	 8
> > +#define FORTIFY_FUNC_memcpy	 9
> > +#define FORTIFY_FUNC_memmove	10
> > +#define FORTIFY_FUNC_memscan	11
> > +#define FORTIFY_FUNC_memcmp	12
> > +#define FORTIFY_FUNC_memchr	13
> > +#define FORTIFY_FUNC_memchr_inv	14
> > +#define FORTIFY_FUNC_kmemdup	15
> > +#define FORTIFY_FUNC_strcpy	16
> 
> enum?

I opted to avoid an enum due to the binary operations used in
"fortify_reason" to collapse it to a u8. It just seemed like more work
to put in enums, and then do u8 casts, etc, all for a strictly
"internal" set of magic numbers.

> 
> > --- a/lib/string_helpers.c
> > +++ b/lib/string_helpers.c
> > @@ -1021,10 +1021,74 @@ EXPORT_SYMBOL(__read_overflow2_field);
> >  void __write_overflow_field(size_t avail, size_t wanted) { }
> >  EXPORT_SYMBOL(__write_overflow_field);
> >  
> > -void fortify_panic(const char *name)
> > +void __fortify_report(u8 reason)
> >  {
> > -	pr_emerg("detected buffer overflow in %s\n", name);
> > +	const char *name;
> > +	const bool write = !!(reason & 0x1);
> > +
> > +	switch (reason >> 1) {
> 
> As already mentioned, I'd use bitfield helpers + couple definitions to
> not miss something when changing the way it's encoded
> 
> #define FORTIFY_REASON_DIR(r)	FIELD_GET(BIT(0), r)
> #define FORTIFY_REASON_FUNC(r)	FIELD_GET(GENMASK(7, 1), r)

Yeah, good idea. Thanks for the FIELD_GET examples!

> [...]
> > +		break;
> > +	default:
> > +		name = "unknown";
> > +	}
> 
> I know this is far from hotpath, but could we save some object code and
> do that via O(1) array lookup? Plus some macro to compress things:
> 
> #define FORTIFY_ENTRY(name)		\
> 	[FORTIFY_FUNC_##name]	= #name
> 
> static const char * const fortify_funcs[] = {
> 	FORTIFY_ENTRY(strncpy),
> 	...
> }
> 
> 	// array bounds check here if you wish, I wouldn't bother as
> 	// we're panicking anyway
> 
> 	name = fortify_funcs[reason >> 1];

Fair enough. I had considered it and then forgot about it for some
reason. :)
Kees Cook April 6, 2023, 10:54 p.m. UTC | #6
On Thu, Apr 06, 2023 at 03:44:54PM +0200, Miguel Ojeda wrote:
> On Thu, Apr 6, 2023 at 2:02 AM Kees Cook <keescook@chromium.org> wrote:
> >
> > +void __fortify_report(u8 reason);
> > +void __fortify_panic(u8 reason) __cold __noreturn;
> 
> (snip)
> 
> > +void __fortify_report(u8 reason)
> 
> (snip)
> 
> > +void __fortify_panic(const u8 reason)
> 
> I am curious: for some reason (no pun intended :) the `reason`s above
> are not `const` except this one, but then in a later patch they become
> `const` (including the declarations).
> 
> So perhaps make everything `const` when they are introduced? Or is
> there some other reason? (e.g. I saw one patch that moved a function,
> so there it seemed to make sense to keep things as they are to make
> the copy 1:1).

I will adjust it -- this was an artifact of splitting up my patches.

Thanks!
Kees Cook April 6, 2023, 10:57 p.m. UTC | #7
On Thu, Apr 06, 2023 at 01:20:52PM +0300, Andy Shevchenko wrote:
> On Thu, Apr 6, 2023 at 3:02 AM Kees Cook <keescook@chromium.org> wrote:
> >
> > In preparation for KUnit testing and further improvements in fortify
> > failure reporting, split out the report and encode the function and
> > access failure (read or write overflow) into a single int argument. This
> > mainly ends up saving some space in the data segment. For a defconfig
> > with FORTIFY_SOURCE enabled:
> >
> > $ size gcc/vmlinux.before gcc/vmlinux.after
> >    text           data     bss     dec              hex filename
> > 26132309        9760658 2195460 38088427        2452eeb gcc/vmlinux.before
> > 26132386        9748382 2195460 38076228        244ff44 gcc/vmlinux.after
> 
> ...
> 
> > +       const char *name;
> > +       const bool write = !!(reason & 0x1);
> 
> Perhaps define that as
> 
> FORTIFY_READ_WRITE  BIT(0)
> FORTIFY_FUNC_SHIFT  1
> 
> const bool write = reason & FORTIFY_READ_WRITE; // and note no need for !! part

Yeah, that reads better. The FIELD_GET suggestion down-thread is
probably how I'll go.

> 
> switch (reason >> FORTIFY_FUNC_SHIFT) {
> 
> > +       switch (reason >> 1) {
> > +       case FORTIFY_FUNC_strncpy:
> > +               name = "strncpy";
> > +               break;
> > +       case FORTIFY_FUNC_strnlen:
> > +               name = "strnlen";
> > +               break;
> > +       case FORTIFY_FUNC_strlen:
> > +               name = "strlen";
> > +               break;
> > +       case FORTIFY_FUNC_strlcpy:
> > +               name = "strlcpy";
> > +               break;
> > +       case FORTIFY_FUNC_strscpy:
> > +               name = "strscpy";
> > +               break;
> > +       case FORTIFY_FUNC_strlcat:
> > +               name = "strlcat";
> > +               break;
> > +       case FORTIFY_FUNC_strcat:
> > +               name = "strcat";
> > +               break;
> > +       case FORTIFY_FUNC_strncat:
> > +               name = "strncat";
> > +               break;
> > +       case FORTIFY_FUNC_memset:
> > +               name = "memset";
> > +               break;
> > +       case FORTIFY_FUNC_memcpy:
> > +               name = "memcpy";
> > +               break;
> > +       case FORTIFY_FUNC_memmove:
> > +               name = "memmove";
> > +               break;
> > +       case FORTIFY_FUNC_memscan:
> > +               name = "memscan";
> > +               break;
> > +       case FORTIFY_FUNC_memcmp:
> > +               name = "memcmp";
> > +               break;
> > +       case FORTIFY_FUNC_memchr:
> > +               name = "memchr";
> > +               break;
> > +       case FORTIFY_FUNC_memchr_inv:
> > +               name = "memchr_inv";
> > +               break;
> > +       case FORTIFY_FUNC_kmemdup:
> > +               name = "kmemdup";
> > +               break;
> > +       case FORTIFY_FUNC_strcpy:
> > +               name = "strcpy";
> > +               break;
> > +       default:
> > +               name = "unknown";
> > +       }
> 
> ...
> 
> > +       WARN(1, "%s: detected buffer %s overflow\n", name, write ? "write" : "read");
> 
> Using str_read_write() ?
> 
> Dunno if it's already there or needs to be added. I have some patches
> to move those str_*() to string_choices.h. We can also prepend yours
> with those.

Oh! Hah. I totally forgot about str_read_write. :) I will use that.
Andy Shevchenko April 7, 2023, 8:34 a.m. UTC | #8
On Fri, Apr 7, 2023 at 1:57 AM Kees Cook <keescook@chromium.org> wrote:
> On Thu, Apr 06, 2023 at 01:20:52PM +0300, Andy Shevchenko wrote:
> > On Thu, Apr 6, 2023 at 3:02 AM Kees Cook <keescook@chromium.org> wrote:

...

> > > +       WARN(1, "%s: detected buffer %s overflow\n", name, write ? "write" : "read");
> >
> > Using str_read_write() ?
> >
> > Dunno if it's already there or needs to be added. I have some patches
> > to move those str_*() to string_choices.h. We can also prepend yours
> > with those.
>
> Oh! Hah. I totally forgot about str_read_write. :) I will use that.

Btw, makes sense to add

  #define str_write_read(v)    str_read_write(!(v))

to the header, so we won't use negation in the parameter for better readability.
kernel test robot April 7, 2023, 10:26 a.m. UTC | #9
Hi Kees,

kernel test robot noticed the following build warnings:

[auto build test WARNING on kees/for-next/hardening]
[also build test WARNING on kees/for-next/pstore kees/for-next/kspp akpm-mm/mm-everything linus/master v6.3-rc5 next-20230406]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Kees-Cook/kunit-tool-Enable-CONFIG_FORTIFY_SOURCE-under-UML/20230406-081014
base:   https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/hardening
patch link:    https://lore.kernel.org/r/20230406000212.3442647-6-keescook%40chromium.org
patch subject: [PATCH 6/9] fortify: Split reporting and avoid passing string pointer
config: arm64-randconfig-s053-20230406 (https://download.01.org/0day-ci/archive/20230407/202304071824.648116sH-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 12.1.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # apt-get install sparse
        # sparse version: v0.6.4-39-gce1a6720-dirty
        # https://github.com/intel-lab-lkp/linux/commit/4fd520e6ee549e1ffe8859e26e57ea64b48e78ea
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Kees-Cook/kunit-tool-Enable-CONFIG_FORTIFY_SOURCE-under-UML/20230406-081014
        git checkout 4fd520e6ee549e1ffe8859e26e57ea64b48e78ea
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' O=build_dir ARCH=arm64 olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' O=build_dir ARCH=arm64 SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>
| Link: https://lore.kernel.org/oe-kbuild-all/202304071824.648116sH-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
   lib/string_helpers.c:1030:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_strncpy'
   lib/string_helpers.c:1033:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_strnlen'
   lib/string_helpers.c:1036:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_strlen'
   lib/string_helpers.c:1039:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_strlcpy'
   lib/string_helpers.c:1042:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_strscpy'
   lib/string_helpers.c:1045:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_strlcat'
   lib/string_helpers.c:1048:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_strcat'
   lib/string_helpers.c:1051:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_strncat'
   lib/string_helpers.c:1054:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_memset'
   lib/string_helpers.c:1057:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_memcpy'
   lib/string_helpers.c:1060:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_memmove'
   lib/string_helpers.c:1063:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_memscan'
   lib/string_helpers.c:1066:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_memcmp'
   lib/string_helpers.c:1069:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_memchr'
   lib/string_helpers.c:1072:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_memchr_inv'
   lib/string_helpers.c:1075:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_kmemdup'
   lib/string_helpers.c:1078:14: sparse: sparse: undefined identifier 'FORTIFY_FUNC_strcpy'
>> lib/string_helpers.c:1030:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1033:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1036:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1039:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1042:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1045:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1048:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1051:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1054:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1057:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1060:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1063:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1066:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1069:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1072:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1075:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1078:14: sparse: sparse: incompatible types for 'case' statement
   lib/string_helpers.c:1030:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1033:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1036:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1039:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1042:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1045:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1048:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1051:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1054:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1057:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1060:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1063:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1066:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1069:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1072:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1075:14: sparse: sparse: Expected constant expression in case statement
   lib/string_helpers.c:1078:14: sparse: sparse: Expected constant expression in case statement

vim +/case +1030 lib/string_helpers.c

  1023	
  1024	void __fortify_report(u8 reason)
  1025	{
  1026		const char *name;
  1027		const bool write = !!(reason & 0x1);
  1028	
  1029		switch (reason >> 1) {
> 1030		case FORTIFY_FUNC_strncpy:
  1031			name = "strncpy";
  1032			break;
  1033		case FORTIFY_FUNC_strnlen:
  1034			name = "strnlen";
  1035			break;
  1036		case FORTIFY_FUNC_strlen:
  1037			name = "strlen";
  1038			break;
  1039		case FORTIFY_FUNC_strlcpy:
  1040			name = "strlcpy";
  1041			break;
  1042		case FORTIFY_FUNC_strscpy:
  1043			name = "strscpy";
  1044			break;
  1045		case FORTIFY_FUNC_strlcat:
  1046			name = "strlcat";
  1047			break;
  1048		case FORTIFY_FUNC_strcat:
  1049			name = "strcat";
  1050			break;
  1051		case FORTIFY_FUNC_strncat:
  1052			name = "strncat";
  1053			break;
> 1054		case FORTIFY_FUNC_memset:
  1055			name = "memset";
  1056			break;
  1057		case FORTIFY_FUNC_memcpy:
  1058			name = "memcpy";
  1059			break;
  1060		case FORTIFY_FUNC_memmove:
  1061			name = "memmove";
  1062			break;
  1063		case FORTIFY_FUNC_memscan:
  1064			name = "memscan";
  1065			break;
  1066		case FORTIFY_FUNC_memcmp:
  1067			name = "memcmp";
  1068			break;
  1069		case FORTIFY_FUNC_memchr:
  1070			name = "memchr";
  1071			break;
  1072		case FORTIFY_FUNC_memchr_inv:
  1073			name = "memchr_inv";
  1074			break;
  1075		case FORTIFY_FUNC_kmemdup:
  1076			name = "kmemdup";
  1077			break;
  1078		case FORTIFY_FUNC_strcpy:
  1079			name = "strcpy";
  1080			break;
  1081		default:
  1082			name = "unknown";
  1083		}
  1084		WARN(1, "%s: detected buffer %s overflow\n", name, write ? "write" : "read");
  1085	}
  1086	EXPORT_SYMBOL(__fortify_report);
  1087
Kees Cook April 7, 2023, 7:49 p.m. UTC | #10
On April 7, 2023 1:34:41 AM PDT, Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
>On Fri, Apr 7, 2023 at 1:57 AM Kees Cook <keescook@chromium.org> wrote:
>> On Thu, Apr 06, 2023 at 01:20:52PM +0300, Andy Shevchenko wrote:
>> > On Thu, Apr 6, 2023 at 3:02 AM Kees Cook <keescook@chromium.org> wrote:
>
>...
>
>> > > +       WARN(1, "%s: detected buffer %s overflow\n", name, write ? "write" : "read");
>> >
>> > Using str_read_write() ?
>> >
>> > Dunno if it's already there or needs to be added. I have some patches
>> > to move those str_*() to string_choices.h. We can also prepend yours
>> > with those.
>>
>> Oh! Hah. I totally forgot about str_read_write. :) I will use that.
>
>Btw, makes sense to add
>
>  #define str_write_read(v)    str_read_write(!(v))
>
>to the header, so we won't use negation in the parameter for better readability.

I ended up not going this far because the use of str_read_write() gets removed again in the last patch in the series.
Arnd Bergmann Feb. 22, 2024, 1 p.m. UTC | #11
On Thu, Apr 6, 2023, at 13:19, kernel test robot wrote:

> If you fix the issue, kindly add following tag where applicable
> | Reported-by: kernel test robot <lkp@intel.com>
> | Link: 
> https://lore.kernel.org/oe-kbuild-all/202304061930.4Au0PASm-lkp@intel.com/
>
> All errors (new ones prefixed by >>):
>
>>> ld.lld: error: undefined symbol: __fortify_panic
>    >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
>    >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_count)
>    >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
>    >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_search)
>    >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
>    >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_get)

I get a related build failure from the same commit:

arch/arm/boot/compressed/misc.c:157:6: error: no previous prototype for '__fortify_panic' [-Werror=missing-prototypes]
  157 | void __fortify_panic(const u8 reason, size_t avail, size_t size)

      Arnd
Kees Cook Feb. 22, 2024, 4:30 p.m. UTC | #12
On Thu, Feb 22, 2024 at 02:00:26PM +0100, Arnd Bergmann wrote:
> On Thu, Apr 6, 2023, at 13:19, kernel test robot wrote:
> 
> > If you fix the issue, kindly add following tag where applicable
> > | Reported-by: kernel test robot <lkp@intel.com>
> > | Link: 
> > https://lore.kernel.org/oe-kbuild-all/202304061930.4Au0PASm-lkp@intel.com/
> >
> > All errors (new ones prefixed by >>):
> >
> >>> ld.lld: error: undefined symbol: __fortify_panic
> >    >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
> >    >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_count)
> >    >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
> >    >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_search)
> >    >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
> >    >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_get)
> 
> I get a related build failure from the same commit:
> 
> arch/arm/boot/compressed/misc.c:157:6: error: no previous prototype for '__fortify_panic' [-Werror=missing-prototypes]
>   157 | void __fortify_panic(const u8 reason, size_t avail, size_t size)

Whoops, thank you! This series got a refresh after you did the
missing-prototypes work and I missed this one. I've fixed it and
double-checked for any others. Hopefully this will be clean in the next
-next. :)

-Kees
Andy Shevchenko Feb. 22, 2024, 5:11 p.m. UTC | #13
On Thu, Feb 22, 2024 at 6:30 PM Kees Cook <keescook@chromium.org> wrote:
> On Thu, Feb 22, 2024 at 02:00:26PM +0100, Arnd Bergmann wrote:
> > On Thu, Apr 6, 2023, at 13:19, kernel test robot wrote:
> >
> > > If you fix the issue, kindly add following tag where applicable
> > > | Reported-by: kernel test robot <lkp@intel.com>
> > > | Link:
> > > https://lore.kernel.org/oe-kbuild-all/202304061930.4Au0PASm-lkp@intel.com/
> > >
> > > All errors (new ones prefixed by >>):
> > >
> > >>> ld.lld: error: undefined symbol: __fortify_panic
> > >    >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
> > >    >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_count)
> > >    >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
> > >    >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_search)
> > >    >>> referenced by fortify-string.h:208 (include/linux/fortify-string.h:208)
> > >    >>>               arch/arm/boot/compressed/fdt_ro.o:(fdt_stringlist_get)
> >
> > I get a related build failure from the same commit:
> >
> > arch/arm/boot/compressed/misc.c:157:6: error: no previous prototype for '__fortify_panic' [-Werror=missing-prototypes]
> >   157 | void __fortify_panic(const u8 reason, size_t avail, size_t size)
>
> Whoops, thank you! This series got a refresh after you did the
> missing-prototypes work and I missed this one. I've fixed it and
> double-checked for any others. Hopefully this will be clean in the next
> -next. :)

Thank you! (FWIW, I also hit it today)
diff mbox series

Patch

diff --git a/include/linux/fortify-string.h b/include/linux/fortify-string.h
index 41dbd641f55c..6db4052db459 100644
--- a/include/linux/fortify-string.h
+++ b/include/linux/fortify-string.h
@@ -9,7 +9,34 @@ 
 #define __FORTIFY_INLINE extern __always_inline __gnu_inline __overloadable
 #define __RENAME(x) __asm__(#x)
 
-void fortify_panic(const char *name) __noreturn __cold;
+#define fortify_reason(func, write)	(((func) << 1) | !!(write))
+
+#define fortify_panic(func, write)	\
+	__fortify_panic(fortify_reason(func, write))
+
+#define FORTIFY_READ		 0
+#define FORTIFY_WRITE		 1
+
+#define FORTIFY_FUNC_strncpy	 0
+#define FORTIFY_FUNC_strnlen	 1
+#define FORTIFY_FUNC_strlen	 2
+#define FORTIFY_FUNC_strlcpy	 3
+#define FORTIFY_FUNC_strscpy	 4
+#define FORTIFY_FUNC_strlcat	 5
+#define FORTIFY_FUNC_strcat	 6
+#define FORTIFY_FUNC_strncat	 7
+#define FORTIFY_FUNC_memset	 8
+#define FORTIFY_FUNC_memcpy	 9
+#define FORTIFY_FUNC_memmove	10
+#define FORTIFY_FUNC_memscan	11
+#define FORTIFY_FUNC_memcmp	12
+#define FORTIFY_FUNC_memchr	13
+#define FORTIFY_FUNC_memchr_inv	14
+#define FORTIFY_FUNC_kmemdup	15
+#define FORTIFY_FUNC_strcpy	16
+
+void __fortify_report(u8 reason);
+void __fortify_panic(u8 reason) __cold __noreturn;
 void __read_overflow(void) __compiletime_error("detected read beyond size of object (1st parameter)");
 void __read_overflow2(void) __compiletime_error("detected read beyond size of object (2nd parameter)");
 void __read_overflow2_field(size_t avail, size_t wanted) __compiletime_warning("detected read beyond size of field (2nd parameter); maybe use struct_group()?");
@@ -147,7 +174,7 @@  char *strncpy(char * const POS p, const char *q, __kernel_size_t size)
 	if (__compiletime_lessthan(p_size, size))
 		__write_overflow();
 	if (p_size < size)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_strncpy, FORTIFY_WRITE);
 	return __underlying_strncpy(p, q, size);
 }
 
@@ -178,7 +205,7 @@  __FORTIFY_INLINE __kernel_size_t strnlen(const char * const POS p, __kernel_size
 	/* Do not check characters beyond the end of p. */
 	ret = __real_strnlen(p, maxlen < p_size ? maxlen : p_size);
 	if (p_size <= ret && maxlen != ret)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_strnlen, FORTIFY_READ);
 	return ret;
 }
 
@@ -214,7 +241,7 @@  __kernel_size_t __fortify_strlen(const char * const POS p)
 		return __underlying_strlen(p);
 	ret = strnlen(p, p_size);
 	if (p_size <= ret)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_strlen, FORTIFY_READ);
 	return ret;
 }
 
@@ -256,7 +283,7 @@  __FORTIFY_INLINE size_t strlcpy(char * const POS p, const char * const POS q, si
 	}
 	if (size) {
 		if (len >= p_size)
-			fortify_panic(__func__);
+			fortify_panic(FORTIFY_FUNC_strlcpy, FORTIFY_WRITE);
 		__underlying_memcpy(p, q, len);
 		p[len] = '\0';
 	}
@@ -334,7 +361,7 @@  __FORTIFY_INLINE ssize_t strscpy(char * const POS p, const char * const POS q, s
 	 * p_size.
 	 */
 	if (len > p_size)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_strscpy, FORTIFY_WRITE);
 
 	/*
 	 * We can now safely call vanilla strscpy because we are protected from:
@@ -392,7 +419,7 @@  size_t strlcat(char * const POS p, const char * const POS q, size_t avail)
 
 	/* Give up if string is already overflowed. */
 	if (p_size <= p_len)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_strlcat, FORTIFY_READ);
 
 	if (actual >= avail) {
 		copy_len = avail - p_len - 1;
@@ -401,7 +428,7 @@  size_t strlcat(char * const POS p, const char * const POS q, size_t avail)
 
 	/* Give up if copy will overflow. */
 	if (p_size <= actual)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_strlcat, FORTIFY_WRITE);
 	__underlying_memcpy(p + p_len, q, copy_len);
 	p[actual] = '\0';
 
@@ -430,7 +457,7 @@  char *strcat(char * const POS p, const char *q)
 	size_t p_size = __member_size(p);
 
 	if (strlcat(p, q, p_size) >= p_size)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_strcat, FORTIFY_WRITE);
 	return p;
 }
 
@@ -466,7 +493,7 @@  char *strncat(char * const POS p, const char * const POS q, __kernel_size_t coun
 	p_len = strlen(p);
 	copy_len = strnlen(q, count);
 	if (p_size < p_len + copy_len + 1)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_strncat, FORTIFY_WRITE);
 	__underlying_memcpy(p + p_len, q, copy_len);
 	p[p_len + copy_len] = '\0';
 	return p;
@@ -507,7 +534,7 @@  __FORTIFY_INLINE void fortify_memset_chk(__kernel_size_t size,
 	 * lengths are unknown.)
 	 */
 	if (p_size != SIZE_MAX && p_size < size)
-		fortify_panic("memset");
+		fortify_panic(FORTIFY_FUNC_memset, FORTIFY_WRITE);
 }
 
 #define __fortify_memset_chk(p, c, size, p_size, p_size_field) ({	\
@@ -561,7 +588,7 @@  __FORTIFY_INLINE bool fortify_memcpy_chk(__kernel_size_t size,
 					 const size_t q_size,
 					 const size_t p_size_field,
 					 const size_t q_size_field,
-					 const char *func)
+					 const u8 func)
 {
 	if (__builtin_constant_p(size)) {
 		/*
@@ -605,9 +632,10 @@  __FORTIFY_INLINE bool fortify_memcpy_chk(__kernel_size_t size,
 	 * (The SIZE_MAX test is to optimize away checks where the buffer
 	 * lengths are unknown.)
 	 */
-	if ((p_size != SIZE_MAX && p_size < size) ||
-	    (q_size != SIZE_MAX && q_size < size))
-		fortify_panic(func);
+	if (p_size != SIZE_MAX && p_size < size)
+		fortify_panic(func, FORTIFY_WRITE);
+	else if (q_size != SIZE_MAX && q_size < size)
+		fortify_panic(func, FORTIFY_READ);
 
 	/*
 	 * Warn when writing beyond destination field size.
@@ -640,7 +668,7 @@  __FORTIFY_INLINE bool fortify_memcpy_chk(__kernel_size_t size,
 	const size_t __q_size_field = (q_size_field);			\
 	WARN_ONCE(fortify_memcpy_chk(__fortify_size, __p_size,		\
 				     __q_size, __p_size_field,		\
-				     __q_size_field, #op),		\
+				     __q_size_field, FORTIFY_FUNC_ ##op), \
 		  #op ": detected field-spanning write (size %zu) of single %s (size %zu)\n", \
 		  __fortify_size,					\
 		  "field \"" #p "\" at " __FILE__ ":" __stringify(__LINE__), \
@@ -707,7 +735,7 @@  __FORTIFY_INLINE void *memscan(void * const POS0 p, int c, __kernel_size_t size)
 	if (__compiletime_lessthan(p_size, size))
 		__read_overflow();
 	if (p_size < size)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_memscan, FORTIFY_READ);
 	return __real_memscan(p, c, size);
 }
 
@@ -724,7 +752,7 @@  int memcmp(const void * const POS0 p, const void * const POS0 q, __kernel_size_t
 			__read_overflow2();
 	}
 	if (p_size < size || q_size < size)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_memcmp, FORTIFY_READ);
 	return __underlying_memcmp(p, q, size);
 }
 
@@ -736,7 +764,7 @@  void *memchr(const void * const POS0 p, int c, __kernel_size_t size)
 	if (__compiletime_lessthan(p_size, size))
 		__read_overflow();
 	if (p_size < size)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_memchr, FORTIFY_READ);
 	return __underlying_memchr(p, c, size);
 }
 
@@ -748,7 +776,7 @@  __FORTIFY_INLINE void *memchr_inv(const void * const POS0 p, int c, size_t size)
 	if (__compiletime_lessthan(p_size, size))
 		__read_overflow();
 	if (p_size < size)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_memchr_inv, FORTIFY_READ);
 	return __real_memchr_inv(p, c, size);
 }
 
@@ -761,7 +789,7 @@  __FORTIFY_INLINE void *kmemdup(const void * const POS0 p, size_t size, gfp_t gfp
 	if (__compiletime_lessthan(p_size, size))
 		__read_overflow();
 	if (p_size < size)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_kmemdup, FORTIFY_READ);
 	return __real_kmemdup(p, size, gfp);
 }
 
@@ -798,7 +826,7 @@  char *strcpy(char * const POS p, const char * const POS q)
 		__write_overflow();
 	/* Run-time check for dynamic size overflow. */
 	if (p_size < size)
-		fortify_panic(__func__);
+		fortify_panic(FORTIFY_FUNC_strcpy, FORTIFY_WRITE);
 	__underlying_memcpy(p, q, size);
 	return p;
 }
diff --git a/lib/string_helpers.c b/lib/string_helpers.c
index 230020a2e076..631c50657096 100644
--- a/lib/string_helpers.c
+++ b/lib/string_helpers.c
@@ -1021,10 +1021,74 @@  EXPORT_SYMBOL(__read_overflow2_field);
 void __write_overflow_field(size_t avail, size_t wanted) { }
 EXPORT_SYMBOL(__write_overflow_field);
 
-void fortify_panic(const char *name)
+void __fortify_report(u8 reason)
 {
-	pr_emerg("detected buffer overflow in %s\n", name);
+	const char *name;
+	const bool write = !!(reason & 0x1);
+
+	switch (reason >> 1) {
+	case FORTIFY_FUNC_strncpy:
+		name = "strncpy";
+		break;
+	case FORTIFY_FUNC_strnlen:
+		name = "strnlen";
+		break;
+	case FORTIFY_FUNC_strlen:
+		name = "strlen";
+		break;
+	case FORTIFY_FUNC_strlcpy:
+		name = "strlcpy";
+		break;
+	case FORTIFY_FUNC_strscpy:
+		name = "strscpy";
+		break;
+	case FORTIFY_FUNC_strlcat:
+		name = "strlcat";
+		break;
+	case FORTIFY_FUNC_strcat:
+		name = "strcat";
+		break;
+	case FORTIFY_FUNC_strncat:
+		name = "strncat";
+		break;
+	case FORTIFY_FUNC_memset:
+		name = "memset";
+		break;
+	case FORTIFY_FUNC_memcpy:
+		name = "memcpy";
+		break;
+	case FORTIFY_FUNC_memmove:
+		name = "memmove";
+		break;
+	case FORTIFY_FUNC_memscan:
+		name = "memscan";
+		break;
+	case FORTIFY_FUNC_memcmp:
+		name = "memcmp";
+		break;
+	case FORTIFY_FUNC_memchr:
+		name = "memchr";
+		break;
+	case FORTIFY_FUNC_memchr_inv:
+		name = "memchr_inv";
+		break;
+	case FORTIFY_FUNC_kmemdup:
+		name = "kmemdup";
+		break;
+	case FORTIFY_FUNC_strcpy:
+		name = "strcpy";
+		break;
+	default:
+		name = "unknown";
+	}
+	WARN(1, "%s: detected buffer %s overflow\n", name, write ? "write" : "read");
+}
+EXPORT_SYMBOL(__fortify_report);
+
+void __fortify_panic(const u8 reason)
+{
+	__fortify_report(reason);
 	BUG();
 }
-EXPORT_SYMBOL(fortify_panic);
+EXPORT_SYMBOL(__fortify_panic);
 #endif /* CONFIG_FORTIFY_SOURCE */
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index f937be1afe65..2d0a67ce1c51 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -197,6 +197,7 @@  static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
 	 * attribute isn't provided in ELF data. Keep 'em sorted.
 	 */
 	static const char * const global_noreturns[] = {
+		"__fortify_panic",
 		"__invalid_creds",
 		"__module_put_and_kthread_exit",
 		"__reiserfs_panic",
@@ -208,7 +209,6 @@  static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
 		"do_group_exit",
 		"do_task_dead",
 		"ex_handler_msr_mce",
-		"fortify_panic",
 		"kthread_complete_and_exit",
 		"kthread_exit",
 		"kunit_try_catch_throw",