[LINUX] signal Know the sender

Introduction

Signal is often used for asynchronous communication between processes, I need to know the sender and I tried to find out how to do it, so I will leave it as a memorandum.

The environment I tried is as follows.

$ lsb_release -d
Description:	Ubuntu 18.04.4 LTS
$ uname -r
5.3.0-61-generic
$ trace-cmd --version | grep version
trace-cmd version 2.6.1
$ stap --version | head -1
Systemtap translator/driver (version 4.3/0.170, commit release-4.3-0-gc9c23c987d81)

Method 1. Know by sigaction (2)

This is an approach to get the source information in the process of receiving the signal.

You can change the process of receiving the signal, And it is a method that can be used when the signal that can be handled in the process is the subject of investigation. It cannot be used for signals that cannot be handled by the process, such as SIGKILL.

sigaction (2) can be done by specifying SA_SIGINFO in sa_flags. The signal handler will be able to receive detailed information about signal siginfo_t. You can know the source PID with si_pid and the real user ID of the source with si_uid.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

static int g_sig;
static siginfo_t g_siginfo;

static void
my_sigaction (int sig, siginfo_t *info, void *ucontext)
{
  g_sig = sig;
  // memcpy(3) is a async-signal-safe function
  // according to man signal-safety(7)
  memcpy (&g_siginfo, info, sizeof(g_siginfo));
}

static char*
code2str (int code)
{
  switch (code)
    {
    case SI_USER: return "SI_USER";
    case SI_KERNEL: return "SI_KERNEL";
    case SI_QUEUE: return "SI_QUEUE";
    case SI_TIMER: return "SI_TIMER";
    case SI_MESGQ: return "SI_MESGQ";
    case SI_ASYNCIO: return "SI_ASYNCIO";
    case SI_SIGIO: return "SI_SIGIO";
    case SI_TKILL: return "SI_TKILL";
    default: return "unknown";
    }
}

int
main (void)
{
  struct sigaction act;
  act.sa_flags = SA_SIGINFO;
  act.sa_sigaction = my_sigaction;
  int ret = sigaction(SIGTERM, &act, NULL);
  if (ret < 0)
    {
      perror ("sigaction");
      exit (EXIT_FAILURE);
    }
  printf ("pid: %d\n", getpid());

  sleep (10000);

  fprintf (stderr, "sig: %d, si_pid: %d, si_uid: %d, si_code: %s\n",
           g_sig,
           g_siginfo.si_pid,
           g_siginfo.si_uid,
           code2str(g_siginfo.si_code));

  return 0;
}

Method 2. Know with trace-cmd (= ftrace)

Since the kernel knows who sent the signal to whom, it is an approach to look up by tracing the kernel's events. Use ftrace for kernel event tracing.

You can identify the source by tracing the signal_generate event with ftrace. You can operate ftrace directly from debugfs, but here we will show you how to trace using trace-cmd.

[Start tracing]
$ sudo trace-cmd start -e signal_generate -f 'sig==9 && pid == 6985'

[End of trace]
$ sudo trace-cmd stop

[Check the trace result]
$ sudo trace-cmd show
# tracer: nop
#
# entries-in-buffer/entries-written: 1/1   #P:8
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
     test_sender-6993  [001] d...  1660.447368: signal_generate: sig=9 errno=0 code=0 comm=test_receiver pid=6985 grp=1 res=0

You can see that the process with PID test_sender, test_sender, sent sig = 9 to the process with pid = 6985.

There are a lot of signal_generate events here, so it's better to use the -f option and filter appropriately as described above. In the above, we are filtering by the signal number and the pid that receives the signal.

You can check the field names that can be used in the filter function with the trace-cmd list command.

$ trace-cmd list -F -e signal_generate
system: signal
name: signal_generate
ID: 194
format:
	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
	field:int common_pid;	offset:4;	size:4;	signed:1;

	field:int sig;	offset:8;	size:4;	signed:1;
	field:int errno;	offset:12;	size:4;	signed:1;
	field:int code;	offset:16;	size:4;	signed:1;
	field:char comm[16];	offset:20;	size:16;	signed:1;
	field:pid_t pid;	offset:36;	size:4;	signed:1;
	field:int group;	offset:40;	size:4;	signed:1;
	field:int result;	offset:44;	size:4;	signed:1;

Method 3. Know with system tap

Similar to method 2, this is an approach to check by tracing kernel events. Use systemtap for kernel event tracing.

You can monitor with one liner as shown below.

$ stap -e 'probe signal.send { if (sig==9 && sig_pid==8722) printf("%s: %s(%d) -> %s(%d)\n", sig_name, execname(), pid(), pid_name, sig_pid) }'
SIGKILL: test_sender(9191) -> test_receiver(8722)

If you make a script and take arguments in addition, it will be as follows. (It seems that qiita does not have system tap mode, so I am using c language mode)

signal_sender.stp


#!/usr/bin/env stap
probe signal.send
{
  if (sig==strtol(@1,10) && sig_pid==strtol(@2,10))
    printf("%s: %s(%d) -> %s(%d)\n", sig_name, execname(), pid(), pid_name, sig_pid)
}

systemtap can be smart like this on a PC, The disadvantage of embedded systems is that the compile environment and execution environment are separate, which makes the procedure a little complicated.

Summary

I think there are others, but I have introduced three options. One of the ways I looked up was to use SELinux's audit.log (see here if you're interested). Personally, I think ftrace (trace-cmd) has a good balance between ease of use and environmental hurdles. I'm a little exhausted, but I think I can do it with eBPF, so I'd like to add the eBPF version soon.

reference

Man page of SIGACTION trace-cmd(1) - Linux man page stap(1): systemtap script translator/driver - Linux man page How to use the trace-cmd command --Qiita How to track the source of SIGKILL to a process-Red Hat Customer Portal (https://access.redhat.com/en/solutions/385753) takeoverjp/test_signal: signal simple test program

Recommended Posts

signal Know the sender
I don't know the value error
I didn't know the basics of Python