gdb似乎忽略了可执行文件的功能

11

我正在调试一个使用libnetfilter_queue的程序。文档说明,用户空间队列处理应用程序需要CAP_NET_ADMIN功能。我已经使用setcap实用程序进行了设置:

$ sudo setcap cap_net_raw,cap_net_admin=eip ./a.out
我已验证功能正确应用,因为a)程序可以正常运行,b)getcap返回以下输出:
$ getcap ./a.out
./a.out = cap_net_admin,cap_net_raw+eip
然而,当我尝试使用命令行中的 gdb (如 $ gdb ./a.out) 对这个程序进行调试时,由于没有正确设置权限,它失败了。否则,gdb 的调试功能完全正常,可以像往常一样进行调试。
我甚至尝试将这些能力应用于 gdb 二进制文件本身,但并没有成功。我这样做是因为(正如manpages所记录的那样),"i"标志可能允许被调试程序从调试器继承该能力。
我是否忽略了什么微不足道的东西,或者真的无法完成?

GDB使用ptrace子系统。它具有CAP_SYS_PTRACE功能吗?它可以与任何其他二进制文件一起使用吗?例如,一个hello world程序? - thkala
@thkala:我编辑了问题,使其更加精确。gdb工作正常,它可以调试任何程序(包括这个)。 - Aidan Steele
您能否提供具体的错误信息? - thkala
本身并没有错误信息。我正在使用开发人员提供的示例代码,nfq_unbind_pf()返回了-1(并且errno设置为1),表示失败。 - Aidan Steele
6个回答

21

我遇到了同样的问题,一开始我也以为像上面那样,可能是由于安全原因,gdb忽略了可执行文件的功能。但是,通过阅读源代码,甚至在调试打开/dev/sda1的ext2fs-prog时使用eclipse调试gdb本身,我意识到:

  1. gdb并不特殊,就像其他程序一样。(就像在矩阵中,即使是特工自己也遵守相同的物理法则、重力等,只不过他们都是看门人。)
  2. gdb不是被调试可执行文件的父进程,而是祖父。
  3. 被调试可执行文件的真正父进程是“shell”,即我的情况下的/bin/bash

因此,解决方案非常简单,除了将cap_net_admin,cap_net_raw + eip添加到gdb中之外,您还需要将其应用于您的shell。即setcap cap_net_admin,cap_net_raw+eip /bin/bash

需要对gdb执行此操作的原因是,在创建调试的进程之前,gdb是/bin/bash的父进程。

在gdb内部的真实可执行命令行如下:

/bin/bash exec /my/executable/program/path

这是在gdb中传递给vfork的参数。


谢谢您的回答!天啊,我浪费了很多时间来尝试解决这个问题!!! - Ashley Duncan
为什么在网络上流传的能力文档或任何使用能力的无数示例中都没有提到这些内容呢?它们只是谈论“ping”。我还没有找到一个好的“面向程序员的POSIX能力”摘要,指导如何解决像这样的问题,这是开发需要和使用能力的程序时可能会遇到的问题。 @nick-huang 您的答案真是太棒了。谢谢! - huoneusto

5

我使用了@NickHuang的解决方案,但是在系统更新后,它破坏了systemd服务(bash的功能太多了,systemd无法启动它或类似的问题)。 我改为不再修改bash,而是通过将命令传递给gdb来直接调用可执行文件。 命令如下

set startup-with-shell off

这很棒。只需具有增强功能的GDB(除了被调试程序)比较合理,至少对我来说,调试器需要至少与被调试程序具有相同的特权,而必须授予“常规” shell 特权似乎是一个次优解决方案。 - huoneusto
这个解决方案对我有效,但我需要将命令添加到~/.gdbinit文件中,以使更改永久生效(来源:https://stackoverflow.com/a/8569435/1131281)。 - undefined

4

对于遇到同样问题的用户,您可以通过使用sudo以管理员身份运行gdb来绕过此问题。


1
+1,虽然它并不适用于每种情况。我有两个应用程序。第一个设置了一些功能,但取决于第二个应用程序,后者通过RT信号与第一个应用程序通信。因此,仅使用“gdb ./a.out”无法工作,因为应用程序失去了她的功能,而使用“sudo gdb ./a.out”也无法工作,因为用户运行的应用程序无法向在root下运行的应用程序发送信号。这很难调试,看起来似乎没有解决方法 :) - Mike S.

3

不久前,我也遇到了同样的问题。我的猜测是,使用额外功能运行调试程序可能存在安全问题。

您的程序比运行它的用户拥有更多的权限。使用调试器,用户可以操纵程序的执行。因此,如果程序在具有额外特权的调试器下运行,则用户可以将这些特权用于其他目的,而非程序意图使用它们的目的。这将是一个严重的安全漏洞,因为用户一开始就没有这些特权。


那个答案对我来说很有道理。我会尝试查看gdb源代码以确认这一点,但无论如何我现在会将您的答案标记为已接受。 - Aidan Steele
我的猜测是这由内核处理而非gdb。你应该研究一下ptrace系统调用。 - Fabian
1
来自linux execve手册页的内容:“如果由filename指向的程序文件设置了set-user-ID位,并且底层文件系统未挂载nosuid(对于mount(2)的MS_NOSUID标志),并且调用进程未被ptraced,则调用进程的有效用户ID将更改为该程序文件所有者的用户ID。”这可能也适用于文件特权,因为像suid / sgid一样,它们提供特权升级。 - Fabian
猜测是错误的 - 见Nick Huang的答案。这对我有用。 - Christian

3

对于那些通过IDE运行GDB的用户,无法像@Stéphane J.的回答中所述sudo GDB。在这种情况下,您可以运行:

sudo gdbserver localhost:12345 /path/to/application

然后将IDE的GDB实例连接到本地的GDBServer。

对于Eclipse CDT,这意味着创建一个新的“C/C++远程应用程序”调试配置,然后在调试器>连接选项卡下,输入TCP / localhost / 12345(或您选择的任何端口)。这样,您就可以在Eclipse中进行调试,而您的应用程序具有特权访问。


0

好的,所以我遇到了一些困难,于是我想把答案结合起来进行总结。

简单的解决方案就是按照建议使用sudo gdb命令,但要小心。你所做的是用root身份运行被调试的程序。这可能会导致它的操作方式与在命令行中以普通用户身份运行时不同。可能有点混淆。不过,我永远不会掉进这个陷阱...... 噢,我错了。

如果你使用sudo以root身份运行被调试的程序,或者被调试的程序已设置setuid位,则没问题。但如果被调试的程序是使用POSIX功能(setcap/getcap)运行的,则需要像Nick Huang建议的那样在bash和gdb中反映这些更细粒度的权限,而不仅仅是使用"sudo"强制执行权限。

做任何其他事情可能会让你陷入极端的学习之中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接