2022-01-03 18:34:57

by Walt Drummond

[permalink] [raw]
Subject: [RFC PATCH 7/8] signals: Add signal debugging

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 <[email protected]>
---
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