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/