Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

compat: Fix RT signal mask corruption via sigprocmask

compat_sys_sigprocmask reads a smaller signal mask from userspace than
sigprogmask accepts for setting. So the high word of blocked.sig[0]
will be cleared, releasing any potentially blocked RT signal.

This was discovered via userspace code that relies on get/setcontext.
glibc's i386 versions of those functions use sigprogmask instead of
rt_sigprogmask to save/restore signal mask and caused RT signal
unblocking this way.

As suggested by Linus, this replaces the sys_sigprocmask based compat
version with one that open-codes the required logic, including the merge
of the existing blocked set with the new one provided on SIG_SETMASK.

Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Jan Kiszka and committed by
Linus Torvalds
b7dafa0e 7ee94d97

+46 -17
+46 -17
kernel/compat.c
··· 372 372 373 373 #ifdef __ARCH_WANT_SYS_SIGPROCMASK 374 374 375 - asmlinkage long compat_sys_sigprocmask(int how, compat_old_sigset_t __user *set, 376 - compat_old_sigset_t __user *oset) 375 + /* 376 + * sys_sigprocmask SIG_SETMASK sets the first (compat) word of the 377 + * blocked set of signals to the supplied signal set 378 + */ 379 + static inline void compat_sig_setmask(sigset_t *blocked, compat_sigset_word set) 377 380 { 378 - old_sigset_t s; 379 - long ret; 380 - mm_segment_t old_fs; 381 + memcpy(blocked->sig, &set, sizeof(set)); 382 + } 381 383 382 - if (set && get_user(s, set)) 383 - return -EFAULT; 384 - old_fs = get_fs(); 385 - set_fs(KERNEL_DS); 386 - ret = sys_sigprocmask(how, 387 - set ? (old_sigset_t __user *) &s : NULL, 388 - oset ? (old_sigset_t __user *) &s : NULL); 389 - set_fs(old_fs); 390 - if (ret == 0) 391 - if (oset) 392 - ret = put_user(s, oset); 393 - return ret; 384 + asmlinkage long compat_sys_sigprocmask(int how, 385 + compat_old_sigset_t __user *nset, 386 + compat_old_sigset_t __user *oset) 387 + { 388 + old_sigset_t old_set, new_set; 389 + sigset_t new_blocked; 390 + 391 + old_set = current->blocked.sig[0]; 392 + 393 + if (nset) { 394 + if (get_user(new_set, nset)) 395 + return -EFAULT; 396 + new_set &= ~(sigmask(SIGKILL) | sigmask(SIGSTOP)); 397 + 398 + new_blocked = current->blocked; 399 + 400 + switch (how) { 401 + case SIG_BLOCK: 402 + sigaddsetmask(&new_blocked, new_set); 403 + break; 404 + case SIG_UNBLOCK: 405 + sigdelsetmask(&new_blocked, new_set); 406 + break; 407 + case SIG_SETMASK: 408 + compat_sig_setmask(&new_blocked, new_set); 409 + break; 410 + default: 411 + return -EINVAL; 412 + } 413 + 414 + set_current_blocked(&new_blocked); 415 + } 416 + 417 + if (oset) { 418 + if (put_user(old_set, oset)) 419 + return -EFAULT; 420 + } 421 + 422 + return 0; 394 423 } 395 424 396 425 #endif