Add CONFIG_SIGNALS_DEBUG, which provides /proc/sys/kern/sigset_size, /proc/sys/kern/compat_sigset_size (if CONFIG_COMPAT is enabled), /proc/sys/kern/max_sig and /proc/sys/kern/sigrtmax to indicate sigset sizes, max signal number (_NSIG) and value of SIGRTMAX respectively. This also adds /proc/<pid>/signal, which sends a signal number to <pid> without going through libc. Signed-off-by: Walt Drummond <walt@xxxxxxxxxxx> --- fs/proc/base.c | 48 ++++++++++++++++++++++++++++++++++++++++++ include/linux/signal.h | 1 + kernel/signal.c | 15 ++++++++----- kernel/sysctl.c | 41 ++++++++++++++++++++++++++++++++++++ lib/Kconfig.debug | 10 +++++++++ 5 files changed, 110 insertions(+), 5 deletions(-) diff --git a/fs/proc/base.c b/fs/proc/base.c index 533d5836eb9a..75184abf9af1 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -3165,6 +3165,51 @@ static int proc_stack_depth(struct seq_file *m, struct pid_namespace *ns, } #endif /* CONFIG_STACKLEAK_METRICS */ +#ifdef CONFIG_SIGNALS_DEBUG +static ssize_t proc_signal_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode *inode = file_inode(file); + struct task_struct *task = get_proc_task(inode); + int ret; + pid_t pid; + unsigned long sig = (unsigned long) -1; + + if (!task) + return -ESRCH; + if (*ppos != 0) + /* No partial writes. */ + return -EINVAL; + + if (count > 4 || count <= 1) + return -EINVAL; + + ret = kstrtoul_from_user(buf, count, 10, &sig); + if (ret != 0) + return -EINVAL; + + if (!valid_signal(sig)) + return -EINVAL; + if (sig == 0) + return count; + + pid = pid_vnr(get_task_pid(task, PIDTYPE_PID)); + if (pid == 0) + return -EINVAL; + + ret = do_sys_kill(pid, sig); + if (ret) + return ret; + + return count; +} + +static const struct file_operations proc_signal_operations = { + .write = proc_signal_write, + .llseek = noop_llseek, +}; +#endif /* CONFIG_SIGNALS_DEBUG */ + /* * Thread groups */ @@ -3281,6 +3326,9 @@ static const struct pid_entry tgid_base_stuff[] = { #ifdef CONFIG_SECCOMP_CACHE_DEBUG ONE("seccomp_cache", S_IRUSR, proc_pid_seccomp_cache), #endif +#ifdef CONFIG_SIGNALS_DEBUG + REG("signal", S_IWUSR, proc_signal_operations), +#endif }; static int proc_tgid_base_readdir(struct file *file, struct dir_context *ctx) diff --git a/include/linux/signal.h b/include/linux/signal.h index 4084b765a6cc..b77f9472a37c 100644 --- a/include/linux/signal.h +++ b/include/linux/signal.h @@ -446,6 +446,7 @@ extern bool get_signal(struct ksignal *ksig); extern void signal_setup_done(int failed, struct ksignal *ksig, int stepping); extern void exit_signals(struct task_struct *tsk); extern void kernel_sigaction(int, __sighandler_t); +extern int do_sys_kill(pid_t pid, int sig); #define SIG_KTHREAD ((__force __sighandler_t)2) #define SIG_KTHREAD_KERNEL ((__force __sighandler_t)3) diff --git a/kernel/signal.c b/kernel/signal.c index 9c846a017201..1ed392df55fb 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -3755,6 +3755,15 @@ static inline void prepare_kill_siginfo(int sig, struct kernel_siginfo *info) info->si_uid = from_kuid_munged(current_user_ns(), current_uid()); } +int do_sys_kill(pid_t pid, int sig) +{ + struct kernel_siginfo info; + + prepare_kill_siginfo(sig, &info); + + return kill_something_info(sig, &info, pid); +} + /** * sys_kill - send a signal to a process * @pid: the PID of the process @@ -3762,11 +3771,7 @@ static inline void prepare_kill_siginfo(int sig, struct kernel_siginfo *info) */ SYSCALL_DEFINE2(kill, pid_t, pid, int, sig) { - struct kernel_siginfo info; - - prepare_kill_siginfo(sig, &info); - - return kill_something_info(sig, &info, pid); + return do_sys_kill(pid, sig); } /* diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 083be6af29d7..0d7e1d16b75b 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -139,6 +139,15 @@ static int minolduid; static int ngroups_max = NGROUPS_MAX; static const int cap_last_cap = CAP_LAST_CAP; +#ifdef CONFIG_SIGNALS_DEBUG +static int max_signal = _NSIG; +static int sigrtmax = SIGRTMAX; +static int sigset_size = sizeof(sigset_t); +# ifdef CONFIG_COMPAT +static int compat_sigset_size = sizeof(compat_sigset_t); +# endif +#endif + /* * This is needed for proc_doulongvec_minmax of sysctl_hung_task_timeout_secs * and hung_task_check_interval_secs @@ -2717,6 +2726,38 @@ static struct ctl_table kern_table[] = { .extra1 = SYSCTL_ZERO, .extra2 = SYSCTL_ONE, }, +#endif +#ifdef CONFIG_SIGNALS_DEBUG + { + .procname = "max_signal", + .data = &max_signal, + .maxlen = sizeof(int), + .mode = 0444, + .proc_handler = proc_dointvec, + }, + { + .procname = "sigrtmax", + .data = &sigrtmax, + .maxlen = sizeof(int), + .mode = 0444, + .proc_handler = proc_dointvec, + }, + { + .procname = "sigset_size", + .data = &sigset_size, + .maxlen = sizeof(int), + .mode = 0444, + .proc_handler = proc_dointvec, + }, +# ifdef CONFIG_COMPAT + { + .procname = "compat_sigset_size", + .data = &compat_sigset_size, + .maxlen = sizeof(int), + .mode = 0444, + .proc_handler = proc_dointvec, + }, +# endif #endif { } }; diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 2a9b6dcdac4f..c433356c1070 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2639,4 +2639,14 @@ endmenu # "Kernel Testing and Coverage" source "Documentation/Kconfig" +config SIGNALS_DEBUG + bool "Enable basic signals debugging" + default n + help + Provides several files in /proc to aid in debugging changes to + the signals code: /proc/sys/kernel/max_signal, + /proc/sys/kernel/sigrtmax and /proc/sys/kernel/sigset_size. + Also adds /proc/<pid>/signal to allow sending a signal number + to <pid> without going through libc. + endmenu # Kernel hacking -- 2.30.2