From: Miloš Stojanović <milos.stojano...@rt-rk.com> Add support for multiplexing in the host_signal_handler() function and in system calls rt_sigqueueinfo()/rt_tgsigqueueinfo(), tgkill(), kill() for the case when pid > 0.
The rt_sigqueueinfo()/rt_tgsigqueueinfo() system calls multiplex target signals which are out of the host range by setting the si_errno value to the actual value of the signal and sending the signal to the MUX_SIG signal number. The host_signal_handler() will pull out the multiplexed signals and set their signal number to the correct value. That value should be in the si_errno field of the siginfo_t structure. The si_errno field is used here but this implementation can be replaced with any other unused field in the uinfo structure. The emulation of larger target signal range is done by spoofing the system call info, adding the signal number to the si_errno field, and sending it to the host multiplex queue via rt_sigqueueinfo()/rt_tgsigqueueinfo(). In order to send a signal using rt_sigqueueinfo()/rt_tgsigqueueinfo() with si_code SI_USER or SI_TKILL to another thread or process, we need to disguise it as some other signal from the kernel range because the host kernel doesn't allow direct impersonations of those signals. This is done with SIG_SPOOF which moves the si_code to the nearest unused kernel si_code value. After the signal is successfully sent the host_signal_handler() of the receiving thread/process will turn it back into the proper kill/tgkill signal, before it gets processed. The tkill() system call as well as kill() with the argument pid <= 0 couldn't be implemented simply using this method because it requires acquiring information about, and sending simultaneous signals to multiple threads or processes. These functionalities are out of the scope of rt_sigqueueinfo()/rt_tgsigqueueinfo(). Signed-off-by: Miloš Stojanović <milos.stojano...@rt-rk.com> Signed-off-by: Aleksandar Markovic <amarko...@wavecomp.com> --- linux-user/signal.c | 22 +++++++++++++ linux-user/syscall.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++ linux-user/syscall_defs.h | 8 +++++ 3 files changed, 111 insertions(+) diff --git a/linux-user/signal.c b/linux-user/signal.c index 601de3e..885ceab 100644 --- a/linux-user/signal.c +++ b/linux-user/signal.c @@ -807,6 +807,28 @@ static void host_signal_handler(int host_signum, siginfo_t *info, sig = host_to_target_signal(host_signum); if (sig < 1 || sig > TARGET_NSIG) return; + +#ifdef MUX_SIG + if (sig == MUX_SIG) { + /* return the spoofed kill/tgkill signals into standard form */ + if (info->si_code == SIG_SPOOF(SI_USER)) { + info->si_code = SI_USER; + } else if (info->si_code == SIG_SPOOF(SI_TKILL)) { + info->si_code = SI_TKILL; + } + + /* + * We assume that si_errno field will remain intact during signal + * processing on the host. If it changes, the signal will be sent to + * the wrong number (most likely to MUX_SIG). + */ + /* get the actual target signal number */ + int target_sig = info->si_errno; + if (target_sig >= _NSIG && target_sig < TARGET_NSIG) { + sig = target_sig; + } + } +#endif trace_user_host_signal(env, host_signum, sig); rewind_if_in_safe_syscall(puc); diff --git a/linux-user/syscall.c b/linux-user/syscall.c index dede443..2e3c951 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -6448,6 +6448,24 @@ static inline abi_long host_to_target_stat64(void *cpu_env, } #endif +#ifdef MUX_SIG +static inline int multiplex(abi_long *arg, siginfo_t *uinfo) +{ + if (*arg >= _NSIG && *arg < TARGET_NSIG) { + /* + * Using si_errno to transfer the signal number assumes that the field + * doesn't change its value before it gets handled in the + * host_signal_handler(). + */ + uinfo->si_errno = *arg; + *arg = MUX_SIG; + uinfo->si_signo = MUX_SIG; + } + + return 0; +} +#endif + /* ??? Using host futex calls even when target atomic operations are not really atomic probably breaks things. However implementing futexes locally would make futexes shared between multiple processes @@ -7535,7 +7553,42 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, return get_errno(syncfs(arg1)); #endif case TARGET_NR_kill: +#ifdef MUX_SIG + if (arg2 >= _NSIG && arg2 < TARGET_NSIG) { + siginfo_t info; + + info.si_errno = arg2; + info.si_signo = MUX_SIG; + info.si_code = SIG_SPOOF(SI_USER); + info.si_pid = getpid(); + info.si_uid = getuid(); + + /* pid > 0 */ + if (arg1 > 0) { + return get_errno(sys_rt_sigqueueinfo(arg1, MUX_SIG, &info)); + } else { + return -TARGET_EINVAL; + } + /* + * TODO: In order to implement kill with rt_tgsigqueueinfo() for + * cases where pid <= 0 one needs to get a list of all the relevant + * processes and simultaniously send the signal to them. + * Missing: + * (pid = 0): + * send to every process in the process group of + * the calling process + * (pid = -1): + * send to every process for which the calling process + * has permission to send signals, except for process 1 (init) + * (pid < -1): + * send to every process in the process group whose ID is -pid + */ + } else { + return get_errno(safe_kill(arg1, target_to_host_signal(arg2))); + } +#else return get_errno(safe_kill(arg1, target_to_host_signal(arg2))); +#endif #ifdef TARGET_NR_rename case TARGET_NR_rename: { @@ -8208,6 +8261,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, } target_to_host_siginfo(&uinfo, p); unlock_user(p, arg3, 0); +#ifdef MUX_SIG + multiplex(&arg2, &uinfo); +#endif ret = get_errno(sys_rt_sigqueueinfo(arg1, arg2, &uinfo)); } return ret; @@ -8221,6 +8277,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, } target_to_host_siginfo(&uinfo, p); unlock_user(p, arg4, 0); +#ifdef MUX_SIG + multiplex(&arg3, &uinfo); +#endif ret = get_errno(sys_rt_tgsigqueueinfo(arg1, arg2, arg3, &uinfo)); } return ret; @@ -11037,11 +11096,33 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, #endif case TARGET_NR_tkill: + /* + * TODO: In order to implement tkill with rt_sigqueueinfo() one needs + * to get a list of all the threads with the specifiend tid and + * simultaniously send the signal to them. + */ return get_errno(safe_tkill((int)arg1, target_to_host_signal(arg2))); case TARGET_NR_tgkill: +#ifdef MUX_SIG + if (arg3 >= _NSIG && arg3 < TARGET_NSIG) { + siginfo_t info; + + info.si_errno = arg3; + info.si_signo = MUX_SIG; + info.si_code = SIG_SPOOF(SI_TKILL); + info.si_pid = getpid(); + info.si_uid = getuid(); + + return get_errno(sys_rt_tgsigqueueinfo(arg1, arg2, MUX_SIG, &info)); + } else { + return get_errno(safe_tgkill((int)arg1, (int)arg2, + target_to_host_signal(arg3))); + } +#else return get_errno(safe_tgkill((int)arg1, (int)arg2, target_to_host_signal(arg3))); +#endif #ifdef TARGET_NR_set_robust_list case TARGET_NR_set_robust_list: diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h index 0c72509..ca97f67 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h @@ -405,6 +405,14 @@ struct target_dirent64 { * the host signal masks. */ #define TRACK_TARGET_SIGMASK + +/* + * This macro is used to change a kill/tgkill signal so it can be sent through + * rt_sigqueueinfo()/rt_tgsigqueueinfo(), since the host kernel doesn't allow + * direct impersonations of those signals. Subtracting 8 from the code moves + * it to the nearest unused kernel si_code value. + */ +#define SIG_SPOOF(code) ((code) - 8) #endif typedef struct { -- 2.7.4