12

I've found that on Linux, by making my own call to the rt_sigqueue syscall, I can put whatever I like in the si_uid and si_pid fields and the call succeeds and happily delivers the incorrect values. Naturally the uid restrictions on sending signals provide some protection against this kind of spoofing, but I'm worried it may be dangerous to rely on this information. Is there any good documentation on the topic I could read? Why does Linux allow the obviously-incorrect behavior of letting the caller specify the siginfo parameters rather than generating them in kernelspace? It seems nonsensical, especially since extra sys calls (and thus performance cost) may be required in order to get the uid/gid in userspace.

Edit: Based on my reading of POSIX (emphasis added by me):

If si_code is SI_USER or SI_QUEUE, [XSI] or any value less than or equal to 0, then the signal was generated by a process and si_pid and si_uid shall be set to the process ID and the real user ID of the sender, respectively.

I believe this behavior by Linux is non-conformant and a serious bug.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Looks like you stumped SO. Maybe you should try the Linux kernel mailing list: http://www.kernel.org/pub/linux/docs/lkml/ – Tim Sylvester Mar 26 '11 at 06:03

2 Answers2

5

That section of the POSIX page you quote also lists what si-code means, and here's the meaning:

SI_QUEUE
    The signal was sent by the sigqueue() function.

That section goes on to say:

If the signal was not generated by one of the functions or events listed above, si_code shall be set either to one of the signal-specific values described in XBD , or to an implementation-defined value that is not equal to any of the values defined above.

Nothing is violated if only the sigqueue() function uses SI_QUEUE. Your scenario involves code other than the sigqueue() function using SI_QUEUE The question is whether POSIX envisions an operating system enforcing that only a specified library function (as opposed to some function which is not a POSIX-defined library function) be permitted to make a system call with certain characteristics. I believe the answer is "no".

EDIT as of 2011-03-26, 14:00 PST:

This edit is in response to R..'s comment from eight hours ago, since the page wouldn't let me leave an adequately voluminous comment:

I think you're basically right. But either a system is POSIX compliant or it is not. If a non-library function does a syscall which results in a non-compliant combination of uid, pid, and 'si_code', then the second statement I quoted makes it clear that the call itself is not compliant. One can interpret this in two ways. One ways is: "If a user breaks this rule, then he makes the system non-compliant." But you're right, I think that's silly. What good is a system when any nonprivileged user can make it noncompliant? The fix, as I see it, is somehow to have the system know that it's not the library 'sigqueue()' making the system call, then the kernel itself should set 'si_code' to something other than 'SI_QUEUE', and leave the uid and pid as you set them. In my opinion, you should raise this with the kernel folks. They may have difficulty, however; I don't know of any secure way for them to detect whether a syscall is made by a particular library function, seeing as how the library functions. almost by definition, are merely convenience wrappers around the syscalls. And that may be the position they take, which I know will be a disappointment.

(voluminous) EDIT as of 2011-03-26, 18:00 PST:

Again because of limitations on comment length.

This is in response to R..'s comment of about an hour ago.

I'm a little new to the syscall subject, so please bear with me.

By "the kernel sysqueue syscall", do you mean the `__NR_rt_sigqueueinfo' call? That's the only one that I found when I did this:

grep -Ri 'NR.*queue' /usr/include

If that's the case, I think I'm not understanding your original point. The kernel will let (non-root) me use SI-QUEUE with a faked pid and uid without error. If I have the sending side coded thus:

#include <sys/syscall.h>
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int    argc,
         char **argv
        )
{
  long john_silver;

  siginfo_t my_siginfo;

  if(argc!=2)
  {
    fprintf(stderr,"missing pid argument\n");

    exit(1);
  }

  john_silver=strtol(argv[1],NULL,0);

  if(kill(john_silver,SIGUSR1))
  {
    fprintf(stderr,"kill() fail\n");

    exit(1);
  }

  sleep(1);

  my_siginfo.si_signo=SIGUSR1;
  my_siginfo.si_code=SI_QUEUE;
  my_siginfo.si_pid=getpid();
  my_siginfo.si_uid=getuid();
  my_siginfo.si_value.sival_int=41;

  if(syscall(__NR_rt_sigqueueinfo,john_silver,SIGUSR1,&my_siginfo))
  {
    perror("syscall()");

    exit(1);
  }

  sleep(1);

  my_siginfo.si_signo=SIGUSR2;
  my_siginfo.si_code=SI_QUEUE;
  my_siginfo.si_pid=getpid()+1;
  my_siginfo.si_uid=getuid()+1;
  my_siginfo.si_value.sival_int=42;

  if(syscall(__NR_rt_sigqueueinfo,john_silver,SIGUSR2,&my_siginfo))
  {
    perror("syscall()");

    exit(1);
  }

  return 0;

} /* main() */

and the receiving side coded thus:

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int signaled_flag=0;

siginfo_t received_information;

void
my_handler(int        signal_number,
           siginfo_t *signal_information,
           void      *we_ignore_this
          )
{
  memmove(&received_information,
          signal_information,
          sizeof(received_information)
         );

  signaled_flag=1;

} /* my_handler() */

/*--------------------------------------------------------------------------*/

int
main(void)
{
  pid_t            myself;

  struct sigaction the_action;

  myself=getpid();

  printf("signal receiver is process %d\n",myself);

  the_action.sa_sigaction=my_handler;
  sigemptyset(&the_action.sa_mask);
  the_action.sa_flags=SA_SIGINFO;

  if(sigaction(SIGUSR1,&the_action,NULL))
  {
    fprintf(stderr,"sigaction(SIGUSR1) fail\n");

    exit(1);
  }

  if(sigaction(SIGUSR2,&the_action,NULL))
  {
    fprintf(stderr,"sigaction(SIGUSR2) fail\n");

    exit(1);
  }

  for(;;)
  {
    while(!signaled_flag)
    {
      sleep(1);
    }

    printf("si_signo: %d\n",received_information.si_signo);
    printf("si_pid  : %d\n",received_information.si_pid  );
    printf("si_uid  : %d\n",received_information.si_uid  );

    if(received_information.si_signo==SIGUSR2)
    {
      break;
    }

    signaled_flag=0;
  }

  return 0;

} /* main() */

I can then run (non-root) the receiving side thus:

wally:~/tmp/20110326$ receive
signal receiver is process 9023
si_signo: 10
si_pid  : 9055
si_uid  : 4000
si_signo: 10
si_pid  : 9055
si_uid  : 4000
si_signo: 12
si_pid  : 9056
si_uid  : 4001
wally:~/tmp/20110326$ 

And see this (non-root) on the send end:

wally:~/tmp/20110326$ send 9023
wally:~/tmp/20110326$ 

As you can see, the third event has spoofed pid and uid. Isn't that what you originally objected to? There's no EINVAL or EPERM in sight. I guess I'm confused.

Bill Evans at Mariposa
  • 3,590
  • 1
  • 18
  • 22
  • I don't see how this contradicts the paragraph I cited and its requirements that the uid and pid be valid when `si_code` takes on one of the set of values indicating a process-generated signal. I interpret the "shall" in the text I cited as indicating that the application can rely on the constraint always being met when a signal handler is invoked, regardless of the interface used to send the signal and whether it is standard or not. – R.. GitHub STOP HELPING ICE Mar 26 '11 at 12:23
  • I couldn't leave my whole comment here; the page said I didn't have enough characters. Please see the edit of my answer above. – Bill Evans at Mariposa Mar 26 '11 at 21:02
  • I think the matter of whether it's conformant depends on whether the uid/pid being valid whenever `si_code` is `SI_QUEUE` or one of the other special values is a requirement of the system's signal *delivery* mechanism or a requirement on the `sigqueue` function. As far as I can tell, it's the former not the latter. In any case I'm rather confused why the kernel is leaving it to the application (and trusting it) to fill in what should be privilege-restricted fields... – R.. GitHub STOP HELPING ICE Mar 26 '11 at 23:12
  • By the way, note that the kernel `sigqueue` syscall does forbid the application from spoofing kernel-delivered signals (positive `si_code`, I believe). It fails with either `EINVAL` or `EPERM` (I forget which) in this case... – R.. GitHub STOP HELPING ICE Mar 26 '11 at 23:14
  • Again please see my voluminous response, at 18:00 PST. – Bill Evans at Mariposa Mar 27 '11 at 01:29
  • It's reserved `si_code` values that the kernel prevents you from sending. But it happily accepts fake `si_uid` and `si_pid` values. That's what I don't get... – R.. GitHub STOP HELPING ICE Mar 27 '11 at 04:14
  • *slaps forehead* Forgot about that. But I went back to the source for my "receive" program listed in the second voluminous edit, and printed out received_information.si_code. It's -1, which is the value of 'SI_QUEUE', and that works fine. I also added to the send code another event, in which I set 'si_code' to 0. That one failed: operation not permitted. Based on your original question, I'd say that the Linux kernel has its heart in the right place, but needs a little tweaking. Can you show this to the kernel guys? – Bill Evans at Mariposa Mar 27 '11 at 09:05
0

I agree that si_uid and si_pid should be trustworthy, and if they are not it is a bug. However, this is only required if the signal is SIGCHLD generated by a state change of a child process, or if si_code is SI_USER or SI_QUEUE, or if the system supports the XSI option and si_code <= 0. Linux/glibc also pass si_uid and si_pid values in other cases; these are often not trustworthy but that is not a POSIX conformance issue.

Of course, for kill() the signal may not be queued in which case the siginfo_t does not provide any additional information.

The reason that rt_sigqueueinfo allows more than just SI_QUEUE is probably to allow implementing POSIX asynchronous I/O, message queues and per-process timers with minimal kernel support. Implementing these in userland requires the ability to send a signal with SI_ASYNCIO, SI_MESGQ and SI_TIMER respectively. I do not know how glibc allocates the resources to queue the signal beforehand; to me it looks like it does not and simply hopes rt_sigqueueinfo does not fail. POSIX clearly forbids discarding a timer expiration (async I/O completion, message arrival on a message queue) notification because too many signals are queued at the time of the expiration; the implementation should have rejected the creation or registration if there were insufficient resources. The objects have been defined carefully such that each I/O request, message queue or timer can have at most one signal in flight at a time.

jilles
  • 10,509
  • 2
  • 26
  • 39
  • But `rt_sigqueueinfo` does enforce `si_code<=0`, in which case the uid and pid are required to be valid, right? – R.. GitHub STOP HELPING ICE May 08 '11 at 14:12
  • Yes, if `si_code<=0` and the system supports the XSI option, uid and pid must be valid. However, there is no requirement that codes such as `SI_QUEUE` or `SI_TIMER` be negative or zero. Adding to the confusion, FreeBSD does not support this part of the XSI option: it sets `si_code=0` (aka `SI_NOINFO`) if no additional information is available; uid and pid are not valid in this case. – jilles May 08 '11 at 14:56
  • But on Linux these values are negative. – R.. GitHub STOP HELPING ICE May 09 '11 at 03:41