siginfo中的数据可信吗?

发布于 2024-10-21 22:37:34 字数 651 浏览 2 评论 0原文

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

编辑:基于我对POSIX(重点是我添加的):

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

蓝天白云 2024-10-28 22:37:34

您引用的 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 PST 的编辑:

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

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

(大量)编辑截至 2011 年 3 月 26 日 18:00 PST:

再次由于评论长度的限制。

这是对 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() */

然后我可以运行(非根)接收端:

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$ 

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

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

如您所见,第三个事件已欺骗 pid 和 uid。这不是你最初反对的吗?看不到 EINVALEPERM。我想我很困惑。

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.

吐个泡泡 2024-10-28 22:37:34

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

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文