argv 可以在运行时更改吗(不是由应用程序本身更改)

Can argv be changed at runtime (not by the app itself)

不知main()的输入参数能否在运行时更改。换句话说,我们是否应该在处理 argv 中的数据时保护应用程序免受可能的 TOCTTOU 攻击?目前,我不知道有什么方法可以更改 argv 中传递的数据,但我不确定这种方法是否不存在。

UPD:我忘了指出我对从程序外部更改 argv 感到好奇,因为 argv 是从程序外部接受的。

根据您的 threat model 这里有两个主要选项:

  1. 您不信任环境,并假设您机器上的其他特权进程能够在程序 运行 时更改程序内存的内容。如果是这样,没有什么是安全的,程序可以被修改为按字面意思任何事情。在这种情况下,您甚至不能相信整数比较。

  2. 您信任您的程序所在的环境运行。在这种情况下,您的程序是其数据的唯一所有者,只要您没有明确决定更改 argv 或任何其他数据,您就可以依赖它。

在第一种情况下,您是否防范潜在的 argv 修改并不重要,因为您不信任执行环境,所以即使是那些警卫也可能被愚弄。在第二种情况下,你信任执行环境,所以你不需要在第一时间防范问题。

在上述两种情况下,答案是:,您不应该在处理 argv 中的数据时保护应用程序免受可能的 TOCTTOU 攻击。

TOCTTOU 类问题通常由 外部不受信任的数据 引起,这些数据可以被其他人修改,并且根据定义不应被信任。一个简单的例子是一个文件的存在:你不能依赖它,因为机器上的其他用户或程序可以删除或移动它,你可以确保文件可以使用的唯一方法是尝试打开它。在argv的情况下,数据不是外部的,是进程自己拥有的,所以这个问题确实不适用。

一般情况下,argv数组中传递给main()的字符串集合是在程序用户space内部设置的,大多在程序顶部的固定位置程序堆栈。

这样一个固定位置的原因是,一些程序修改这个区域以允许特权程序(例如 ps 命令)收集并显示不同的命令参数,随着程序的发展运行。这用于 sendmail(8) 之类的程序或用户程序的线程中,以向您显示哪个线程在您的程序中执行什么工作。

这是一个不标准的功能,不同的操作系统使用不同的方式(我已经向您描述了 BSD 方式)据我所知,linux 也表现出这种行为和 Solaris。

一般来说,这使得 main 的参数属于用户进程 space,必须小心修改(使用某些操作系统特定的合同),因为它通常受到严格的约束惯例。 ps(1) 命令挖掘它要显示的进程的用户 space 以显示显示命令参数的长列表。不同的操作系统文档(可能你可以从你系统中使用的链接器标准脚本中得到这个确切的格式或者堆栈是如何被exec(2)调用家族初始化的——exec(2)手册页应该也有帮助)

我不完全知道这是否是您所期望的,或者您是否只是想看看是否可以修改参数....作为属于进程用户 space 的东西,它们很可能是可以修改的,但除了这个答案中描述的那些之外,我猜不出有任何理由这样做。

顺便说一下,execlp(2) 系统调用的 FreeBSD 手册页显示了以下摘录:

The type of the argv and envp parameters to execle(), exect(), execv(), execvp(), and execvP() is a historical accident and no sane implementation should modify the provided strings. The bogus parameter types trigger false positives from const correctness analyzers. On FreeBSD, the __DECONST() macro may be used to work around this limitation.

这清楚地表明您不能修改它们(至少在 FreeBSD 中)。我假设 ps(8) 命令将以正确的方式处理验证这些参数的额外工作,以便永远不会出现安全问题错误(好吧,这可以测试,但我把它留作感兴趣的人的练习人)

编辑

如果你查看 FreeBSD 中的 /usr/include/sys/exec.h(第 43 行),你会发现在用户堆栈的顶部有一个 struct ps_strings,它被 ps(1) 使用命令查找和定位进程环境和 argv 字符串。虽然您可以编辑它以更改程序提供给 ps(1) 的信息,但您有一个 setproctitle(3) 库函数(同样,所有这些都是 FreeBSDish,您必须深入挖掘才能找到 linux, 或其他, 解决了这个问题)

我已经尝试过这种方法,但它不起作用。今天有一个库函数调用来获取这个方法,但是栈顶实际上填充了上面提到的数据(我假设是出于兼容性原因)