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