正如Basile Starynkevitch所
回答的,这是不可能的。
然而,有一种非常简单和常见的解决方案,不依赖于Linux特定的行为或内存超额控制:使用一个早期派生的从进程来进行fork和exec。
让大型父进程尽早创建一个Unix域套接字,并fork一个从进程,在从进程中关闭所有其他描述符(重新打开STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO到/dev/null)。我更喜欢数据报套接字,因为它简单且保证可靠,尽管流套接字也可以工作。
在一些罕见的情况下,有用的是让从进程执行一个单独的专用小助手程序。在大多数情况下,这是不必要的,并且使安全设计更加容易。(在Linux中,当使用Unix域套接字传递数据时,您可以包含
SCM_CREDENTIALS辅助消息,并在其中使用进程ID来验证对等方正在使用的标识/可执行文件
/proc/PID/exe
伪文件。)
无论如何,从套接字读取时,从属进程将被阻塞。当另一端关闭套接字时,读/接收将返回0,并且从属进程将退出。
从属进程接收到的每个数据报都描述了要执行的命令。(使用数据报允许使用C字符串,用NUL字符分隔,无需进行任何转义等;使用Unix流套接字通常需要以某种方式定界“命令”,这反过来意味着需要在命令组件字符串中转义定界符。)
从属进程创建一个或多个管道,并fork出一个子进程。该子进程关闭原始的Unix套接字,将标准流替换为相应的管道端(关闭其他端),并执行所需的命令。我个人喜欢在Linux上使用额外的close-on-exec套接字来检测成功执行;在错误情况下,errno代码将写入套接字,以便从属-父进程可以可靠地检测故障和确切原因。如果成功,从属-父进程将关闭不必要的管道端,回复原始进程关于成功的信息,并使用
SCM_RIGHTS附加数据作为其他管道端。发送消息后,它关闭其余的管道端,并等待新消息。
在原始进程方面,上述过程是顺序的;每次只能有一个线程开始执行外部进程。(您只需使用互斥锁对访问进行序列化。)可以同时运行多个线程;只有与从属助手的请求和响应是序列化的,如果这是一个问题--在典型情况下不应该是--您可以例如通过在每个消息前缀中添加ID号(由父进程分配,单调递增)来复用连接。在这种情况下,您可能会在父端使用专用线程来管理与从属的通信,因为您肯定不能同时有多个线程从同一套接字读取,并期望确定性结果。改进计划的进一步改进包括使用专用进程组执行进程、通过设置从属进程的限制来设置限制,并使用特权从属将命令作为专用用户和组执行。
特权从属案例是最有用的父进程为其执行单独的辅助程序。在Linux中,双方都可以使用通过Unix域套接字的
SCM_CREDENTIALS
辅助消息来验证对等方的身份(PID,并且具有ID,可执行文件),从而使实现强大的安全性变得相当简单。(但请注意,必须检查
/proc/PID/exe
不止一次,以捕获恶意程序发送消息的攻击,该程序快速执行适当的程序,但使用命令行参数导致它很快退出,偶尔看起来像正确的可执行文件发出了请求,而描述符的副本——因此整个通信通道——由邪恶的用户控制。)
总之,原始问题可以解决,尽管所提问的答案是否定的。如果执行对安全性敏感,例如更改特权(用户账户)或能力(在Linux中),则必须仔细考虑设计,但在正常情况下,实现相当简单。
如有必要,我很乐意进一步阐述。