sezanzeb
sezanzeb

Reputation: 1139

Bad file descriptor when child process opens pipe

I need to run an executable and pass an fd to it which is from os.pipe, so that the parent process can read results from it. I don't want to use stdout for that because I need to see log messages in the terminal of the child, so I'm trying to implement a pipe for that purpose.

I can't use fork and multiprocessing.Process, because furthermore that child process is supposed to run via sudo as opposed to the parent, which should not run with root permissions.

No other process should be allowed to see what is in that pipe.

This is ospipeexample.py and the isolated problem. For simplicity, this minimal example is squeezed into one single file with two branches.

#!/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()))

my output from running python3 ospipeexample.py is (EDIT: this has been solved, see below):

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

expected output:

parent created 3 4
child opening 4
1

here is the output of 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

what would be an alternative, is there any way to set up shared memory or something using python and pass it to the child process via os.system or subprocess.Popen?

Upvotes: 0

Views: 1520

Answers (2)

pilcrow
pilcrow

Reputation: 58731

Your ultimate child process' call to os.fdopen fails because sudo already closed that fd (as well as the inherited read-end fd) before spawning that child.

This is a safety measure built in to sudo, which normally "close[s] all open file descriptors other than standard input, standard output and standard error." (see the sudo 1.8.3 manual)

You'll could (dangerously) change this behavior, or access the pipe some other way (as you've done by opening the special Linux procfs representation of the parent pipe), or otherwise contrive some other IPC.

Upvotes: 1

sezanzeb
sezanzeb

Reputation: 1139

The child process probably checked in /proc/child-pid/fd, so I passed the path of the fd as argument instead.

#!/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

So it works now.


However, via opening a new unrelated terminal I can get those contents as well though, which should not be possible

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

Upvotes: 1

Related Questions