【问题标题】:Bad file descriptor when child process opens pipe子进程打开管道时文件描述符错误
【发布时间】:2021-05-23 23:11:58
【问题描述】:

我需要运行一个可执行文件并将来自 os.pipe 的 fd 传递给它,以便父进程可以从中读取结果。我不想为此使用标准输出,因为我需要在孩子的终端中查看日志消息,所以我正在尝试为此目的实现一个管道。

我不能使用 fork 和 multiprocessing.Process,因为此外,子进程应该通过 sudo 运行,而不是父进程,父进程不应该以 root 权限运行。

不应允许其他进程查看该管道中的内容。

这是ospipeexample.py 和孤立的问题。为简单起见,这个最小示例被压缩到一个包含两个分支的文件中。

#!/usr/bin/python3
import os
import pickle
import sys

if len(sys.argv) == 2:
    # write in the child process started via os.system
    print('child opening', sys.argv[-1]) 
    w = os.fdopen(int(sys.argv[-1]), 'wb')
    w.write(pickle.dumps(1))
    w.write(b'\n')
    w.flush()
else:
    # read in the parent process
    rfd, wfd = os.pipe()
    print('parent created', rfd, wfd)
    r = os.fdopen(rfd, 'rb')
    os.system('sudo python3 ospipeexample.py %d' % wfd)
    print(pickle.loads(r.readline()))

我运行python3 ospipeexample.py 的输出是(编辑:这已解决,见下文):

parent created 3 4
child opening 4
Traceback (most recent call last):
  File "/mnt/data/Code/ospipeexample.py", line 8, in <module>
    os.fdopen(int(sys.argv[-1]), 'wb')
  File "/usr/lib/python3.9/os.py", line 1023, in fdopen
    return io.open(fd, *args, **kwargs)
OSError: [Errno 9] Bad file descriptor

预期输出:

parent created 3 4
child opening 4
1

这是ps -aux --forest的输出

mango      12102  0.2  0.0  14480  9236 pts/2    S+   15:40   0:00  |   \_ python3 ospipeexample.py
root       12103  0.0  0.0  16184  7468 pts/2    S+   15:40   0:00  |       \_ sudo python3 ospipeexample.py 4
root       12104  0.2  0.0  14476  8816 pts/2    S+   15:40   0:00  |           \_ python3 ospipeexample.py 4

有什么替代方法,有没有办法使用 python 设置共享内存或其他东西,并通过 os.system 或 subprocess.Popen 将其传递给子进程?

【问题讨论】:

  • 问题的标题是“任何进程都可以读取包含机密内容的我的管道”,但是您在问题中显示的似乎是一个相当相反的问题:即使您的进程也无法读取管道.您是如何得出任何流程都可以的结论的?
  • 见下面的答案。因为我可以从给定路径的任何 python 终端读取管道内容,例如/proc/12709/fd/4
  • 是的,这很令人困惑。我想我会为此设置另一个最小示例并缩小问题的范围
  • 没有其他进程可以访问它?我必须通过os.system/subprocess.Popen 传递该共享内存的名称,这将使该名称在ps 中可见

标签: python unix pipe


【解决方案1】:

您对os.fdopen 的最终子进程调用失败,因为sudo 在生成该子进程之前已经关闭了该fd(以及继承的读取端fd)。

这是sudo 内置的一项安全措施,通常“关闭[s]除标准输入、标准输出和标准错误之外的所有打开的文件描述符。” (见sudo 1.8.3 manual)

您可以(危险地)change this behavior,或以其他方式访问管道(就像您通过打开父管道的特殊 Linux procfs 表示所做的那样),或以其他方式设计其他 IPC。

【讨论】:

  • 谢谢。您知道使用套接字而不是管道是否会阻止其他非根进程读取我的内容?即有没有办法让非根进程嗅探发送到套接字的消息?
  • 根据humbug.in/2013/sniffing-unix-domain-sockets 和serverfault.com/a/518904 可以嗅探套接字,这意味着我不能使用套接字。
  • 啊等等,那篇博文使用 sudo,所以不算数
【解决方案2】:

子进程可能签入 /proc/child-pid/fd,所以我将 fd 的路径作为参数传递。

#!/usr/bin/python3
import os
import pickle
import sys
import subprocess

if len(sys.argv) == 2:
    # write in the child process started via os.system
    print('child writing 1 to', sys.argv[-1])
    w = open(sys.argv[1], 'wb')
    w.write(pickle.dumps(1))
    w.write(b'\n')
    w.flush()
else:
    # read in the parent process
    rfd, wfd = os.pipe()
    print('parent created', rfd, wfd)
    r = os.fdopen(rfd, 'rb')
    path = f'/proc/{os.getpid()}/fd/{wfd}'
    subprocess.Popen(['sudo', 'python3', 'ospipeexample.py', path])
    print(pickle.loads(r.readline()))
parent created 3 4
child writing 1 to /proc/12501/fd/4
1

所以现在可以了。


但是,通过打开一个新的不相关终端,我也可以获得这些内容,这应该是不可能的

>>> p = open('/proc/12501/fd/4', 'rb')
>>> p.readline()
b'\x80\x04K\x01.\n'
>>> pickle.loads(b'\x80\x04K\x01.\n')
1

【讨论】:

  • 这是回答还是澄清?
  • 回答。我还有其他问题,但我每 90 分钟只能创建一个问题。之后,我将为访问内容创建一个带有最小示例的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-22
  • 2018-11-29
  • 2023-03-06
  • 1970-01-01
  • 2021-03-12
  • 1970-01-01
相关资源
最近更新 更多