11

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

编辑:基于我对POSIX的阅读(重点由我添加):

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

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

4

2 回答 2

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 年 3 月 26 日,太平洋标准时间 14:00:

此编辑是对八小时前R..的评论的回应,因为该页面不允许我留下足够多的评论:

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

(大量)编辑截至 2011 年 3 月 26 日,太平洋标准时间 18:00:

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

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

我对系统调用主题有点陌生,所以请多多包涵。

“内核sysqueue系统调用”是指“__NR_rt_sigqueueinfo”调用吗?这是我在执行此操作时发现的唯一一个:

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

如果是这样的话,我想我不明白你的原点。内核将允许(非 root)我使用SI-QUEUE伪造的 pid 和 uid 而不会出错。如果我有这样编码的发送方:

#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看不到。我想我很困惑。

于 2011-03-26T11:38:54.793 回答
0

我同意这一点si_uid并且si_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、消息队列和每个进程的计时器。在用户空间中实现这些需要能够分别用和SI_ASYNCIO发送信号。我不知道 glibc 是如何分配资源来预先对信号进行排队的;对我来说,它看起来没有,只是希望SI_MESGQSI_TIMERrt_sigqueueinfo不会失败。POSIX 明确禁止丢弃计时器到期(异步 I/O 完成,消息到达消息队列)通知,因为在到期时排队的信号太多;如果资源不足,实施应该拒绝创建或注册。这些对象已经过仔细定义,以便每个 I/O 请求、消息队列或计时器一次最多可以有一个正在运行的信号。

于 2011-05-08T13:45:45.843 回答