proot icon indicating copy to clipboard operation
proot copied to clipboard

Exit with signal-dependent returncode if tracee killed by a signal

Open llchan opened this issue 5 years ago • 10 comments

~It appears that the current implementation only saves the last exit status if WIFEXITED but not if WIFSIGNALED. Is this intentional? If not, we should add that. If it is intentional, then may I propose we add an opt-in mode that returns something bash-ish (128 + signal) or python-ish (-signal)? Even if it just returns a constant nonzero value, it would be more helpful than the current behavior which exits with 0.~

Edit: oops, spoke too soon, turns out in my test the proot process itself was also getting violently killed by the cgroup OOM killer. The existing return code of -1 works for now to indicate a failure, though I think it would still be useful to be able to return something based on the signal, perhaps something bash-ish (128 + signal) or python-ish (-signal)?

llchan avatar Sep 12 '18 00:09 llchan

Okay with verbose logging enabled I now get what's happening. The main process spawns a subprocess which returns with 0, causing proot to set last_exit_status=0. Then when the main process is killed, proot returns with the last exit status from the child rather than the main process.

I haven't read through the source too carefully yet, but if proot knows who the main process is, maybe it can treat signal terminations of that process differently? I sort of think the whole last_exit_status thing should only reflect the main process's exit code, but that may change existing behavior so maybe would have to sit inside an opt-in flag.

llchan avatar Sep 12 '18 01:09 llchan

Can you provide a more concrete example of your findings so that the issue can be reproduced?

oxr463 avatar Apr 19 '19 00:04 oxr463

If you run e.g. proot sleep 60 and send a signal to the sleep process, it currently returns -1 regardless of the signal. It would be helpful to return something signal-dependent so that it's possible to distinguish between e.g. a SIGINT and a SIGSEGV. Bash uses 128 + signal as the returncode, and python uses -signal as the returncode. Those may be reasonable conventions to adopt?

llchan avatar Apr 22 '19 20:04 llchan

Alright, here is what I have,

../src/proot sleep 60

# Then in a separate shell...
kill -s SIGSEGV $(pgrep proot)

# Finally, back in the first window...
proot warning: proot warning: signal 11 received from process 4218
signal 11 received from process 4218

I also tried SIGINT, but that didn't kill it at all?

oxr463 avatar Apr 23 '19 23:04 oxr463

Try sending the signal to $(pgrep sleep) or somesuch, what we want is for inner signals to bubble up.

llchan avatar Apr 24 '19 00:04 llchan

Oh, I see.

So SIGINT returns proot info: vpid 1: terminated with signal 2, and then SIGSEGV returns proot info: vpid 1: terminated with signal 11.

Now, running sleep 60, locally outside of PRoot, SIGINT returns 130, and SIGSEGV returns 139, respectively, (via echo $?).

What is this signal 128 from bash that you are referring to?

Thank you for bearing with me as I try to understand this issue.

oxr463 avatar Apr 24 '19 01:04 oxr463

Not sure if these are the official docs, but it's mentioned here. So for example, if the process gets killed by SIGINT=2 or SIGSEGV=11, bash sets the returncode to 128 + signum for 130 and 139, respectively.

llchan avatar Apr 24 '19 17:04 llchan

Since both Bash and Python are scripting languages, I think it would be more appropriate to try to use the conventions set forth in /usr/include/sysexits.h, as mentioned in the resource you supplied. Which do you think would be the most appropriate? Would this be an operating system error?

#define EX_OSERR 71 /* system error (e.g., can't fork) */

 *  EX_OSERR -- An operating system error has been detected.
 *      This is intended to be used for such things as "cannot
 *      fork", "cannot create pipe", or the like.  It includes
 *      things like getuid returning a user that does not
 *      exist in the passwd file.

oxr463 avatar Apr 25 '19 00:04 oxr463

Proot is a wrapper, and thus much closer to a shell. The important thing would be to reproduce transparently the WIFSIGNALED, WTERMSIG, WIFEXITED, WEXITSTATUS of the child process.

This can be accomplished by checking whether the child exited normally (and if so exit with the same status) or was killed by a signal (and if so kill the proot process with the same signal).

Failing that, the 128 - SIGNAL convention is quite widespread (shells, perl) and allows whoever calls proot to figure out what the wrapped command did.

mralusw avatar Oct 16 '20 13:10 mralusw

If you lose the normal-exit / signaled status of the child process and the signal number (if any), the only option for me is to

  • wrap the child process in a wrapper that saves WIFSIGNALED, WTERMSIG, WIFEXITED, WEXITSTATUS to a temporary file
  • call that wrapper from proot, then
  • parse the temporary file in yet another wrapper.

Which makes little sense, since proot should be a transparent wrapper itself.

mralusw avatar Oct 16 '20 14:10 mralusw