siginfo中的数据可信吗?

Posted

技术标签:

【中文标题】siginfo中的数据可信吗?【英文标题】:Is the data in siginfo trustworthy? 【发布时间】:2011-03-10 23:05:51 【问题描述】:

我发现在 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 的这种行为是不合规的并且是一个严重的错误。

【问题讨论】:

看起来你难倒了。也许你应该试试 Linux 内核邮件列表:kernel.org/pub/linux/docs/lkml 【参考方案1】:

您引用的 POSIX 页面的该部分还列出了 si-code 的含义,其含义如下:

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

那部分接着说:

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

如果仅sigqueue() 函数使用SI_QUEUE,则不会违反任何规定。您的方案涉及使用SI_QUEUEsigqueue() 函数以外的代码问题是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 syscall”是指“__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。我想我很困惑。

【讨论】:

我看不出这与我引用的段落及其要求 uid 和 pid 在si_code 采用指示进程生成的信号的一组值之一时有效。我将我引用的文本中的“shall”解释为表明应用程序可以依赖于在调用信号处理程序时始终满足的约束,无论用于发送信号的接口如何以及它是否是标准的。跨度> 我无法在这里留下我的全部评论;该页面说我没有足够的字符。请参阅上面我的答案的编辑。 我认为它是否符合要求取决于 uid/pid 是否在 si_codeSI_QUEUE 或其他特殊值之一时有效是系统信号的要求 传递 机制或sigqueue 函数的要求。据我所知,是前者而不是后者。无论如何,我很困惑为什么内核将它留给应用程序(并信任它)来填写应该是特权限制的字段...... 顺便提一下,内核sigqueue 系统调用确实禁止应用程序欺骗内核传递的信号(我相信是肯定的si_code)。在这种情况下,它会因EINVALEPERM 失败(我忘记了)... 再次请参阅我在太平洋标准时间 18:00 的大量回复。【参考方案2】:

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

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

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

【讨论】:

但是rt_sigqueueinfo 确实强制执行si_code&lt;=0,在这种情况下,uid 和 pid 必须是有效的,对吧? 是的,如果si_code&lt;=0并且系统支持XSI选项,uid和pid必须有效。但是,不要求SI_QUEUESI_TIMER 等代码为负数或零。更令人困惑的是,FreeBSD 不支持 XSI 选项的这一部分:如果没有可用的附加信息,它会设置si_code=0(又名SI_NOINFO); uid 和 pid 在这种情况下无效。 但在 Linux 上这些值为负数。

以上是关于siginfo中的数据可信吗?的主要内容,如果未能解决你的问题,请参考以下文章

57 mac 中 SIGINFO 信号, jdk8 支持, 但是 jdk9 不支持?

57 mac 中 SIGINFO 信号, jdk8 支持, 但是 jdk9 不支持?

57 mac 中 SIGINFO 信号, jdk8 支持, 但是 jdk9 不支持?

gperftools尝试

如何接收 sigqueue() 系统调用发送的数据

sigaction()