lkml.org 
[lkml]   [1999]   [Dec]   [6]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
Date
From
SubjectPROBLEM: Asynchronous I/O with realtime signals erroneously sends SIGIO

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/

\
 
 \ /
  Last update: 2005-03-22 13:55    [W:0.031 / U:2.908 seconds]
©2003-2018 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site