| 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(¤t->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); |
| _ |