blob: 9cc8f3fdc81ff8353eb946185e0ca184a428c10e [file] [log] [blame]
From: Florian Mayer <fmayer@google.com>
Subject: procfs: add sicode to /proc/<PID>/stat.
Date: Fri, 9 Sep 2022 11:06:17 -0700
In order to enable additional debugging features, Android init needs a way
to distinguish MTE-related SEGVs (with si_code of SEGV_MTEAERR) from other
SEGVs. This is not possible with current APIs, neither by the existing
information in /proc/<pid>/stat, nor via waitpid.
Tested with the following program
int main(int argc, char** argv) {
int pid = fork();
if (!pid) {
if (strcmp(argv[1], "sigqueue") == 0) {
union sigval value;
value.sival_int = 0;
sigqueue(getpid(), SIGSEGV, value);
} else if (strcmp(argv[1], "raise") == 0) {
raise(SIGSEGV);
} else if (strcmp(argv[1], "kill") == 0) {
kill(getpid(), SIGSEGV);
} else if (strcmp(argv[1], "raisestop") == 0) {
raise(SIGSTOP);
} else if (strcmp(argv[1], "crash") == 0) {
volatile int* x = (int*)(0x23);
*x = 1;
} else if (strcmp(argv[1], "mte") == 0) {
volatile char* y = malloc(1);
y += 100;
*y = 1;
}
} else {
printf("%d\n", pid);
sleep(5);
char buf[1024];
sprintf(buf, "/proc/%d/stat", pid);
int fd = open(buf, O_RDONLY);
char statb[1024];
read(fd, statb, sizeof(statb));
printf("%s\n", statb);
}
}
Link: https://lkml.kernel.org/r/20220909180617.374238-1-fmayer@google.com
Signed-off-by: Florian Mayer <fmayer@google.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Christian Brauner (Microsoft) <brauner@kernel.org>
Cc: "Eric W . Biederman" <ebiederm@xmission.com>
Cc: Evgenii Stepanov <eugenis@google.com>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Kees Cook <keescook@chromium.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Peter Collingbourne <pcc@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---
Documentation/filesystems/proc.rst | 2 +
fs/coredump.c | 17 +++++++++------
fs/proc/array.c | 12 +++++++----
include/linux/sched/signal.h | 1
include/linux/sched/task.h | 2 -
kernel/exit.c | 5 ++--
kernel/pid_namespace.c | 4 ++-
kernel/signal.c | 29 +++++++++++++++++----------
8 files changed, 47 insertions(+), 25 deletions(-)
--- a/Documentation/filesystems/proc.rst~add-sicode-to-proc-pid-stat
+++ a/Documentation/filesystems/proc.rst
@@ -381,6 +381,8 @@ It's slow but very precise.
env_end address below which program environment is placed
exit_code the thread's exit_code in the form reported by the waitpid
system call
+ exit_sicode if the process was stopped or terminated by a signal, the
+ signal's si_code. 0 otherwise
============= ===============================================================
The /proc/PID/maps file contains the currently mapped memory regions and
--- a/fs/coredump.c~add-sicode-to-proc-pid-stat
+++ a/fs/coredump.c
@@ -353,7 +353,7 @@ out:
return ispipe;
}
-static int zap_process(struct task_struct *start, int exit_code)
+static int zap_process(struct task_struct *start, int exit_code, int sicode)
{
struct task_struct *t;
int nr = 0;
@@ -361,6 +361,7 @@ static int zap_process(struct task_struc
/* ignore all signals except SIGKILL, see prepare_signal() */
start->signal->flags = SIGNAL_GROUP_EXIT;
start->signal->group_exit_code = exit_code;
+ start->signal->group_exit_sicode = sicode;
start->signal->group_stop_count = 0;
for_each_thread(start, t) {
@@ -375,8 +376,8 @@ static int zap_process(struct task_struc
return nr;
}
-static int zap_threads(struct task_struct *tsk,
- struct core_state *core_state, int exit_code)
+static int zap_threads(struct task_struct *tsk, struct core_state *core_state,
+ int exit_code, int sicode)
{
struct signal_struct *signal = tsk->signal;
int nr = -EAGAIN;
@@ -384,7 +385,7 @@ static int zap_threads(struct task_struc
spin_lock_irq(&tsk->sighand->siglock);
if (!(signal->flags & SIGNAL_GROUP_EXIT) && !signal->group_exec_task) {
signal->core_state = core_state;
- nr = zap_process(tsk, exit_code);
+ nr = zap_process(tsk, exit_code, sicode);
clear_tsk_thread_flag(tsk, TIF_SIGPENDING);
tsk->flags |= PF_DUMPCORE;
atomic_set(&core_state->nr_threads, nr);
@@ -393,7 +394,8 @@ static int zap_threads(struct task_struc
return nr;
}
-static int coredump_wait(int exit_code, struct core_state *core_state)
+static int coredump_wait(int exit_code, int sicode,
+ struct core_state *core_state)
{
struct task_struct *tsk = current;
int core_waiters = -EBUSY;
@@ -402,7 +404,7 @@ static int coredump_wait(int exit_code,
core_state->dumper.task = tsk;
core_state->dumper.next = NULL;
- core_waiters = zap_threads(tsk, core_state, exit_code);
+ core_waiters = zap_threads(tsk, core_state, exit_code, sicode);
if (core_waiters > 0) {
struct core_thread *ptr;
@@ -565,7 +567,8 @@ void do_coredump(const kernel_siginfo_t
need_suid_safe = true;
}
- retval = coredump_wait(siginfo->si_signo, &core_state);
+ retval =
+ coredump_wait(siginfo->si_signo, siginfo->si_code, &core_state);
if (retval < 0)
goto fail_creds;
--- a/fs/proc/array.c~add-sicode-to-proc-pid-stat
+++ a/fs/proc/array.c
@@ -474,6 +474,7 @@ static int do_task_stat(struct seq_file
unsigned long rsslim = 0;
unsigned long flags;
int exit_code = task->exit_code;
+ int exit_sicode = 0;
state = *get_task_state(task);
vsize = eip = esp = 0;
@@ -538,8 +539,10 @@ static int do_task_stat(struct seq_file
thread_group_cputime_adjusted(task, &utime, &stime);
gtime += sig->gtime;
- if (sig->flags & (SIGNAL_GROUP_EXIT | SIGNAL_STOP_STOPPED))
+ if (sig->flags & (SIGNAL_GROUP_EXIT | SIGNAL_STOP_STOPPED)) {
exit_code = sig->group_exit_code;
+ exit_sicode = sig->group_exit_sicode;
+ }
}
sid = task_session_nr_ns(task, ns);
@@ -638,10 +641,11 @@ static int do_task_stat(struct seq_file
} else
seq_puts(m, " 0 0 0 0 0 0 0");
- if (permitted)
+ if (permitted) {
seq_put_decimal_ll(m, " ", exit_code);
- else
- seq_puts(m, " 0");
+ seq_put_decimal_ll(m, " ", exit_sicode);
+ } else
+ seq_puts(m, " 0 0");
seq_putc(m, '\n');
if (mm)
--- a/include/linux/sched/signal.h~add-sicode-to-proc-pid-stat
+++ a/include/linux/sched/signal.h
@@ -109,6 +109,7 @@ struct signal_struct {
/* thread group exit support */
int group_exit_code;
+ int group_exit_sicode;
/* notify group_exec_task when notify_count is less or equal to 0 */
int notify_count;
struct task_struct *group_exec_task;
--- a/include/linux/sched/task.h~add-sicode-to-proc-pid-stat
+++ a/include/linux/sched/task.h
@@ -82,7 +82,7 @@ static inline void exit_thread(struct ta
{
}
#endif
-extern __noreturn void do_group_exit(int);
+extern __noreturn void do_group_exit(int,int);
extern void exit_files(struct task_struct *);
extern void exit_itimers(struct task_struct *);
--- a/kernel/exit.c~add-sicode-to-proc-pid-stat
+++ a/kernel/exit.c
@@ -904,7 +904,7 @@ SYSCALL_DEFINE1(exit, int, error_code)
* as well as by sys_exit_group (below).
*/
void __noreturn
-do_group_exit(int exit_code)
+do_group_exit(int exit_code, int sicode)
{
struct signal_struct *sig = current->signal;
@@ -923,6 +923,7 @@ do_group_exit(int exit_code)
exit_code = 0;
else {
sig->group_exit_code = exit_code;
+ sig->group_exit_sicode = sicode;
sig->flags = SIGNAL_GROUP_EXIT;
zap_other_threads(current);
}
@@ -940,7 +941,7 @@ do_group_exit(int exit_code)
*/
SYSCALL_DEFINE1(exit_group, int, error_code)
{
- do_group_exit((error_code & 0xff) << 8);
+ do_group_exit((error_code & 0xff) << 8, 0);
/* NOTREACHED */
return 0;
}
--- a/kernel/pid_namespace.c~add-sicode-to-proc-pid-stat
+++ a/kernel/pid_namespace.c
@@ -248,8 +248,10 @@ void zap_pid_ns_processes(struct pid_nam
}
__set_current_state(TASK_RUNNING);
- if (pid_ns->reboot)
+ if (pid_ns->reboot) {
current->signal->group_exit_code = pid_ns->reboot;
+ current->signal->group_exit_sicode = 0;
+ }
acct_exit_ns(pid_ns);
return;
--- a/kernel/signal.c~add-sicode-to-proc-pid-stat
+++ a/kernel/signal.c
@@ -963,6 +963,7 @@ static bool prepare_signal(int sig, stru
signal_set_stop_flags(signal, why | SIGNAL_STOP_CONTINUED);
signal->group_stop_count = 0;
signal->group_exit_code = 0;
+ signal->group_exit_sicode = 0;
}
}
@@ -994,7 +995,8 @@ static inline bool wants_signal(int sig,
return task_curr(p) || !task_sigpending(p);
}
-static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
+static void complete_signal(int sig, int code, struct task_struct *p,
+ enum pid_type type)
{
struct signal_struct *signal = p->signal;
struct task_struct *t;
@@ -1051,6 +1053,7 @@ static void complete_signal(int sig, str
*/
signal->flags = SIGNAL_GROUP_EXIT;
signal->group_exit_code = sig;
+ signal->group_exit_sicode = code;
signal->group_stop_count = 0;
t = p;
do {
@@ -1082,6 +1085,7 @@ static int __send_signal_locked(int sig,
struct sigqueue *q;
int override_rlimit;
int ret = 0, result;
+ int code = 0;
lockdep_assert_held(&t->sighand->siglock);
@@ -1129,7 +1133,7 @@ static int __send_signal_locked(int sig,
clear_siginfo(&q->info);
q->info.si_signo = sig;
q->info.si_errno = 0;
- q->info.si_code = SI_USER;
+ code = q->info.si_code = SI_USER;
q->info.si_pid = task_tgid_nr_ns(current,
task_active_pid_ns(t));
rcu_read_lock();
@@ -1142,12 +1146,13 @@ static int __send_signal_locked(int sig,
clear_siginfo(&q->info);
q->info.si_signo = sig;
q->info.si_errno = 0;
- q->info.si_code = SI_KERNEL;
+ code = q->info.si_code = SI_KERNEL;
q->info.si_pid = 0;
q->info.si_uid = 0;
break;
default:
copy_siginfo(&q->info, info);
+ code = info->si_code;
break;
}
} else if (!is_si_special(info) &&
@@ -1186,7 +1191,7 @@ out_set:
}
}
- complete_signal(sig, t, type);
+ complete_signal(sig, code, t, type);
ret:
trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
return ret;
@@ -1960,6 +1965,7 @@ void sigqueue_free(struct sigqueue *q)
int send_sigqueue(struct sigqueue *q, struct pid *pid, enum pid_type type)
{
int sig = q->info.si_signo;
+ int code = q->info.si_code;
struct sigpending *pending;
struct task_struct *t;
unsigned long flags;
@@ -1995,7 +2001,7 @@ int send_sigqueue(struct sigqueue *q, st
pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;
list_add_tail(&q->list, &pending->list);
sigaddset(&pending->signal, sig);
- complete_signal(sig, t, type);
+ complete_signal(sig, code, t, type);
result = TRACE_SIGNAL_DELIVERED;
out:
trace_signal_generate(sig, &q->info, t, type != PIDTYPE_PID, result);
@@ -2380,7 +2386,7 @@ int ptrace_notify(int exit_code, unsigne
* %false if group stop is already cancelled or ptrace trap is scheduled.
* %true if participated in group stop.
*/
-static bool do_signal_stop(int signr)
+static bool do_signal_stop(int signr, int sicode)
__releases(&current->sighand->siglock)
{
struct signal_struct *sig = current->signal;
@@ -2415,8 +2421,10 @@ static bool do_signal_stop(int signr)
* an intervening stop signal is required to cause two
* continued events regardless of ptrace.
*/
- if (!(sig->flags & SIGNAL_STOP_STOPPED))
+ if (!(sig->flags & SIGNAL_STOP_STOPPED)) {
sig->group_exit_code = signr;
+ sig->group_exit_sicode = sicode;
+ }
sig->group_stop_count = 0;
@@ -2701,7 +2709,7 @@ relock:
}
if (unlikely(current->jobctl & JOBCTL_STOP_PENDING) &&
- do_signal_stop(0))
+ do_signal_stop(0, 0))
goto relock;
if (unlikely(current->jobctl &
@@ -2806,7 +2814,8 @@ relock:
spin_lock_irq(&sighand->siglock);
}
- if (likely(do_signal_stop(ksig->info.si_signo))) {
+ if (likely(do_signal_stop(ksig->info.si_signo,
+ ksig->info.si_code))) {
/* It released the siglock. */
goto relock;
}
@@ -2854,7 +2863,7 @@ relock:
/*
* Death signals, no core dump.
*/
- do_group_exit(ksig->info.si_signo);
+ do_group_exit(ksig->info.si_signo, ksig->info.si_code);
/* NOTREACHED */
}
spin_unlock_irq(&sighand->siglock);
_