PROBLEM: Asynchronous I/O with realtime signals erroneously sends SIGIO

Alessandro Sala (a.sala@mclink.it)
Mon, 06 Dec 1999 23:06:16 +0100


Hi,
I think I found two bugs in the handling of real time signals in
the kernel. Here is the bug report in the suggested format.

Please forgive me for my English and any technical inaccuracy, since
I am not kernel hacker and I could have missed some important point.

Please CC to me any comments, as I'm not subscribed to the list.

Thank you,
Alessandro Sala

--

[1.] One line summary of the problem: Asynchronous I/O with realtime signals erroneously sends SIGIO

[2.] Full description of the problem/report: I think I found two bugs in the handling of real time signals with siginfo_t data if they are sent from asynchronous I/O on an interrupt-driven device, i.e. kill_fasync() after changing the default SIGIO signal using fcntl(fd, F_SETSIG, <realtime signal>)

Bug #1 (the less serious one): In the siginfo_t data sent with the real time signal, the si_uid field always has the same value as the si_fd field. Digging in the kernel sources I found that kill_fasync() calls send_sigio() (both inside fs/fcntl.c) which, after a permissions check, builds the siginfo_t data and calls send_sig_info() (kernel/signal.c) to actually queue the signal:

siginfo_t si; default: si.si_signo = fown->signum; si.si_errno = 0; si.si_code = SI_SIGIO; si.si_pid = pid; >>> si.si_uid = uid; >>> si.si_fd = fa->fa_fd; if (!send_sig_info(fown->signum, &si, p)) break;

/* fall-through: fall back on the old plain SIGIO signal */ case 0: send_sig(SIGIO, p, 1);

Looking at the siginfo_t definition in <asm-i386/siginfo.h> you can see that si_uid and si_fd actually overlap:

typedef struct siginfo { int si_signo; int si_errno; int si_code;

union { int _pad[SI_PAD_SIZE];

/* kill() */ struct { pid_t _pid; /* sender's pid */ >>> uid_t _uid; /* sender's uid */ } _kill;

...

/* SIGPOLL */ struct { int _band; /* POLL_IN, POLL_OUT, POLL_MSG */ >>> int _fd; } _sigpoll; } _sifields; } siginfo_t;

# define si_uid _sifields._kill._uid ... # define si_fd _sifields._sigpoll._fd

Perhaps, for a SI_SIGIO si_code, si_pid and si_uid shouldn't be set at all, since the signal isn't really generated by a process.

But even if SI_SIGIO is not defined in "The Single Unix specification, version 2", they say that if the si_code field is less than or equal to 0 then si_pid and si_uid indicate the process ID and the real user ID of the sender, and since SI_SIGIO is -5 it seems that si_uid and si_pid should be set after all. If so, the siginfo_t definition needs some changes (e.g. the addition of the fields _pid and _uid in the _sigpoll struct to align it with the _kill struct).

Another solution would be to change the value of SI_SIGIO (and perhaps SI_ASYNCIO, SI_MESGQ e SI_TIMER too: see bug #2) to something positive and don't bother about si_pid and si_uid.

Besides, regarding si_uid, there is another problem: it seems that its type is different in kernel space vs. user space, at least on the x86 architecture.

In fact the uid_t type used above maps to __kernel_uid_t (see <linux/types.h>), which is defined in <asm-i386/posix_types.h> as unsigned short (16 bits). On the other hand the definition of siginfo_t in <bits/siginfo.h> uses the __uid_t type (the same as uid_t in various other headers) which is defined as __u_int -> unsigned int (32 bits) in <bits/types.h>.

So, if kernel code assigns si_uid in siginfo_t, user space code retrieves garbage from the 3rd and 4th byte of the si_uid field.

Bug #2 (the more serious one): When send_sig_info() is called from asynchronous code (again kill_fasync() or some other kernel code within an interrupt or timer handler) it sometimes fails with the EPERM error and the user process gets a SIGIO without info instead of the signal it requested: this happens if the signal being sent comes with attached siginfo_t data and the si_code field is less than or equal to 0 (SI_SIGIO for example): in this case send_sig_info() checks the permissions of the current process against the target process:

/* The somewhat baroque permissions check... */ ret = -EPERM; if ((!info || ((unsigned long)info != 1 && SI_FROMUSER(info))) && ((sig != SIGCONT) || (current->session != t->session)) && (current->euid ^ t->suid) && (current->euid ^ t->uid) && (current->uid ^ t->suid) && (current->uid ^ t->uid) && !capable(CAP_SYS_ADMIN)) goto out_nolock;

but when called asynchronously, the current process has generally nothing to do with the sender of the signal (the device driver in the case of SI_SIGIO), so if 'current' is owned by the same user as the target o by the superuser the signal gets delivered, otherwise it is rejected. If send_sig_info() was called from send_sigio() (see bug #1) then the target gets a SIGIO instead of the requested signal, as if memory was exhausted or the maximum number of queued signals was reached.

I think the problem is related to the definition of SI_SIGIO, SI_ASYNCIO, SI_MESGQ and SI_TIMER: being negative numbers they pass the SI_FROMUSER() check, defined in <asm-i386/siginfo.h> as:

#define SI_FROMUSER(siptr) ((siptr)->si_code <= 0)

but, as I suggested above, the sender of the signal for these codes isn't a user process but a kernel subsystem, so I think it shouldn't undergo a permission check (in fact, for SI_SIGIO the permission check has already been performed by send_sigio()).

The simplest solution (and the one I'm using at the moment) is to modify the SI_FROMUSER macro so that it catches only SI_QUEUE and SI_USER:

#define SI_FROMUSER(siptr) ((siptr)->si_code <= 0 && \ (siptr)->si_code >= SI_QUEUE)

The drawback is that a user process can forge a siginfo_t with si_code < SI_QUEUE and call sys_rt_sigqueueinfo() (kernel/signal.c), thus sending a signal to another process even if it doesn't have the rights to do that.

But this, I think, is a security hole in sys_rt_sigqueueinfo(): even without changing SI_FROMUSER(), if for example some sort of server is using the fields si_uid and si_pid for authenticating the senders of the signals, a user process with permissions to signal the server could impersonate any other user/process by appropriately filling the si_uid and si_pid fields.

So I think that sys_rt_sigqueueinfo() should set si_uid and si_pid overriding the user provided values, and should check the permissions by itself, before calling kill_proc_info().

I think the best solution, as I suggested above, is to change SI_SIGIO, SI_ASYNCIO, SI_MESGQ and SI_TIMER to positive values: in this way the they would be interpreted as kernel-generated signals, so sys_rt_sigqueueinfo() would discard them with EPERM, and send_sig_info() wouldn't do a permission check, thus allowing them to be sent asynchronously. However, to avoid pid/uid forging, sys_rt_sigqueueinfo() should in any case set si_pid and si_uid by itself.

But I don't know if this renumbering can break something else in the kernel or, worse, in user libraries/applications.

[3.] Keywords: kernel, signals, siginfo_t, realtime, asynchronous I/O

[4.] Kernel version: Linux version 2.2.12-20 (root@localhost) (gcc version egcs-2.91.66 19990314 /Linux (egcs-1.1.2 release)) #2 dom ott 31 13:02:05 CET 1999

[5.] Output of Oops.. message: No Oops applicable

[6.] A small shell script or example program which triggers the problem: The following program demonstrates the two bugs described: just run it as user A (he must have read permissions for /dev/mouse or for the other source of async input) while there are several other processes running as user B (B different from root!) and move the mouse at will: you can immediately see that the uid field is the same as the file descriptor; as soon as the program gets a SIGIO it stops, showing the amount of free memory and the number of queued signals, to verify that neither memory is low nor signals have overflowed causing a SIGIO instead of SIGRTMIN. It works with PS/2 mice. It should work with serial mice too.

--- siginfo_bug.cc --8<-----------------8<----------------8<---------

// // siginfo_bug.cc -- Demonstrates send_sigio() and send_sig_info() bug // // Compiled with "g++ siginfo_bug.cc -o siginfo_bug" // // Alessandro Sala (a.sala@mclink.it) //

#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #define __USE_GNU #include <fcntl.h> #include <string.h> #include <asm/posix_types.h>

void main(int argc, char **argv) { siginfo_t si; int fd, sig = SIGRTMIN; char *dev;

if (argc > 1) dev = argv[1]; else dev = "/dev/mouse";

// Open the source of asynchronous data if ((fd = open(dev,O_RDONLY)) == -1) { perror("open"); exit(1); }

// Request asynchronous I/O int oflags;

fcntl(fd,F_SETOWN, getpid()); oflags = fcntl(fd,F_GETFL); fcntl(fd,F_SETFL, oflags | O_ASYNC); fcntl(fd,F_SETSIG, sig);

// Block the real time signal and SIGIO sigset_t set;

sigemptyset(&set); sigaddset(&set,sig); sigaddset(&set,SIGIO); sigprocmask(SIG_BLOCK,&set,0);

printf("mypid : %ld\n",(long)getpid()); printf("rtsignal : %s (%d)\n",strsignal(sig),sig); printf("fd : %d\n",fd); printf("sizeof(uid_t)=%d,sizeof(si_uid)=%d,sizeof(__kernel_uid_t)=%d\n"); (int)sizeof(uid_t), (int)sizeof(si.si_uid), (int)sizeof(__kernel_uid_t)); printf("-----------------------------------------------------------\n");

int sigcnt = 0; while (1) { // Wait for a signal sigwaitinfo(&set,&si); printf("[%d] signal: %s (%d)\ncode %d", ++sigcnt, strsignal(si.si_signo),si.si_signo,si.si_code); if (si.si_code == SI_SIGIO) printf(", errno %d, pid %d, uid %d, fd %d", si.si_errno, si.si_pid, si.si_uid, si.si_fd); puts(""); if (si.si_signo == SIGIO) { // Got SIGIO instead of the real time signal printf("Got SIGIO: no memory, signal overflow or NO PERMISSIONS?\n"); system("echo \"freemem : \"; free"); system("echo -n \"rtsig queued: \"; cat /proc/sys/kernel/rtsig-nr"); system("echo -n \"rtsig max : \"; cat /proc/sys/kernel/rtsig-max"); pause(); break; } }

}

------>8------------->8----------------->8---------------->8---------

[7.1] Software: -- Versions installed: (if some fields are empty or looks -- unusual then possibly you have very old versions) Linux localhost 2.2.12-20 #2 dom ott 31 13:02:05 CET 1999 i586 unknown Kernel modules 2.1.121 Gnu C egcs-2.91.66 Binutils 2.9.1.0.24 Linux C Library 2.1.2 Dynamic linker ldd (GNU libc) 2.1.2 Procps 2.0.4 Mount 2.9u Net-tools 1.53 Console-tools 1999.03.02 Sh-utils 2.0 Sh-utils Parker. Sh-utils Sh-utils Inc. Sh-utils NO Sh-utils PURPOSE. Modules Loaded ds i82365 pcmcia_core opl3 sb uart401 sound soundlow soundcore

[7.2] Processor information: processor : 0 vendor_id : GenuineIntel cpu family : 5 model : 4 model name : Pentium MMX stepping : 3 cpu MHz : 166.093942 fdiv_bug : no hlt_bug : no sep_bug : no f00f_bug : yes coma_bug : no fpu : yes fpu_exception : yes cpuid level : 1 wp : yes flags : fpu vme de pse tsc msr mce cx8 mmx bogomips : 330.96

[7.3] Module information: ds 5740 4 i82365 22640 4 pcmcia_core 39912 0 [ds i82365] opl3 11208 0 sb 33620 0 uart401 5968 0 [sb] sound 57240 0 [opl3 sb uart401] soundlow 300 0 [sound] soundcore 2372 6 [sb sound]

[7.4] SCSI information: No scsi

[7.5] Other information that might be relevant to the problem: "cat /proc/sys/kernel/rtsig-max" gives 1024

[X.] Other notes, patches, fixes, workarounds: I described workarounds and possible solutions in section 2

-- 
+-----------------------------------+
|     /\                            |
|    //\\                           |
|   <<  >>                          |
|    >><<     Alessandro Sala       |
|   <<  >>    a.sala@mclink.it      |
|    ||||                           |
+-----------------------------------+

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/