0

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?

sezanzeb
  • 816
  • 8
  • 20
  • The title of the question says "Any process can read my pipe that contains confidental stuff", but what you show in the question seems a rather opposite problem: that even your process can't read the pipe. How did you come to the conclusion that any process could? – ilkkachu Feb 21 '21 at 15:11
  • see answer below. Because I can read the pipes contents from any python terminal given the path of e.g. `/proc/12709/fd/4` – sezanzeb Feb 21 '21 at 15:13
  • yeah it's quite confusing. I guess I'll set up another minimal example for that and reduce the scope of the question – sezanzeb Feb 21 '21 at 15:33
  • https://docs.python.org/3/library/multiprocessing.shared_memory.html , [https://docs.python.org/3/library/multiprocessing.html#shared-ctypes-objects](https://docs.python.org/3/library/multiprocessing.html#shared-ctypes-objects) – wwii Feb 21 '21 at 15:52
  • And no other process will be able to access it? I would have to pass the name of that shared memory via `os.system`/`subprocess.Popen`, which would make that name visible in `ps` – sezanzeb Feb 21 '21 at 15:55
  • Encrypt and decrypt for stuff *in the open*? Set up a socket and pass the info via the socket? I'm just guessing, I've never done this. – wwii Feb 21 '21 at 16:01
  • Since wayland prevents GUIs from running with root there must be a sane way to require information only available via root... But after researching for over one day on that stuff it doesn't seem to be that easy. I can't fork/multiprocess because I need pkexec to get elevated permissions to certain parts of my code. So annoying. And it makes my architecture much more complicated... Socket sounds like a good idea, thanks. encrypting sounds ridiculous considering that the operating system should be able to hand data confidentaly over anyway imo. – sezanzeb Feb 21 '21 at 16:01
  • Can you make the process that requires **less** access the child of the other? Or have a third intermediary process which is the parent of the other two? – wwii Feb 21 '21 at 16:06
  • https://docs.python.org/3/library/mmap.html ...... [https://stackoverflow.com/a/48756506/2823755](https://stackoverflow.com/a/48756506/2823755) – wwii Feb 21 '21 at 16:07
  • Old post: [https://stackoverflow.com/a/1269055/2823755](https://stackoverflow.com/a/1269055/2823755) – wwii Feb 21 '21 at 16:13

2 Answers2

1

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.

pilcrow
  • 56,591
  • 13
  • 94
  • 135
  • Thanks. Do you know if using a socket instead of a pipe prevents other non-root processes from reading my stuff? i.e. is there a way for non-root processes to sniff on messages sent to sockets? – sezanzeb Feb 21 '21 at 16:28
  • according to http://www.humbug.in/2013/sniffing-unix-domain-sockets/ and https://serverfault.com/a/518904 it is possible to sniff on sockets, which means I can't use sockets. – sezanzeb Feb 21 '21 at 17:02
  • ah wait, that blog post is using sudo so that doesn't count – sezanzeb Feb 21 '21 at 17:57
0

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
sezanzeb
  • 816
  • 8
  • 20
  • Is this an answer or a clarification? – wwii Feb 21 '21 at 15:43
  • answer. I still have that other problem, but I can only create one question every 90 minutes. After that I'll create a question with a minimal example for that access stuff. – sezanzeb Feb 21 '21 at 15:45