Repository: Gallopsled/pwntools

Issue:
"TypeError: not enough arguments for format string" while trying to look up a corefile
I am using pwntools 3.12.0 on Fedora 28.

When trying to execute

```python
from pwn import *
process("echo").corefile
```

I get the TypeError message. This also reproduces in the pwntools:stable docker container.

The full error message is:

```
[x] Starting local process '/usr/bin/echo' argv=['echo']
[+] Starting local process '/usr/bin/echo' argv=['echo'] : pid 9185
[*] Process '/usr/bin/echo' stopped with exit code 0 (pid 9185)
[DEBUG] core_pattern: '|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h %e'                                                                           
[DEBUG] core_uses_pid: True
[DEBUG] interpreter: ''
[DEBUG] Looking for QEMU corefile
[DEBUG] Trying corefile_path: '/home/jbushart/projects/qemu_echo_*_9185.core'
[DEBUG] Looking for native corefile
[DEBUG] Checking for corefile (piped)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-5665c95c54fa> in <module>()
----> 1 p.corefile

/home/jbushart/.local/lib/python2.7/site-packages/pwnlib/tubes/process.pyc in corefile(self)                                                                 
    908             return pwnlib.gdb.corefile(self)
    909
--> 910         finder = pwnlib.elf.corefile.CorefileFinder(self)
    911         if not finder.core_path:
    912             self.warn("Could not find core file for pid %i" % self.pid)

/home/jbushart/.local/lib/python2.7/site-packages/pwnlib/elf/corefile.pyc in __init__(self, proc)                                                            
   1151         if not self.core_path:
   1152             log.debug("Looking for native corefile")
-> 1153             self.core_path = self.native_corefile()
   1154
   1155         if not self.core_path:

/home/jbushart/.local/lib/python2.7/site-packages/pwnlib/elf/corefile.pyc in native_corefile(self)                                                           
   1294         if self.kernel_core_pattern.startswith('|'):
   1295             log.debug("Checking for corefile (piped)")
-> 1296             return self.native_corefile_pipe()
   1297
   1298         log.debug("Checking for corefile (pattern)")

/home/jbushart/.local/lib/python2.7/site-packages/pwnlib/elf/corefile.pyc in native_corefile_pipe(self)                                                      
   1304         # We only support apport
   1305         if '/apport' not in self.kernel_core_pattern:
-> 1306             log.warn_once("Unsupported core_pattern: %r" % self.kernel_core_pattern)                                                                 
   1307             return None
   1308

/home/jbushart/.local/lib/python2.7/site-packages/pwnlib/log.pyc in warn_once(self, *args, **kwargs)                                                         
    370     def warn_once(self, *args, **kwargs):
    371         """Alias for :meth:`warning_once`."""
--> 372         return self.warning_once(*args, **kwargs)
    373
    374     # logging functions also exposed by `logging.Logger`

/home/jbushart/.local/lib/python2.7/site-packages/pwnlib/log.pyc in warning_once(self, message, *args, **kwargs)                                             
    362         Logs a warning message.  The same message is never printed again.                                                                            
    363         """
--> 364         m = message % args
    365         if m not in self._one_time_warnings:
    366             if self.isEnabledFor(logging.INFO):

TypeError: not enough arguments for format string
```

I guess [this line](https://github.com/Gallopsled/pwntools/blob/9023b383af7024198434be93d1db1e64422f4513/pwnlib/elf/corefile.py#L1316) is the cause, as the core_pattern (`|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h %e`) contains more %-signs, this creating a new format string.

Interestingly, setting the PWNLIB_DEBUG variable changes the error:

```
pwntools@7e3194fabc37:~$ env PWNLIB_DEBUG=1 python                                                                                                           
Python 2.7.12 (default, Dec  4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> process("echo").corefile
[x] Starting local process '/bin/echo' argv=['echo']
[+] Starting local process '/bin/echo' argv=['echo'] : pid 22
[ERROR] argv must be strings: [None, '--version']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/tubes/process.py", line 908, in corefile                                                               
    return pwnlib.gdb.corefile(self)
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/gdb.py", line 959, in corefile                                                                         
    if version() < (7,11):
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/gdb.py", line 1001, in version                                                                         
    with tubes.process.process([program, '--version'], level='error') as gdb:
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/tubes/process.py", line 241, in __init__                                                               
    executable, argv, env = self._validate(cwd, executable, argv, env)
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/tubes/process.py", line 513, in _validate                                                              
    self.error("argv must be strings: %r" % argv)
  File "/usr/local/lib/python2.7/dist-packages/pwnlib/log.py", line 417, in error                                                                            
    raise PwnlibException(message % args)
pwnlib.exception.PwnlibException: argv must be strings: [None, '--version']
```


Discussion:

Resolving patch:
diff --git a/pwnlib/elf/corefile.py b/pwnlib/elf/corefile.py
--- a/pwnlib/elf/corefile.py
+++ b/pwnlib/elf/corefile.py
@@ -71,6 +71,7 @@
 import os
 import socket
 import StringIO
+import subprocess
 import tempfile
 
 import elftools
@@ -1314,12 +1315,42 @@ def apport_read_crash_data(self):
 
         return data
 
+    def systemd_coredump_corefile(self):
+        """Find the systemd-coredump crash for the process and dump it to a file.
+
+        Arguments:
+            process(process): Process object we're looking for.
+
+        Returns:
+            `str`: Filename of core file, if coredump was found.
+        """
+        filename = "core.%s.%i.coredumpctl" % (self.basename, self.pid)
+        try:
+            subprocess.check_call(
+                [
+                    "coredumpctl",
+                    "dump",
+                    "--output=%s" % filename,
+                    # Filter coredump by pid and filename
+                    str(self.pid),
+                    self.basename,
+                ],
+                stdout=open(os.devnull, 'w'),
+                stderr=subprocess.STDOUT,
+                shell=False,
+            )
+            return filename
+        except subprocess.CalledProcessError as e:
+            log.debug("coredumpctl failed with status: %d" % e.returncode)
+
     def native_corefile(self):
         """Find the corefile for a native crash.
 
         Arguments:
             process(process): Process whose crash we should find.
 
+        Returns:
+            `str`: Filename of core file.
         """
         if self.kernel_core_pattern.startswith('|'):
             log.debug("Checking for corefile (piped)")
@@ -1329,25 +1360,36 @@ def native_corefile(self):
         return self.native_corefile_pattern()
 
     def native_corefile_pipe(self):
-        """native_corefile_pipe(self) -> str
-        """
-        # We only support apport
-        if '/apport' not in self.kernel_core_pattern:
-            log.warn_once("Unsupported core_pattern: %r" % self.kernel_core_pattern)
-            return None
+        """Find the corefile for a piped core_pattern
 
-        apport_core = self.apport_corefile()
+        Supports apport and systemd-coredump.
 
-        if apport_core:
-            # Write the corefile to the local directory
-            filename = 'core.%s.%i.apport' % (self.basename, self.pid)
-            with open(filename, 'wb+') as f:
-                f.write(apport_core)
-            return filename
+        Arguments:
+            process(process): Process whose crash we should find.
 
-        # Pretend core_pattern was just 'core', and see if we come up with anything
-        self.kernel_core_pattern = 'core'
-        return self.native_corefile_pattern()
+        Returns:
+            `str`: Filename of core file.
+        """
+        if '/apport' in self.kernel_core_pattern:
+            log.debug("Found apport in core_pattern")
+            apport_core = self.apport_corefile()
+
+            if apport_core:
+                # Write the corefile to the local directory
+                filename = 'core.%s.%i.apport' % (self.basename, self.pid)
+                with open(filename, 'wb+') as f:
+                    f.write(apport_core)
+                return filename
+
+            # Pretend core_pattern was just 'core', and see if we come up with anything
+            self.kernel_core_pattern = 'core'
+            return self.native_corefile_pattern()
+        elif 'systemd-coredump' in self.kernel_core_pattern:
+            log.debug("Found systemd-coredump in core_pattern")
+            return self.systemd_coredump_corefile()
+        else:
+            log.warn_once("Unsupported core_pattern: %r", self.kernel_core_pattern)
+            return None
 
     def native_corefile_pattern(self):
         """


Test patch: