Repository: Gallopsled/pwntools

Issue:
Bring back support for not having a timeout on tubes
The tubes are full of eels!

It is not possible to have no timeout on a socket (which should be the default by the way).

Example:

```
>>> from pwn import *
>>> r = remote('localhost', 1337, timeout = None)
[*] Opening connection to localhost on port 1337: 
[*] Opening connection to localhost on port 1337: Trying 127.0.0.1
[+] Opening connection to localhost on port 1337: Done
>>> print r.timeout
1.0
```



Discussion:
This is working as intended.  

# pwnlib 2.2 behavior

The 'tl;dr' is that the magic value `'default'` has been removed.  `context.timeout` and `tube.timeout` is now always an integer object.

The following is the behavior as of my contributions for 2.2.  

## Using the default timeout

`timeout=None` is the default value, and does not indicate an infinite timeout.  It implies that the timeout should fall through to the current `tube` timeout, then the `context` value, etc.  This takes the place of the old default magic string `'default'`.  This also means that `timeout=0` means to return immediately.

See the [documentation](https://github.com/Gallopsled/pwntools/blob/master/pwnlib/context/__init__.py#L785) for the `timeout` property on contexts. 

See the [documentation](https://github.com/Gallopsled/pwntools/blob/master/pwnlib/tubes/timeout.py) for the `tube.Timeout` object for the fall-through behavior.

Specifically, the correct way to do this is currently `timeout=float('inf')`.  All of the timeouts are numeric.  The `context` handles the magic string `'inf'` by itself to shorten infinite timeouts.

``` python
r = remote('localhost', 1337, timeout=float('inf'))
```

After fixing the issue described below, this will be shortened to

``` python
r = remote('localhost', 1337, timeout='inf')
```

## Default timeout value

The default timeout is [specified](https://github.com/Gallopsled/pwntools/blob/master/pwnlib/context/__init__.py#L299) to be 1 second.  If we want it to be infinite, this should be changed to `float('inf')`.

# Known Issues

While investigating this, I discovered the following sub-issues.

## OSX Timeouts > `2**22`

It appears that specifying a timeout value of `2**22` or greater on OSX causes the underlying socket calls to fail.  The suggested method of fixing this is to assume that any timeout value of `2**21` seconds or longer is effectively infinite, and to put the tube into blocking mode.

This example code will demonstrate the issue.  It works on Linux, but not OSX.

``` python
import socket
l = listen(1337)
socket.create_connection(('localhost',1337),timeout=float('inf'))
```

## tube.Timeout object does not accept 'inf'

The `tube.Timeout` object's constructor does not handle the shortcut for `'inf'`.


Resolving patch:
diff --git a/pwnlib/context/__init__.py b/pwnlib/context/__init__.py
--- a/pwnlib/context/__init__.py
+++ b/pwnlib/context/__init__.py
@@ -296,7 +296,7 @@ class ContextType(object):
         'newline': '\n',
         'os': 'linux',
         'signed': False,
-        'timeout': 1.0,
+        'timeout': float('inf'),
     }
 
     #: Valid values for :meth:`pwnlib.context.ContextType.os`
@@ -439,6 +439,8 @@ def local(self, **kwargs):
 
             >>> context.clear()
             >>> context.timeout = 1
+            >>> context.timeout == 1
+            True
             >>> print context.timeout
             1.0
             >>> with context.local(timeout = 2):
@@ -470,7 +472,7 @@ def clear(self):
             >>> # Default value
             >>> del context.timeout
             >>> print context.timeout
-            1.0
+            inf
             >>> # Inside of a scope, use clear
             >>> context.timeout = 2
             >>> with context.local():
@@ -478,14 +480,14 @@ def clear(self):
             ...     context.clear()
             ...     print context.timeout
             2.0
-            1.0
+            inf
             >>> # Outer scope is unaffected
             >>> print context.timeout
             2.0
             >>> # It can also be used in the global scope
             >>> context.clear()
             >>> print context.timeout
-            1.0
+            inf
         """
         self._tls._current.clear()
 
@@ -788,23 +790,34 @@ def timeout(self, value):
         Default amount of time to wait for a blocking operation before it times out,
         specified in seconds.
 
-        The default value is ``1``.
+        The default value is to have an infinite timeout.
 
         Any floating point value is accepted, as well as the special
         string ``'inf'`` which implies that a timeout can never occur.
 
         Examples:
 
-            >>> context.timeout == 1
+            >>> # Default value
+            >>> context.clear()
+            >>> context.timeout == float('inf')
             True
+            >>> # Ten seconds
+            >>> context.timeout = 10
+            >>> # Shortcut for 'inf'
             >>> context.timeout = 'inf'
-            >>> context.timeout > 2**256
+            >>> context.timeout == float('inf')
             True
             >>> context.timeout - 30
             inf
         """
         value = float(value)
 
+        # OSX does not permit setting socket timeouts to 2**22.
+        # Assume that if we receive a timeout of 2**21 or greater,
+        # that the value is effectively infinite.
+        if value > 2*21:
+            value = float('inf')
+
         if value < 0:
             raise AttributeError("timeout must not be negative (%r)" % value)
         return value
diff --git a/pwnlib/tubes/sock.py b/pwnlib/tubes/sock.py
--- a/pwnlib/tubes/sock.py
+++ b/pwnlib/tubes/sock.py
@@ -69,11 +69,13 @@ def settimeout_raw(self, timeout):
         if not self.sock:
             return
 
-        if timeout != None and timeout <= 0:
-            self.sock.setblocking(0)
-        else:
-            self.sock.setblocking(1)
-            self.sock.settimeout(timeout)
+        # OSX behaves oddly with timeouts of 2**22 or greater.
+        # Change it so that this is 'infinite'
+        if timeout > 2**21:
+            timeout = 2**21
+
+        self.sock.setblocking(1)
+        self.sock.settimeout(timeout)
 
     def can_recv_raw(self, timeout):
         if not self.sock or self.closed["recv"]:
diff --git a/pwnlib/tubes/timeout.py b/pwnlib/tubes/timeout.py
--- a/pwnlib/tubes/timeout.py
+++ b/pwnlib/tubes/timeout.py
@@ -44,9 +44,11 @@ class Timeout(object):
 
     """
     def __init__(self, timeout=None):
-        self._timeout = timeout
+        with context.local(timeout=timeout):
+            self._timeout = context.timeout
         self._start   = 0
 
+
     @property
     def timeout(self):
         """


Test patch: