process.send() blocked indefinitely if buffer is full - Python pwntools

Environment

  • OS: Linux linux 5.4.0-52-generic #57~18.04.1-Ubuntu SMP Thu Oct 15 14:04:49 UTC 2020 x8664 x8664 x86_64 GNU/Linux
  • Python: Python 3.6.9
  • Pwntools: 4.2.2

Description

I know that process.recv() related functions are blocking I/O, and they all have timeouts keyword-arguments. Recently, however, I found that process.send() functions are also blocking I/O, which surprises me a lot! When the output buffer is full, the program would be blocked indefinitely, and there is no way of providing some sort of timeout, which is really bad.

The following is a proof-of-concept:

$ cat test.c
#include <unistd.h>
int main() {sleep(1000);}

$ gcc test.c -o  a.out

$ cat test.py
from pwn import *
pr = process('a.out')
for i in range(1000):
    print(i)
    pr.send(b'A' * 1000)

$ python test.py
0
1
2
3
...
61
62
63
64    <-- blocked

To my brief inspection, I found that process.send_raw() is the underlying call, which called Popen.write() and Popen.flush(). https://github.com/Gallopsled/pwntools/blob/0b1f430935842d23aa6f4027eea7ac1bb292bcc1/pwnlib/tubes/process.py#L720-L732

From their document, they said that if the buffer is full, it would raise BlockingIOError:

> help(Popen('a.out', stdin=PIPE).stdin.write)
write(buffer, /) method of _io.BufferedWriter instance
   Write the given buffer to the IO stream.

   Returns the number of bytes written, which is always the length of b
   in bytes.

   Raises BlockingIOError if the buffer is full and the
   underlying raw stream cannot accept more data at the moment.

It seems that they didn't raise an exception at all, because I tested with Popen() and it behaves the same way as the PoC above.

Therefore, I wonder if there's a workaround to detect this? Or at least get rid of the blocking I/O hassle. Thanks.

Asked Oct 11 '21 21:10
avatar davidhcefx
davidhcefx

2 Answer:

I'm not sure that this is something that Pwntools can work around, given that buffering is dictated by the host OS and its settings.

This StackExchange post seems relevant, and goes along with your experiment: https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer

I expect that if you trace your Python script with strace:

$ strace -e write,writev,pwrite,pwritev python test.py

You'll see that you'll see write will start returning something like EAGAIN EFBIG etc.

For what it's worth, it looks like we do attempt to set the file descriptors to non-blocking:

https://github.com/Gallopsled/pwntools/blob/0b1f430935842d23aa6f4027eea7ac1bb292bcc1/pwnlib/tubes/process.py#L352-L357

1
Answered Jan 12 '21 at 11:28
avatar  of zachriggle
zachriggle

Closing this out as it's not clear this is something that Pwntools can work around.

1
Answered Jan 17 '21 at 02:53
avatar  of heapcrash
heapcrash