signal: Deliver SIGTRAP on perf event asynchronously if blocked
authorMarco Elver <elver@google.com>
Mon, 4 Apr 2022 11:12:04 +0000 (13:12 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 9 Jun 2022 08:22:48 +0000 (10:22 +0200)
[ Upstream commit 78ed93d72ded679e3caf0758357209887bda885f ]

With SIGTRAP on perf events, we have encountered termination of
processes due to user space attempting to block delivery of SIGTRAP.
Consider this case:

    <set up SIGTRAP on a perf event>
    ...
    sigset_t s;
    sigemptyset(&s);
    sigaddset(&s, SIGTRAP | <and others>);
    sigprocmask(SIG_BLOCK, &s, ...);
    ...
    <perf event triggers>

When the perf event triggers, while SIGTRAP is blocked, force_sig_perf()
will force the signal, but revert back to the default handler, thus
terminating the task.

This makes sense for error conditions, but not so much for explicitly
requested monitoring. However, the expectation is still that signals
generated by perf events are synchronous, which will no longer be the
case if the signal is blocked and delivered later.

To give user space the ability to clearly distinguish synchronous from
asynchronous signals, introduce siginfo_t::si_perf_flags and
TRAP_PERF_FLAG_ASYNC (opted for flags in case more binary information is
required in future).

The resolution to the problem is then to (a) no longer force the signal
(avoiding the terminations), but (b) tell user space via si_perf_flags
if the signal was synchronous or not, so that such signals can be
handled differently (e.g. let user space decide to ignore or consider
the data imprecise).

The alternative of making the kernel ignore SIGTRAP on perf events if
the signal is blocked may work for some usecases, but likely causes
issues in others that then have to revert back to interception of
sigprocmask() (which we want to avoid). [ A concrete example: when using
breakpoint perf events to track data-flow, in a region of code where
signals are blocked, data-flow can no longer be tracked accurately.
When a relevant asynchronous signal is received after unblocking the
signal, the data-flow tracking logic needs to know its state is
imprecise. ]

Fixes: 97ba62b27867 ("perf: Add support for SIGTRAP on perf events")
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Marco Elver <elver@google.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Geert Uytterhoeven <geert@linux-m68k.org>
Tested-by: Dmitry Vyukov <dvyukov@google.com>
Link: https://lore.kernel.org/r/20220404111204.935357-1-elver@google.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
12 files changed:
arch/arm/kernel/signal.c
arch/arm64/kernel/signal.c
arch/arm64/kernel/signal32.c
arch/m68k/kernel/signal.c
arch/sparc/kernel/signal32.c
arch/sparc/kernel/signal_64.c
arch/x86/kernel/signal_compat.c
include/linux/compat.h
include/linux/sched/signal.h
include/uapi/asm-generic/siginfo.h
kernel/events/core.c
kernel/signal.c

index a41e27ace391fa98450e1abd9b9a58e12758cba2..539897ac28284846428a2ce1dbcf03b33006dd2f 100644 (file)
@@ -708,6 +708,7 @@ static_assert(offsetof(siginfo_t, si_upper) == 0x18);
 static_assert(offsetof(siginfo_t, si_pkey)     == 0x14);
 static_assert(offsetof(siginfo_t, si_perf_data)        == 0x10);
 static_assert(offsetof(siginfo_t, si_perf_type)        == 0x14);
+static_assert(offsetof(siginfo_t, si_perf_flags) == 0x18);
 static_assert(offsetof(siginfo_t, si_band)     == 0x0c);
 static_assert(offsetof(siginfo_t, si_fd)       == 0x10);
 static_assert(offsetof(siginfo_t, si_call_addr)        == 0x0c);
index 981f0c4157c2fa41c3b2284ffef7ca310a819263..b3e1beccf45886b2df777e3acb563e0609967660 100644 (file)
@@ -1012,6 +1012,7 @@ static_assert(offsetof(siginfo_t, si_upper)       == 0x28);
 static_assert(offsetof(siginfo_t, si_pkey)     == 0x20);
 static_assert(offsetof(siginfo_t, si_perf_data)        == 0x18);
 static_assert(offsetof(siginfo_t, si_perf_type)        == 0x20);
+static_assert(offsetof(siginfo_t, si_perf_flags) == 0x24);
 static_assert(offsetof(siginfo_t, si_band)     == 0x10);
 static_assert(offsetof(siginfo_t, si_fd)       == 0x18);
 static_assert(offsetof(siginfo_t, si_call_addr)        == 0x10);
index d984282b979f86e6debaf29aa5a52a480de253a6..4700f8522d27b191858bff1924587793151a6acd 100644 (file)
@@ -487,6 +487,7 @@ static_assert(offsetof(compat_siginfo_t, si_upper)  == 0x18);
 static_assert(offsetof(compat_siginfo_t, si_pkey)      == 0x14);
 static_assert(offsetof(compat_siginfo_t, si_perf_data) == 0x10);
 static_assert(offsetof(compat_siginfo_t, si_perf_type) == 0x14);
+static_assert(offsetof(compat_siginfo_t, si_perf_flags)        == 0x18);
 static_assert(offsetof(compat_siginfo_t, si_band)      == 0x0c);
 static_assert(offsetof(compat_siginfo_t, si_fd)                == 0x10);
 static_assert(offsetof(compat_siginfo_t, si_call_addr) == 0x0c);
index 338817d0cb3fb100609c1c38dc899d27ff5508ba..74ee1e3013d700f689f0b6b3882eb9b1db8ad0a0 100644 (file)
@@ -625,6 +625,7 @@ static inline void siginfo_build_tests(void)
        /* _sigfault._perf */
        BUILD_BUG_ON(offsetof(siginfo_t, si_perf_data) != 0x10);
        BUILD_BUG_ON(offsetof(siginfo_t, si_perf_type) != 0x14);
+       BUILD_BUG_ON(offsetof(siginfo_t, si_perf_flags) != 0x18);
 
        /* _sigpoll */
        BUILD_BUG_ON(offsetof(siginfo_t, si_band)   != 0x0c);
index 6cc124a3bb98a0394ef81d07ba7d1d4b01ca8787..90ff7ff94ea7f5b7352c86324eb514fac7d85d28 100644 (file)
@@ -780,5 +780,6 @@ static_assert(offsetof(compat_siginfo_t, si_upper)  == 0x18);
 static_assert(offsetof(compat_siginfo_t, si_pkey)      == 0x14);
 static_assert(offsetof(compat_siginfo_t, si_perf_data) == 0x10);
 static_assert(offsetof(compat_siginfo_t, si_perf_type) == 0x14);
+static_assert(offsetof(compat_siginfo_t, si_perf_flags)        == 0x18);
 static_assert(offsetof(compat_siginfo_t, si_band)      == 0x0c);
 static_assert(offsetof(compat_siginfo_t, si_fd)                == 0x10);
index 2a78d2af1265525f27a9d08ad88dc23b839968b5..6eeb766987d1ad42f546d6902512951d5d0375d4 100644 (file)
@@ -590,5 +590,6 @@ static_assert(offsetof(siginfo_t, si_upper) == 0x28);
 static_assert(offsetof(siginfo_t, si_pkey)     == 0x20);
 static_assert(offsetof(siginfo_t, si_perf_data)        == 0x18);
 static_assert(offsetof(siginfo_t, si_perf_type)        == 0x20);
+static_assert(offsetof(siginfo_t, si_perf_flags) == 0x24);
 static_assert(offsetof(siginfo_t, si_band)     == 0x10);
 static_assert(offsetof(siginfo_t, si_fd)       == 0x14);
index b52407c56000e11c6a97a1d0b414e535cbc6b5dd..879ef8c72f5c0f073f47d37e7584ac65a3d0cdc8 100644 (file)
@@ -149,8 +149,10 @@ static inline void signal_compat_build_tests(void)
 
        BUILD_BUG_ON(offsetof(siginfo_t, si_perf_data) != 0x18);
        BUILD_BUG_ON(offsetof(siginfo_t, si_perf_type) != 0x20);
+       BUILD_BUG_ON(offsetof(siginfo_t, si_perf_flags) != 0x24);
        BUILD_BUG_ON(offsetof(compat_siginfo_t, si_perf_data) != 0x10);
        BUILD_BUG_ON(offsetof(compat_siginfo_t, si_perf_type) != 0x14);
+       BUILD_BUG_ON(offsetof(compat_siginfo_t, si_perf_flags) != 0x18);
 
        CHECK_CSI_OFFSET(_sigpoll);
        CHECK_CSI_SIZE  (_sigpoll, 2*sizeof(int));
index 1c758b0e03598f672267110fa99bc447446896a1..01fddf72a81f081d4392b56aa3734123f8fa7a29 100644 (file)
@@ -235,6 +235,7 @@ typedef struct compat_siginfo {
                                struct {
                                        compat_ulong_t _data;
                                        u32 _type;
+                                       u32 _flags;
                                } _perf;
                        };
                } _sigfault;
index 9a707b555b0a07cd9292433086b89ff230279f11..5f0e8403e8cebedaf799c98a8c8285277c7452f1 100644 (file)
@@ -318,7 +318,7 @@ int send_sig_mceerr(int code, void __user *, short, struct task_struct *);
 
 int force_sig_bnderr(void __user *addr, void __user *lower, void __user *upper);
 int force_sig_pkuerr(void __user *addr, u32 pkey);
-int force_sig_perf(void __user *addr, u32 type, u64 sig_data);
+int send_sig_perf(void __user *addr, u32 type, u64 sig_data);
 
 int force_sig_ptrace_errno_trap(int errno, void __user *addr);
 int force_sig_fault_trapno(int sig, int code, void __user *addr, int trapno);
index 3ba180f550d7cfc9cf78e4c7133047070625b11b..ffbe4cec9f32de390af4887de2e0aa42c945f153 100644 (file)
@@ -99,6 +99,7 @@ union __sifields {
                        struct {
                                unsigned long _data;
                                __u32 _type;
+                               __u32 _flags;
                        } _perf;
                };
        } _sigfault;
@@ -164,6 +165,7 @@ typedef struct siginfo {
 #define si_pkey                _sifields._sigfault._addr_pkey._pkey
 #define si_perf_data   _sifields._sigfault._perf._data
 #define si_perf_type   _sifields._sigfault._perf._type
+#define si_perf_flags  _sifields._sigfault._perf._flags
 #define si_band                _sifields._sigpoll._band
 #define si_fd          _sifields._sigpoll._fd
 #define si_call_addr   _sifields._sigsys._call_addr
@@ -270,6 +272,11 @@ typedef struct siginfo {
  * that are of the form: ((PTRACE_EVENT_XXX << 8) | SIGTRAP)
  */
 
+/*
+ * Flags for si_perf_flags if SIGTRAP si_code is TRAP_PERF.
+ */
+#define TRAP_PERF_FLAG_ASYNC (1u << 0)
+
 /*
  * SIGCHLD si_codes
  */
index 565910de92e9b65b5a85b3e1dd4690b24a12a1b7..d7e05d9375602a0dce7a628b54efda595084344c 100644 (file)
@@ -6529,8 +6529,8 @@ static void perf_sigtrap(struct perf_event *event)
        if (current->flags & PF_EXITING)
                return;
 
-       force_sig_perf((void __user *)event->pending_addr,
-                      event->attr.type, event->attr.sig_data);
+       send_sig_perf((void __user *)event->pending_addr,
+                     event->attr.type, event->attr.sig_data);
 }
 
 static void perf_pending_event_disable(struct perf_event *event)
index 6e3dbb3d12170c02ab5d768269de9c9626d2219c..d831f0aec56e65453fdb633fe04ce3173a874788 100644 (file)
@@ -1802,7 +1802,7 @@ int force_sig_pkuerr(void __user *addr, u32 pkey)
 }
 #endif
 
-int force_sig_perf(void __user *addr, u32 type, u64 sig_data)
+int send_sig_perf(void __user *addr, u32 type, u64 sig_data)
 {
        struct kernel_siginfo info;
 
@@ -1814,7 +1814,18 @@ int force_sig_perf(void __user *addr, u32 type, u64 sig_data)
        info.si_perf_data = sig_data;
        info.si_perf_type = type;
 
-       return force_sig_info(&info);
+       /*
+        * Signals generated by perf events should not terminate the whole
+        * process if SIGTRAP is blocked, however, delivering the signal
+        * asynchronously is better than not delivering at all. But tell user
+        * space if the signal was asynchronous, so it can clearly be
+        * distinguished from normal synchronous ones.
+        */
+       info.si_perf_flags = sigismember(&current->blocked, info.si_signo) ?
+                                    TRAP_PERF_FLAG_ASYNC :
+                                    0;
+
+       return send_sig_info(info.si_signo, &info, current);
 }
 
 /**
@@ -3445,6 +3456,7 @@ void copy_siginfo_to_external32(struct compat_siginfo *to,
                to->si_addr = ptr_to_compat(from->si_addr);
                to->si_perf_data = from->si_perf_data;
                to->si_perf_type = from->si_perf_type;
+               to->si_perf_flags = from->si_perf_flags;
                break;
        case SIL_CHLD:
                to->si_pid = from->si_pid;
@@ -3522,6 +3534,7 @@ static int post_copy_siginfo_from_user32(kernel_siginfo_t *to,
                to->si_addr = compat_ptr(from->si_addr);
                to->si_perf_data = from->si_perf_data;
                to->si_perf_type = from->si_perf_type;
+               to->si_perf_flags = from->si_perf_flags;
                break;
        case SIL_CHLD:
                to->si_pid    = from->si_pid;
@@ -4702,6 +4715,7 @@ static inline void siginfo_buildtime_checks(void)
        CHECK_OFFSET(si_pkey);
        CHECK_OFFSET(si_perf_data);
        CHECK_OFFSET(si_perf_type);
+       CHECK_OFFSET(si_perf_flags);
 
        /* sigpoll */
        CHECK_OFFSET(si_band);