siginfo中的数据值得信赖吗?

时间:2011-03-10 23:05:52

标签: c linux posix signals sigqueue

我发现在Linux上,通过自己调用rt_sigqueue系统调用,我可以在si_uidsi_pid字段中添加我喜欢的内容,并且调用成功并且愉快地提供不正确的值。当然,发送信号的uid限制提供了一些防止这种欺骗的保护,但我担心依赖这些信息可能是危险的。关于我能读到的主题,有没有好的文件?为什么Linux允许调用者指定siginfo参数而不是在内核空间中生成它们的明显不正确的行为?这似乎是荒谬的,特别是因为额外的系统 为了在用户空间中获取uid / gid,可能需要调用(以及性能成本)。

修改:根据我对POSIX的阅读(我强调):

  

如果si_code是SI_USER或SI_QUEUE,[XSI]或任何小于或等于0的值,则信号由进程生成,si_pid和si_uid 应设置为进程ID和真实用户ID发件人,分别为。

我认为Linux的这种行为不符合并且是一个严重的错误。

2 个答案:

答案 0 :(得分:5)

您引用的POSIX页面的那一部分还列出了si-code的含义,以及这里的含义:

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

该部分继续说:

  

如果信号不是由一个人产生的   列出的功能或事件   在上面,si_code也应设置   到一个特定于信号的值   在XBD中描述,或者在   实现定义的值   不等于任何定义的值   上方。

如果只有sigqueue()函数使用SI_QUEUE,则不会违反任何内容。您的方案涉及sigqueue()函数以外的代码使用SI_QUEUE问题是POSIX是否设想操作系统仅强制执行指定的库函数(而不是某些函数不是POSIX定义的库函数)允许进行具有某些特征的系统调用。我相信答案是“不”。

编辑截至2011-03-26,太平洋标准时间14:00:

此修改是为了回应八小时前的 R .. 的评论,因为该页面不会让我留下足够多的评论:

我认为你基本上是正确的。但是系统是POSIX兼容还是不兼容。如果非库函数执行系统调用导致uid,pid和'si_code'的不兼容组合,那么我引用的第二个语句清楚地表明调用本身不符合。人们可以用两种方式来解释这一点。一种方法是:“如果用户违反此规则,那么他会使系统不合规。”但你是对的,我认为这很愚蠢。当任何非特权用户可以使其不合规时,系统有什么用处?正如我所看到的那样,修复程序在某种程度上让系统知道它不是库'sigqueue()'进行系统调用,然后内核本身应该将'si_code'设置为'SI_QUEUE'以外的其他东西,并保留你设置它们的uid和pid。在我看来,你应该向内核人员提出这个问题。然而,他们可能有困难;我不知道他们有什么安全的方法来检测系统调用是否是由特定的库函数构成的,看看库的运行方式。几乎按定义,它们只是系统调用的便利包装器。这可能是他们采取的立场,我知道这将是一种失望。

(浩繁)EDIT截至2011-03-26,太平洋标准时间18:00:

再次因为评论长度的限制。

这是对大约一小时前 R .. 的评论的回应。

我对系统调用主题有点新意,所以请耐心等待。

通过“内核sysqueue系统调用”,你的意思是“__NR_rt_sigqueueinfo”调用吗?那是我在做这件事时唯一发现的那个:

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

如果是这样的话,我想我不理解你原来的观点。内核会让(非root)我使用伪造的pid和uid SI-QUEUE而没有错误。如果我这样编码发送方:

#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() */

并且接收方编码如此:

#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() */

然后我可以运行(非root)接收方:

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$ 

在发送端看到这个(非root):

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

正如你所看到的,第三个事件已经欺骗了pid和uid。这不是你最初反对的吗?视线中没有EINVALEPERM。我想我很困惑。

答案 1 :(得分:0)

我同意si_uidsi_pid应该值得信赖,如果不是,那就是错误。但是,仅当子进程的状态更改生成SIGCHLD信号,或者si_codeSI_USERSI_QUEUE,或者系统支持时,才需要执行此操作XSI选项和si_code <= 0。在其他情况下,Linux / glibc也会传递si_uidsi_pid值;这些通常不值得信赖,但这不是POSIX一致性问题。

当然,对于kill(),信号可能不会排队,在这种情况下,siginfo_t不会提供任何其他信息。

rt_sigqueueinfo允许的不仅仅是SI_QUEUE的原因可能是允许以最小的内核支持实现POSIX异步I / O,消息队列和每进程定时器。在userland中实现这些功能需要能够分别发送SI_ASYNCIOSI_MESGQSI_TIMER的信号。我不知道glibc如何预先分配资源来排队信号;对我而言,它似乎没有,只是希望rt_sigqueueinfo不会失败。 POSIX明确禁止丢弃计时器到期(异步I / O完成,消息到达消息队列)通知,因为在到期时有太多信号排队;如果资源不足,实施应该拒绝创建或注册。这些对象已经过仔细定义,因此每个I / O请求,消息队列或计时器一次最多只能有一个信号。