Skip to content
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

Misc run_in_new_terminal improvements. #1261

Merged
merged 4 commits into from
Nov 1, 2020
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 35 additions & 23 deletions pwnlib/util/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import base64
import errno
import os
import platform
import re
import signal
import socket
import stat
import string
import subprocess

from pwnlib import atexit
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util import fiddling
Expand Down Expand Up @@ -178,27 +180,31 @@ def which(name, all = False):
else:
return None

def run_in_new_terminal(command, terminal = None, args = None):
"""run_in_new_terminal(command, terminal = None) -> None
def run_in_new_terminal(command, terminal = None, args = None, kill_at_exit = True):
"""run_in_new_terminal(command, terminal = None, args = None, kill_at_exit = True) -> int

Run a command in a new terminal.

When ``terminal`` is not set:
- If ``context.terminal`` is set it will be used.
If it is an iterable then ``context.terminal[1:]`` are default arguments.
- If a ``pwntools-terminal`` command exists in ``$PATH``, it is used
- If ``$TERM_PROGRAM`` is set, that is used.
- If X11 is detected (by the presence of the ``$DISPLAY`` environment
variable), ``x-terminal-emulator`` is used.
- If tmux is detected (by the presence of the ``$TMUX`` environment
variable), a new pane will be opened.
- If GNU Screen is detected (by the presence of the ``$STY`` environment
variable), a new screen will be opened.
- If ``$TERM_PROGRAM`` is set, that is used.
- If X11 is detected (by the presence of the ``$DISPLAY`` environment
variable), ``x-terminal-emulator`` is used.

If `kill_at_exit` is :const:`True`, try to close the command/terminal when the
current process exits. This may not work for all terminal types.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a little unfortunate that this doesn't work nicely for all terminal types. One way to do it would be to wrap the command in a shell script which dumps its pid to a temp file before execing the command. I didn't want to think too hard about shell escaping, which is why I've settled for making it just work in tmux here. Happy to try out a more general approach if you think it'd be useful though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that it should work quite ok. For shell escaping, there is an excellent sh_string module in pwntools, so you don't have to think so hard :)


Arguments:
command (str): The command to run.
terminal (str): Which terminal to use.
args (list): Arguments to pass to the terminal
kill_at_exit (bool): Whether to close the command/terminal on process exit.

Note:
The command is opened with ``/dev/null`` for stdin, stdout, stderr.
Expand All @@ -214,18 +220,18 @@ def run_in_new_terminal(command, terminal = None, args = None):
elif which('pwntools-terminal'):
terminal = 'pwntools-terminal'
args = []
elif 'TMUX' in os.environ and which('tmux'):
terminal = 'tmux'
args = ['splitw', '-F' '#{pane_pid}', '-P']
elif 'STY' in os.environ and which('screen'):
terminal = 'screen'
args = ['-t','pwntools-gdb','bash','-c']
elif 'TERM_PROGRAM' in os.environ:
terminal = os.environ['TERM_PROGRAM']
args = []
elif 'DISPLAY' in os.environ and which('x-terminal-emulator'):
terminal = 'x-terminal-emulator'
args = ['-e']
elif 'TMUX' in os.environ and which('tmux'):
terminal = 'tmux'
args = ['splitw']
elif 'STY' in os.environ and which('screen'):
terminal = 'screen'
args = ['-t','pwntools-gdb','bash','-c']

if not terminal:
log.error('Could not find a terminal binary to use. Set context.terminal to your terminal.')
Expand All @@ -248,17 +254,23 @@ def run_in_new_terminal(command, terminal = None, args = None):

log.debug("Launching a new terminal: %r" % argv)

pid = os.fork()

if pid == 0:
# Closing the file descriptors makes everything fail under tmux on OSX.
if platform.system() != 'Darwin':
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added in 60e5a80. I wasn't able to repro on my Mac OS X machine with tmux 2.8, but should be straightforward to add back if it is still an issue.

devnull = open(os.devnull, 'rwb')
os.dup2(devnull.fileno(), 0)
os.dup2(devnull.fileno(), 1)
os.dup2(devnull.fileno(), 2)
os.execv(argv[0], argv)
os._exit(1)
stdin = stdout = stderr = open(os.devnull, 'rwb')
if terminal == 'tmux':
stdout = subprocess.PIPE

p = subprocess.Popen(argv, stdin=stdin, stdout=stdout, stderr=stderr)

if terminal == 'tmux':
out, _ = p.communicate()
pid = int(out)
else:
pid = p.pid

if kill_at_exit:
if terminal == 'tmux':
atexit.register(lambda: os.kill(pid, signal.SIGTERM))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could kill the wrong process on PID reuse, but unfortunately these are the APIs we have to work with. Hopefully PID reuse is relatively unlikely to happen during the relatively short lifetime a pwntools script.

else:
atexit.register(lambda: p.terminate())

return pid

Expand Down