diff mbox series

[tip,1/2] signal, perf: Fix siginfo_t by avoiding u64 on 32-bit architectures

Message ID 20210422064437.3577327-1-elver@google.com (mailing list archive)
State New, archived
Headers show
Series [tip,1/2] signal, perf: Fix siginfo_t by avoiding u64 on 32-bit architectures | expand

Commit Message

Marco Elver April 22, 2021, 6:44 a.m. UTC
On some architectures, like Arm, the alignment of a structure is that of
its largest member.

This means that there is no portable way to add 64-bit integers to
siginfo_t on 32-bit architectures, because siginfo_t does not contain
any 64-bit integers on 32-bit architectures.

In the case of the si_perf field, word size is sufficient since there is
no exact requirement on size, given the data it contains is user-defined
via perf_event_attr::sig_data. On 32-bit architectures, any excess bits
of perf_event_attr::sig_data will therefore be truncated when copying
into si_perf.

Since this field is intended to disambiguate events (e.g. encoding
relevant information if there are more events of the same type), 32 bits
should provide enough entropy to do so on 32-bit architectures.

For 64-bit architectures, no change is intended.

Fixes: fb6cc127e0b6 ("signal: Introduce TRAP_PERF si_code and si_perf to siginfo")
Reported-by: Marek Szyprowski <m.szyprowski@samsung.com>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Reported-by: Jon Hunter <jonathanh@nvidia.com>
Signed-off-by: Marco Elver <elver@google.com>
---

Note: I added static_assert()s to verify the siginfo_t layout to
arch/arm and arch/arm64, which caught the problem. I'll send them
separately to arm&arm64 maintainers respectively.
---
 include/linux/compat.h                                | 2 +-
 include/uapi/asm-generic/siginfo.h                    | 2 +-
 tools/testing/selftests/perf_events/sigtrap_threads.c | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

Comments

Jon Hunter April 22, 2021, 8:15 a.m. UTC | #1
On 22/04/2021 07:44, Marco Elver wrote:
> On some architectures, like Arm, the alignment of a structure is that of
> its largest member.
> 
> This means that there is no portable way to add 64-bit integers to
> siginfo_t on 32-bit architectures, because siginfo_t does not contain
> any 64-bit integers on 32-bit architectures.
> 
> In the case of the si_perf field, word size is sufficient since there is
> no exact requirement on size, given the data it contains is user-defined
> via perf_event_attr::sig_data. On 32-bit architectures, any excess bits
> of perf_event_attr::sig_data will therefore be truncated when copying
> into si_perf.
> 
> Since this field is intended to disambiguate events (e.g. encoding
> relevant information if there are more events of the same type), 32 bits
> should provide enough entropy to do so on 32-bit architectures.
> 
> For 64-bit architectures, no change is intended.
> 
> Fixes: fb6cc127e0b6 ("signal: Introduce TRAP_PERF si_code and si_perf to siginfo")
> Reported-by: Marek Szyprowski <m.szyprowski@samsung.com>
> Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
> Reported-by: Jon Hunter <jonathanh@nvidia.com>
> Signed-off-by: Marco Elver <elver@google.com>


Thanks for fixing!

Tested-by: Jon Hunter <jonathanh@nvidia.com>

Cheers
Jon
David Laight April 22, 2021, 9:48 a.m. UTC | #2
From: Marco Elver
> Sent: 22 April 2021 07:45
> 
> On some architectures, like Arm, the alignment of a structure is that of
> its largest member.

That is true everywhere.
(Apart from obscure ABI where structure have at least 4 byte alignment!)

> This means that there is no portable way to add 64-bit integers to
> siginfo_t on 32-bit architectures, because siginfo_t does not contain
> any 64-bit integers on 32-bit architectures.

Uh?

The actual problem is that adding a 64-bit aligned item to the union
forces the union to be 8 byte aligned and adds a 4 byte pad before it
(and possibly another one at the end of the structure).

> In the case of the si_perf field, word size is sufficient since there is
> no exact requirement on size, given the data it contains is user-defined
> via perf_event_attr::sig_data. On 32-bit architectures, any excess bits
> of perf_event_attr::sig_data will therefore be truncated when copying
> into si_perf.

Is that right on BE architectures?

> Since this field is intended to disambiguate events (e.g. encoding
> relevant information if there are more events of the same type), 32 bits
> should provide enough entropy to do so on 32-bit architectures.

What is the size of the field used to supply the data?
The size of the returned item really ought to match.

Much as I hate __packed, you could add __packed to the
definition of the structure member _perf.
The compiler will remove the padding before it and will
assume it has the alignment of the previous item.

So it will never use byte accesses.

	David

> 
> For 64-bit architectures, no change is intended.
> 
> Fixes: fb6cc127e0b6 ("signal: Introduce TRAP_PERF si_code and si_perf to siginfo")
> Reported-by: Marek Szyprowski <m.szyprowski@samsung.com>
> Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
> Reported-by: Jon Hunter <jonathanh@nvidia.com>
> Signed-off-by: Marco Elver <elver@google.com>
> ---
> 
> Note: I added static_assert()s to verify the siginfo_t layout to
> arch/arm and arch/arm64, which caught the problem. I'll send them
> separately to arm&arm64 maintainers respectively.
> ---
>  include/linux/compat.h                                | 2 +-
>  include/uapi/asm-generic/siginfo.h                    | 2 +-
>  tools/testing/selftests/perf_events/sigtrap_threads.c | 2 +-
>  3 files changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/include/linux/compat.h b/include/linux/compat.h
> index c8821d966812..f0d2dd35d408 100644
> --- a/include/linux/compat.h
> +++ b/include/linux/compat.h
> @@ -237,7 +237,7 @@ typedef struct compat_siginfo {
>  					u32 _pkey;
>  				} _addr_pkey;
>  				/* used when si_code=TRAP_PERF */
> -				compat_u64 _perf;
> +				compat_ulong_t _perf;
>  			};
>  		} _sigfault;
> 
> diff --git a/include/uapi/asm-generic/siginfo.h b/include/uapi/asm-generic/siginfo.h
> index d0bb9125c853..03d6f6d2c1fe 100644
> --- a/include/uapi/asm-generic/siginfo.h
> +++ b/include/uapi/asm-generic/siginfo.h
> @@ -92,7 +92,7 @@ union __sifields {
>  				__u32 _pkey;
>  			} _addr_pkey;
>  			/* used when si_code=TRAP_PERF */
> -			__u64 _perf;
> +			unsigned long _perf;
>  		};
>  	} _sigfault;
> 
> diff --git a/tools/testing/selftests/perf_events/sigtrap_threads.c
> b/tools/testing/selftests/perf_events/sigtrap_threads.c
> index 9c0fd442da60..78ddf5e11625 100644
> --- a/tools/testing/selftests/perf_events/sigtrap_threads.c
> +++ b/tools/testing/selftests/perf_events/sigtrap_threads.c
> @@ -44,7 +44,7 @@ static struct {
>  } ctx;
> 
>  /* Unique value to check si_perf is correctly set from perf_event_attr::sig_data. */
> -#define TEST_SIG_DATA(addr) (~(uint64_t)(addr))
> +#define TEST_SIG_DATA(addr) (~(unsigned long)(addr))
> 
>  static struct perf_event_attr make_event_attr(bool enabled, volatile void *addr)
>  {
> --
> 2.31.1.498.g6c1eba8ee3d-goog

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
Marco Elver April 22, 2021, 10:17 a.m. UTC | #3
On Thu, 22 Apr 2021 at 11:48, David Laight <David.Laight@aculab.com> wrote:
>
> From: Marco Elver
> > Sent: 22 April 2021 07:45
> >
> > On some architectures, like Arm, the alignment of a structure is that of
> > its largest member.
>
> That is true everywhere.
> (Apart from obscure ABI where structure have at least 4 byte alignment!)

For instance, x86 didn't complain, nor did m68k. Both of them have
compile-time checks for the layout (I'm adding those for Arm
elsewhere).

> > This means that there is no portable way to add 64-bit integers to
> > siginfo_t on 32-bit architectures, because siginfo_t does not contain
> > any 64-bit integers on 32-bit architectures.
>
> Uh?
>
> The actual problem is that adding a 64-bit aligned item to the union
> forces the union to be 8 byte aligned and adds a 4 byte pad before it
> (and possibly another one at the end of the structure).

Yes, which means there's no portable way (without starting to add
attributes that are outside the C std) to add 64-bit integers to
siginfo_t without breaking the ABI on 32-bit architectures.

> > In the case of the si_perf field, word size is sufficient since there is
> > no exact requirement on size, given the data it contains is user-defined
> > via perf_event_attr::sig_data. On 32-bit architectures, any excess bits
> > of perf_event_attr::sig_data will therefore be truncated when copying
> > into si_perf.
>
> Is that right on BE architectures?

We effectively do

  u64 sig_data = ...;
  unsigned long si_perf = sig_data;

Since the user decides what to place into perf_event_attr::sig_data,
whatever ends up in si_perf is fully controllable by the user, who
knows which arch they're on. So I do not think this is a problem.

> > Since this field is intended to disambiguate events (e.g. encoding
> > relevant information if there are more events of the same type), 32 bits
> > should provide enough entropy to do so on 32-bit architectures.
>
> What is the size of the field used to supply the data?
> The size of the returned item really ought to match.

It's u64, but because perf_event_attr wants fixed size fields, this
can't change.

> Much as I hate __packed, you could add __packed to the
> definition of the structure member _perf.
> The compiler will remove the padding before it and will
> assume it has the alignment of the previous item.
>
> So it will never use byte accesses.

Sure __packed works for Arm. But I think there's no precedent using
this on siginfo_t, possibly for good reasons? I simply can't find
evidence that this is portable on *all* architectures and for *all*
possible definitions of siginfo_t, including those that live in things
like glibc.

Can we confirm that __packed is fine to add to siginfo_t on *all*
architectures for *all* possible definitions of siginfo_t? I currently
can't. And given it's outside the scope of the C standard (as of C11
we got _Alignas, but that doesn't help I think), I'd vote to not
venture too far for code that should be portable especially things as
important as siginfo_t, and has definitions *outside* the kernel (I
know we do lots of non-standard things, but others might not).

Thanks,
-- Marco

>         David
>
> >
> > For 64-bit architectures, no change is intended.
> >
> > Fixes: fb6cc127e0b6 ("signal: Introduce TRAP_PERF si_code and si_perf to siginfo")
> > Reported-by: Marek Szyprowski <m.szyprowski@samsung.com>
> > Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
> > Reported-by: Jon Hunter <jonathanh@nvidia.com>
> > Signed-off-by: Marco Elver <elver@google.com>
> > ---
> >
> > Note: I added static_assert()s to verify the siginfo_t layout to
> > arch/arm and arch/arm64, which caught the problem. I'll send them
> > separately to arm&arm64 maintainers respectively.
> > ---
> >  include/linux/compat.h                                | 2 +-
> >  include/uapi/asm-generic/siginfo.h                    | 2 +-
> >  tools/testing/selftests/perf_events/sigtrap_threads.c | 2 +-
> >  3 files changed, 3 insertions(+), 3 deletions(-)
> >
> > diff --git a/include/linux/compat.h b/include/linux/compat.h
> > index c8821d966812..f0d2dd35d408 100644
> > --- a/include/linux/compat.h
> > +++ b/include/linux/compat.h
> > @@ -237,7 +237,7 @@ typedef struct compat_siginfo {
> >                                       u32 _pkey;
> >                               } _addr_pkey;
> >                               /* used when si_code=TRAP_PERF */
> > -                             compat_u64 _perf;
> > +                             compat_ulong_t _perf;
> >                       };
> >               } _sigfault;
> >
> > diff --git a/include/uapi/asm-generic/siginfo.h b/include/uapi/asm-generic/siginfo.h
> > index d0bb9125c853..03d6f6d2c1fe 100644
> > --- a/include/uapi/asm-generic/siginfo.h
> > +++ b/include/uapi/asm-generic/siginfo.h
> > @@ -92,7 +92,7 @@ union __sifields {
> >                               __u32 _pkey;
> >                       } _addr_pkey;
> >                       /* used when si_code=TRAP_PERF */
> > -                     __u64 _perf;
> > +                     unsigned long _perf;
> >               };
> >       } _sigfault;
> >
> > diff --git a/tools/testing/selftests/perf_events/sigtrap_threads.c
> > b/tools/testing/selftests/perf_events/sigtrap_threads.c
> > index 9c0fd442da60..78ddf5e11625 100644
> > --- a/tools/testing/selftests/perf_events/sigtrap_threads.c
> > +++ b/tools/testing/selftests/perf_events/sigtrap_threads.c
> > @@ -44,7 +44,7 @@ static struct {
> >  } ctx;
> >
> >  /* Unique value to check si_perf is correctly set from perf_event_attr::sig_data. */
> > -#define TEST_SIG_DATA(addr) (~(uint64_t)(addr))
> > +#define TEST_SIG_DATA(addr) (~(unsigned long)(addr))
> >
> >  static struct perf_event_attr make_event_attr(bool enabled, volatile void *addr)
> >  {
> > --
> > 2.31.1.498.g6c1eba8ee3d-goog
>
> -
> Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
> Registration No: 1397386 (Wales)
Marco Elver April 22, 2021, 7:22 p.m. UTC | #4
On Thu, 22 Apr 2021 at 12:17, Marco Elver <elver@google.com> wrote:
> On Thu, 22 Apr 2021 at 11:48, David Laight <David.Laight@aculab.com> wrote:
> >
> > From: Marco Elver
> > > Sent: 22 April 2021 07:45
> > >
> > > On some architectures, like Arm, the alignment of a structure is that of
> > > its largest member.
> >
> > That is true everywhere.
> > (Apart from obscure ABI where structure have at least 4 byte alignment!)
>
> For instance, x86 didn't complain, nor did m68k. Both of them have
> compile-time checks for the layout (I'm adding those for Arm
> elsewhere).
[...]
> > Much as I hate __packed, you could add __packed to the
> > definition of the structure member _perf.
> > The compiler will remove the padding before it and will
> > assume it has the alignment of the previous item.
> >
> > So it will never use byte accesses.
>
> Sure __packed works for Arm. But I think there's no precedent using
> this on siginfo_t, possibly for good reasons? I simply can't find
> evidence that this is portable on *all* architectures and for *all*
> possible definitions of siginfo_t, including those that live in things
> like glibc.
>
> Can we confirm that __packed is fine to add to siginfo_t on *all*
> architectures for *all* possible definitions of siginfo_t? I currently
> can't. And given it's outside the scope of the C standard (as of C11
> we got _Alignas, but that doesn't help I think), I'd vote to not
> venture too far for code that should be portable especially things as
> important as siginfo_t, and has definitions *outside* the kernel (I
> know we do lots of non-standard things, but others might not).

After thinking about this all afternoon, you convinced me that the
commit message wasn't great, and this should be in the commit message,
too: https://lkml.kernel.org/r/20210422191823.79012-1-elver@google.com

Thanks,
-- Marco
diff mbox series

Patch

diff --git a/include/linux/compat.h b/include/linux/compat.h
index c8821d966812..f0d2dd35d408 100644
--- a/include/linux/compat.h
+++ b/include/linux/compat.h
@@ -237,7 +237,7 @@  typedef struct compat_siginfo {
 					u32 _pkey;
 				} _addr_pkey;
 				/* used when si_code=TRAP_PERF */
-				compat_u64 _perf;
+				compat_ulong_t _perf;
 			};
 		} _sigfault;
 
diff --git a/include/uapi/asm-generic/siginfo.h b/include/uapi/asm-generic/siginfo.h
index d0bb9125c853..03d6f6d2c1fe 100644
--- a/include/uapi/asm-generic/siginfo.h
+++ b/include/uapi/asm-generic/siginfo.h
@@ -92,7 +92,7 @@  union __sifields {
 				__u32 _pkey;
 			} _addr_pkey;
 			/* used when si_code=TRAP_PERF */
-			__u64 _perf;
+			unsigned long _perf;
 		};
 	} _sigfault;
 
diff --git a/tools/testing/selftests/perf_events/sigtrap_threads.c b/tools/testing/selftests/perf_events/sigtrap_threads.c
index 9c0fd442da60..78ddf5e11625 100644
--- a/tools/testing/selftests/perf_events/sigtrap_threads.c
+++ b/tools/testing/selftests/perf_events/sigtrap_threads.c
@@ -44,7 +44,7 @@  static struct {
 } ctx;
 
 /* Unique value to check si_perf is correctly set from perf_event_attr::sig_data. */
-#define TEST_SIG_DATA(addr) (~(uint64_t)(addr))
+#define TEST_SIG_DATA(addr) (~(unsigned long)(addr))
 
 static struct perf_event_attr make_event_attr(bool enabled, volatile void *addr)
 {