1

As an example I am trying to "imitate" the behaviour of the following sets of commands is bash:

mkfifo named_pipe /challenge/embryoio_level103 < named_pipe & cat > named_pipe

In Python I have tried the following commands:

import os
import subprocess as sp

os.mkfifo("named_pipe",0777) #equivalent to mkfifo in bash..
fw = open("named_pipe",'w')

#at this point the system hangs...

My idea it was to use subprocess.Popen and redirect stdout to fw... next open named_pipe for reading and giving it as input to cat (still using Popen).

I know it is a simple (and rather stupid) example, but I can't manage to make it work..

How would you implement such simple scenario?

  • The bash script is taking advantage of the implied pre-emptive multitasking of the OS. If you want to do the same in a single-threaded program you will have to supply the event loop. – Keith Feb 22 '22 at 15:47

1 Answers1

1

Hello fellow pwn college user! I just solved this level :)

open(path, flags) blocks execution. There are many similar stackoverflow Q&As, but I'll reiterate here. A pipe will not pass data until both ends are opened, which is why the process hangs (only 1 end was opened).

If you want to open without blocking, you may do so on certain operating systems (Unix works, Windows doesn't as far as I'm aware) using os.open with the flag os.O_NONBLOCK. I don't know what consequences there are, but be cautious of opening with nonblocking because you may try reading prematurely and there will be nothing to read (possibly leading to error, etc.).

Also, note that using the integer literal 0777 causes a syntax error, so I assume you mean 0o777 (max permissions), where the preceding 0o indicates octal. The default for os.mkfifo is 0o666, which is identical to 0o777 except for the execute flags, which are useless because pipes cannot be executed. Also, be aware that these permissions might not all be granted and when trying to set to 0o666, the permissions may actually be 0o644 (like in my case). I believe this is due to the umask, which can be changed and is used simply for security purposes, but more info can be found elsewhere.

For the blocking case, you can use the package multiprocessing like so:

import os
import subprocess as sp
from multiprocessing import Process

path='named_pipe'
os.mkfifo(path)

def read(): sp.run("cat", stdin=open(path, "r"))
def write(): sp.run(["echo", "hello world"], stdout=open(path, "w"))

if __name__ == "__main__":
    p_read = Process(target=read)
    p_write = Process(target=write)

    p_read.start()
    p_write.start()

    p_read.join()
    p_write.join()

    os.remove(path)

output:

hello world
ReubenBeeler
  • 142
  • 1
  • 10
  • i think the correct thing to point out here is that the pipe needs to be first opened on a reading mode before it is opened for write. The same thing would happen in the shell if you mkfifo /tmp/f and then echo foo > /tmp/f. The system would block because there are no readers. Thus in this case you can open the fifo first in read mode (keep a reference like fr=open(fifo, os.O_RDWR) and then keep the fw=open(fifo, os.O_WRONLY). opening in nonblocking mode just masks the problem. – ramrunner Jan 07 '23 at 06:11