Proxy access to terminal
Status: Done
Rather than giving the sandboxed process the file descriptor for the user's terminal device, pola-run should give the sandboxed process a private file descriptor, and proxy access.
Motivations:
- Also, this allows access to the terminal to be revoked when the process exits.
This will initially only be implemented in the Python version of pola-run. pola-shell and the C pola-run will be removed from the Debian package since they are vulnerable; they may be fixed in a later release.
See TerminalAccess for a general discussion of tty devices.
Stage 1
Grant the sandboxed process pipe FDs or socket FDs.
Note that, as a side effect, this will cause programs that check whether they are running under a terminal (such as readline in bash) to behave differently.
Tasks
Python implementation: Extend the plash.process module to provide a means of specifying the new process's initial FDs. Currently, FDs are passed on by default.
We will have to use the glib main loop in all cases, rather than using it only when Gtk is required.
Remove pola-shell and related programs from the Debian package
Switch to include Python pola-run instead of C pola-run in the Debian package
Change ForwardFD and plash.mainloop so that server process stays alive while FD forwarding is in progress
Ensure that forwarding stdin ends when there is no reader, even when we have nothing to write to it
Change plash.process to not forward stdin/stdout/stderr by default, and require its various users to set these FDs up explicitly
Make pola-run fork to the background when the child process exits
There is a potential race condition: When the sandboxed process exits, we must check for any remaining data buffered on the pipe/socket, otherwise the process's output could be cut short. This is necessary because pipes are asynchronous and not synchronised with the results of wait(). NB. Ideally we need to know the size of the pipe's in-kernel buffer to do this properly.
Questions
Should terminal access be revoked when the child process exits? (Note that it may have forked further processes that continue running in the background.)
- If yes, the server process must run as the parent (at least initially), in order to be able to discover when the child exits. There are two choices:
- When the child exits, the server also exits and so ceases to handle any request.
- When the child exits, the server forks to the background where it continues to handle requests, but no longer forwards terminal access. Forking to the background would make it harder to debug the server process using a debugger.
- Another option is to have two processes besides the sandboxed process. The parent notifies its child server process when the sandboxed process has exited.
- If no, the server process can run as the child process. It does not need to find out when the sandboxed process exits.
- The top-level loop will need to know when no FD forwarding is in progress (as well as when no object references are being served, as it does now).
There are three issues here:
- How important is it that pola-run should return the child's exit code? Does this extend to returning the same signal number (which could be confusing)?
- Do we need to minimise the number of processes running?
- How much authority do we need to revoke when the sandboxed child process exits?
Does it matter if the server process continues to forward terminal access after the job has gone into the background? Since it is no longer the current job on the terminal, it will not be able to read from the FD, but since it does select() and not read() on the FD it should not get stopped by a SIGTTIN signal. (The sandboxed process will block if it tries to read from the proxy FD but it also will not be stopped by SIGTTIN.)
- That only applies if SIGTTIN has been enabled. Usually it is not. What happens then? I have seen processes fight over which is reading from the terminal.
- What happens if the sandboxed process does not try to read from its proxied terminal FD, but input is available? pola-run will swallow characters typed by the user or redirected from a file, upto the buffer size, and these will not be seen by the next process.
- Can pola-run detect when the process is no longer reading from stdin? (If not the server process would end up staying around indefinitely, if the forwarding were not revoked.) Yes: poll() sets the POLLERR flag.
How should EOF be handled? Pressing Ctrl-D at the terminal gives the appearance of end-of-file to the reading process (read() returns an empty string), yet the process can still read from the terminal after that (including reading further EOFs!). With sockets and pipes, an end-of-file condition is final, and can only be signalled by closing the FD of the other end.
Not being implemented
Conditional proxying
If stdout and stderr are the same tty file descriptor (the common case), we must replace them with the same proxy FD, to ensure synchronised output.
- How do we compare FDs? For file FDs this is not possible: we can compare the device/inode numbers returned by fstat(), but multiple FDs can be created on the same file. Comparing device/inode numbers might work for device FDs such as ttys and pipes though.
For any of stdin, stdout and stderr that are file FDs or pipe FDs, we should not proxy access. What should happen with FDs for unusual devices?
If stdin, stdout or stderr refer to two different tty FDs, this would be an unusual case. What should happen? The kernel might not allow a process to write to an FD of a tty other than its controlling tty.
Stage 2
The second stage would be to present the terminal to the sandboxed program as a tty device. i.e. Create a private pty device. Switch the user's tty to raw mode. See ssh for how this can be done. Note that window size information should be copied across.
