-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Processes with stdout=PTY hang on select() because of glibc buffering #1038
Comments
I can confirm I can repro this. The issue is Python not doing what it's supposed to (a single (*"block" until the default timeout expires) |
|
It looks like Python internally does
|
This works around the issue, but I don't like it. I'm still not sure why diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py
index 8028457..6667214 100644
--- a/pwnlib/tubes/process.py
+++ b/pwnlib/tubes/process.py
@@ -676,6 +676,15 @@ class process(tube):
if not self.connected_raw('recv'):
raise EOFError
+ # Work-around https://github.com/Gallopsled/pwntools/issues/1038
+ try:
+ with self.local(timeout=0):
+ data = self.proc.stdout.read()
+ if data:
+ return data
+ except IOError as (err, strerror):
+ pass
+
if not self.can_recv_raw(self.timeout):
return '' |
This can be reproduced with minimal pwntools involvement. The second call to from pwn import *
import select
r = process('./test')
timeout = 60
stdout = r.stdout
select.select([stdout], [], [], timeout) == ([stdout], [], [])
data = stdout.read(4096)
print "Got", len(data)
select.select([stdout], [], [], timeout) == ([stdout], [], []) |
Switching between
|
So apparently what's happening is:
Aaaaand I'm too tired to continue troubleshooting. |
First read:
Second
|
This patch also resolves the issue sufficiently, by avoiding diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py
index 8028457..0182b0c 100644
--- a/pwnlib/tubes/process.py
+++ b/pwnlib/tubes/process.py
@@ -685,7 +685,7 @@ class process(tube):
data = ''
try:
- data = self.proc.stdout.read(numb)
+ data = os.read(self.proc.stdout.fileno(), numb)
except IOError as (err, strerror):
pass @idolf what do you think? |
After a bit more digging, this is entirely the fault of Python's file implementation and our use of
Unrelated, the |
Real fix is here, PR coming soon: diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py
index 8028457..2f54ed1 100644
--- a/pwnlib/tubes/process.py
+++ b/pwnlib/tubes/process.py
@@ -332,11 +332,11 @@ class process(tube):
if self.pty is not None:
if stdin is slave:
- self.proc.stdin = os.fdopen(os.dup(master), 'r+')
+ self.proc.stdin = os.fdopen(os.dup(master), 'r+', 0)
if stdout is slave:
- self.proc.stdout = os.fdopen(os.dup(master), 'r+')
+ self.proc.stdout = os.fdopen(os.dup(master), 'r+', 0)
if stderr is slave:
- self.proc.stderr = os.fdopen(os.dup(master), 'r+')
+ self.proc.stderr = os.fdopen(os.dup(master), 'r+', 0)
os.close(master)
os.close(slave) |
See issue Gallopsled#1038 for extensive details
@idolf Need to cut a |
This was fixed in #1040 and the fix was released in 3.9.1. |
When calling readline or readuntil on a process tube, the tube sometimes gets blocked on select if more than 4096 bytes were received.
It's easier to explain with an example:
where
./test
is just a small binary that prints 4096 A's and 1 BThe expected outcome is that the script finishes, though it blocks on the second
r.readuntil('B')
.When stracing the script, you can see that it receives the complete data but then blocks on a select call:
The text was updated successfully, but these errors were encountered: