从C内部调用shell命令是一个好主意吗?


50

udevadm info -q path -n /dev/ttyUSB2我想从C程序中调用一个unix shell命令()。经过大约一周的努力,我可以自己重新实施它,但是我不想这样做。

我打电话popen("my_command", "r");还是被广泛接受的优良作法,还是会引入不可接受的安全性问题并转发兼容性问题?做这样的事情对我来说是错误的,但是我不能指责为什么会不好。


8
要考虑的一件事是注入攻击。
MetaFight

8
我主要是在考虑将用户输入作为shell命令参数提供的情况。
MetaFight

8
有人可以将恶意udevadm可执行文件放在与您的程序相同的目录中,然后使用它来升级特权吗?我对UNIX安全性了解不多,但是您的程序可以运行setuid root吗?
安德鲁·皮利斯

7
@AndrewPiliser如果当前目录不在上PATH,则否-必须使用来运行它./udevadm。但是,IIRC Windows将运行相同目录的可执行文件。
伊兹卡塔'17

4
只要有可能,就直接调用所需的程序,而无需通过外壳。
CodesInChaos

Answers:


58

这不是特别糟糕,但是有一些警告。

  1. 您的解决方案的便携性如何?您选择的二进制文件会在任何地方都一样操作,以相同格式输出结果等吗?它的设置LANG等会不同吗?
  2. 这会给您的过程增加多少额外的负担?与执行库调用(通常而言)相比,派生二进制文件会导致更多的负载并需要更多的资源。在您的情况下可以接受吗?
  3. 有安全性问题吗?有人可以用您选择的二进制文件替换另一个,然后执行恶意行为吗?您是否将用户提供的args用于二进制文件,并且它们是否可以提供;rm -rf /(例如)(请注意,某些API允许您比仅在命令行上提供args更安全地指定args)

当我处于可以预测的已知环境中,易于解析二进制输出时(如果需要-您可能只需要退出代码),并且我也不需要这样做,我通常很乐意执行二进制文件经常。

正如您已经指出的,另一个问题是复制二进制文件需要做多少工作?它是否使用您也可以利用的库?


13
Forking a binary results in a lot more load and requires more resources than executing library calls (generally speaking). 如果在Windows上,我会同意。但是,OP在Unix上指定了分叉成本足够低的Unix,因此很难说动态加载库是否比分叉差。
wallyk

1
我通常希望一个库被加载一次,而二进制文件可能被执行多次。与以往一样,它取决于使用情况,但需要加以考虑(如果后来被撤消)
Brian Agnew

3
Windows上没有“分叉”,因此报价必须特定于Unix。
科迪·格雷

5
NT内核确实支持分支,尽管它不会通过Win32公开。无论如何,我相信运行外部程序的成本来自而exec不是来自fork。加载节,链接共享对象,为每个节运行初始化代码。然后必须将所有数据都转换为要在另一侧解析的文本,包括输入和输出。
频谱

8
公平地说,udevadm info -q path -n /dev/ttyUSB2无论如何都无法在Windows上运行,因此整个分叉讨论都有些理论化。
MSalters

37

一旦引入了潜在的媒介,就要格外小心,以防止注入漏洞。现在这是您的首要考虑事项,但是稍后您可能需要选择的能力ttyUSB0-3,然后该列表将在其他地方使用,因此它将被排除在遵循单一责任原则的范围之外,然后客户将需要放置列表中的任意设备,并且使得开发商认为改变将不知道该名单最终被以不安全的方式使用。

换句话说,就像您所认识的最粗心的开发人员一样,对似乎无关的代码部分进行不安全的更改。

其次,除非文档特别说明,否则CLI工​​具的输出通常不被认为是稳定的接口。对于运行的脚本,您可以自己进行故障排除,但对于部署到客户的脚本,可以依靠它们。

第三,如果您想要一种简单的方法来从软件中提取价值,那么其他人也可能会想要它。查找已经可以满足您需要的库。 libudev已经安装在我的系统上:

#include <libudev.h>
#include <sys/stat.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
    struct stat statbuf;

    if (stat("dev/ttyUSB2", &statbuf) < 0)
        return -1;
    struct udev* udev = udev_new();
    struct udev_device *dev = udev_device_new_from_devnum(udev, 'c', statbuf.st_rdev);

    printf("%s\n", udev_device_get_devpath(dev));

    udev_device_unref(dev);
    udev_unref(udev);
    return 0;
}

该库中还有其他有用的功能。我的猜测是,如果您需要此功能,一些相关功能也可能会派上用场。


2
谢谢。我花了很长时间寻找libudev。我只是找不到它!
johnny_boy

2
我看不到这里的“注入漏洞”是什么问题,除非在另一个用户控制其环境时调用OP的程序,这主要是在suid的情况下(也可能,但在较小程度上,诸如cgi和ssh强制命令)。没有理由需要针对正常运行的用户强化常规程序。
R.,

2
@R ..:“注入漏洞”是您泛化ttyUSB2并使用时的情况sprintf(buf, "udevadm info -q path -n /dev/%s", argv[1])。现在看来相当安全的,但如果argv[1]"nul || echo OOPS"
MSalters

3
@R ..:您只需要更多的想象力。首先是一个固定的字符串,然后是用户提供的参数,然后是由用户运行的其他程序提供的参数,但他们不理解,然后是浏览器从网页上提取的参数。如果事情继续“走下坡路”,那么最终就必须有人负责清理输入内容,而卡尔说要特别注意找出可能出现的问题。
史蒂夫·杰索普

6
尽管如果预防原则确实是“假设您认识的最粗心的开发人员正在做出不安全的更改”,并且我现在必须编写代码以确保不会导致漏洞利用,那么就我个人而言,我无法下床。我知道最粗心的开发人员会让马基耶维利看起来甚至没有尝试
史蒂夫·杰索普

16

在您的特定情况下,您想在哪里调用udevadm,我怀疑您可以udev作为一个库加入并进行适当的函数调用作为替代?

例如,您可以看看以“信息”模式调用时udevadm本身在做什么:https : //github.com/gentoo/eudev/blob/master/src/udev/udevadm-info.c并进行等效调用关于那些udevadm正在制造。

这样可以避免在Brian Agnew的出色答案中列举的许多不利因素-例如,不依赖特定路径中存在的二进制文件,避免了分叉的开销等。


谢谢,我确实找到了确切的回购信息,最后我仔细检查了一下。
johnny_boy

1
…libudev并不是特别难用。它的错误处理有些欠缺,但是枚举,查询和监视API非常简单。如果尝试一下,您的恐惧可能会少于2小时。
频谱

7

您的问题似乎需要一个森林答案,而这里的答案似乎就像是树的答案,所以我想给您一个森林答案。

这很少是C程序的编写方式。总是编写外壳脚本的方式,有时甚至是编写Python,perl或Ruby程序的方式。

人们通常使用C语言编写代码,以方便使用系统库,并直接对OS系统调用进行低级访问,以及提高速度。而且C是很难编写的语言,因此,如果人们不需要这些东西,那么他们就不会使用C。通常,C程序通常只依赖于共享库和配置文件。

壳化到子进程并不是特别快,并且不需要对低级系统设施进行细粒度和受控的访问,并且它引入了对外部可执行文件的可能令人惊讶的依赖关系,因此很少见到在C程序中。

还有一些其他问题。人们提到的安全性和可移植性问题是完全有效的。当然,它们对于Shell脚本同样有效,但是人们期望Shell脚本中出现此类问题。但是通常不期望C程序具有此类安全问题,这使其更加危险。

但是,我认为,popen与程序其余部分交互的方式最重要的问题。popen必须创建一个子进程,读取其输出并收集其退出状态。同时,该进程的stderr将与您的程序连接到同一stderr,这可能会导致混乱的输出,并且其stdin与您的程序相同,这可能会导致其他有趣的问题。您可以通过</dev/null 2>/dev/null在传递的字符串中包含popen它来解决此问题,因为它是由Shell解释的。

popen创建一个子进程。如果您自己对信号处理或派生处理进行任何操作,最终可能会收到奇怪的SIGCHLD信号。您对的呼叫wait可能会与popen奇怪的比赛条件产生奇怪的互动,并可能会产生奇怪的比赛条件。

安全和可移植性问题在那里。因为它们是用于shell脚本或启动系统上其他可执行文件的任何东西。而且您必须小心,使用您的程序的人无法将shell元字符包含在您输入的字符串中,popen因为该字符串是直接由shwith 给出的sh -c <string from popen as a single argument>

但是我不认为这就是为什么使用C语言程序很奇怪popen。之所以奇怪,是因为C通常是一种低级语言,而popen不是低级语言。并且由于popen在程序上使用了位置设计约束,因为它会与程序的标准输入和输出奇怪地交互,从而使您难以进行自己的过程管理或信号处理。而且,由于通常不希望C程序依赖于外部可执行文件。


0

您的程序可能会受到黑客的攻击。保护自己免受此类活动侵害的一种方法是创建当前计算机环境的副本,然后使用名为chroot的系统运行程序。

从程序的角度看,它是在正常环境下执行的,从安全方面来说,如果有人破坏了您的程序,则它只能访问复制时提供的元素。

这种设置称为chroot监狱,有关更多详细信息,请参阅 chroot监狱

它通常用于设置可公开访问的服务器等。


3
OP正在编写供其他人运行的程序,而不是在自己系统上与不受信任的二进制文件或源代码一起玩。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.