New issue
Advanced search Search tips

Issue 899270 link

Starred by 2 users

Issue metadata

Status: Assigned
Owner:
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Chrome
Pri: 2
Type: Bug



Sign in to add a comment

Security: Minijail0 hangs with defunct child process due to SECCOMP_RET_KILL_THREAD use

Project Member Reported by allenwebb@google.com, Oct 26

Issue description

This is a big of an odd scenario but instead of minijail successfully killing the child process on a non-whitelisted syscall the child ends up becoming defunct and minijail0 doesn't exit. In a daemon config this leads to a case where the daemon won't respawn.

To reproduce start with (this may have landed, but the seccomp policy change is a good reference):
https://chromium-review.googlesource.com/c/chromiumos/overlays/chromiumos-overlay/+/1239225

1) Revert the changes to the seccomp policy files.

2) emerge and deploy usbguard

3) From a root shell run:
/sbin/minijail0 -u usbguard -g usbguard -c 2 -l -p -n -e --uts \
      --profile=minimalistic-mountns \
      -b /sys,,1 \
      -S /usr/share/policy/usbguard-daemon-seccomp.policy \
      /usr/bin/usbguard generate-policy

------------------------------------------------------------------
Note that this works as intended when "uname: 1" is in the seccomp policy file, but hangs when it isn't present instead of exiting.

Running "ps aux | grep usbguard" after it hangs gives output similar to the following:
localhost ~ # ps aux | grep usbguard
root     15065  0.0  0.0   8656  1952 pts/0    S+   09:45   0:00 /sbin/minijail0 -u usbguard -g usbguard -c 2 -l -p -n -e --uts --profile=minimalistic-mountns -b /sys  1 -S /usr/share/policy/usbguard-daemon-seccomp.policy /usr/bin/usbguard generate-policy
root     15066  0.0  0.0   8656   212 ?        Ss   09:45   0:00 /sbin/minijail0 -u usbguard -g usbguard -c 2 -l -p -n -e --uts --profile=minimalistic-mountns -b /sys  1 -S /usr/share/policy/usbguard-daemon-seccomp.policy /usr/bin/usbguard generate-policy
usbguard 15067  0.2  0.0      0     0 ?        Zl   09:45   0:00 [usbguard] <defunct>
root     15140  0.0  0.0   4376   844 pts/4    S+   09:45   0:00 grep --colour=auto usbguard

If you then manually kill the "[usbguard] <defunct>" process id (15067 in the example), then minijail0 exits as expected.

------------------------------------------------------------
I tried this with uname.policy file and /usr/bin/uname and the problem doesn't show up. I am guessing it might have something to do with the way the signal handlers are setup in usbguard, but regardless minijail should terminate.

 
uname.policy
289 bytes Download
Cc: allenwebb@google.com
I write a small binary to test whether or not the signal blocking code in usbguard might be the cause, but that also doesn't show the issue. My next guess is child threads/processes might be needed to reproduce the bug.
Here the output of "strace -f usbguard generate-policy"
usbguard_generate_policy_strace.txt
57.5 KB View Download
It turns out the root cause related to spawning a child thread. The following source file and policy show the behavior. Here is the main():

int main() {
  std::cout << "Starting...\n";

  volatile bool not_done = true;
  std::thread child(sleep_loop, &not_done);

  do_uname();

  not_done = false;
  if (child.joinable()) {
    child.join();
  }
  return 0;
}

do_uname in the main thread is triggering the signal. For some reason the process becomes a zombie and minijail doesn't clean it up:

localhost ~ # ps aux | grep uname_test
root     17612  0.0  0.0   6564  1496 pts/4    S+   14:14   0:00 /sbin/minijail0 -S /home/uname_test.policy /usr/local/bin/uname_test
root     17613  0.0  0.0      0     0 ?        Zsl  14:14   0:00 [uname_test] <defunct>
root     17626  0.0  0.0   4376   848 pts/6    S+   14:15   0:00 grep --colour=auto uname_test

uname_test.policy
292 bytes Download
uname_test.cc
972 bytes View Download
Labels: OS-Chrome
Status: Untriaged (was: Unconfirmed)
I replaced the uname() call with socket() and it results in the same behavior. I built minijail with the debugging symbols, and sent it a SIGSTOP while it was hung and got the following stack trace:

#0  0x00007ffff78eca5a in __GI___waitpid (pid=19008, stat_loc=0x7fffffffe36c, options=0) at ../sysdeps/unix/sysv/linux/waitpid.c:29
#1  0x000055555555b66d in minijail_wait (j=0x555555573010) at ../minijail-8/libminijail.c:2894
#2  0x000055555555ffee in main (argc=<optimized out>, argv=0x7fffffffe4e0) at ../minijail-8/minijail0.c:74

It is possible this is an issue in libc.
This appears to be the same issue in a different software: https://github.com/eteran/edb-debugger/issues/382
Owner: allenwebb@google.com
Status: Assigned (was: Untriaged)

Comment 8 Deleted

Comment 9 Deleted

Labels: allpublic
part of the seccomp design is to allow per-thread seccomp filters.  that way one thread can run with more restrictive settings than others (e.g. a main thread to kick of various workers).  but it also means that the default kill behavior is to only kill the violating thread, not the whole process.  so while unfortunate for us, it's WAI form the kernel pov.

when you look at `ps` output, by default it merges threads and processes together.  if the main thread hit a seccomp failure, but its children did not, i could see the display make it look like a zombie process even while it's still running.
try using something like `ps auxH` to see threads-as-processes.

starting with linux-4.14, we've got newer actions to choose from:
- SECCOMP_RET_KILL is renamed to SECCOMP_RET_KILL_THREAD
- SECCOMP_RET_KILL_PROCESS will terminate the entire process

the commits i think we'd need to backport don't look too bad.
https://chromium.googlesource.com/chromiumos/third_party/kernel/+/0466bdb99e8744bc9befa8d62a317f0fd7fd7421%5E%21/
 3 files changed, 14 insertions(+), 3 deletions(-)
https://chromium.googlesource.com/chromiumos/third_party/kernel/+/4d3b0b05aae9ee9ce0970dc4cc0fb3fad5e85945%5E%21/
 2 files changed, 26 insertions(+), 14 deletions(-)
https://chromium.googlesource.com/chromiumos/third_party/kernel/+/fd76875ca289a3d4722f266fd2d5532a27083903%5E%21/
 7 files changed, 39 insertions(+), 32 deletions(-)

then we'd have to find a way of updating minijail.  i guess make it a compile-time knob for now that we can have CrOS opt-into ?
That sounds good to me.
to clarify, i don't have cycles atm to do the work :/
Yeah, I already have the issue assigned to me. I may trying and take care of it once I finish or get blocked on my other work.
Labels: Pri-2
Setting defect without priority to default.

Comment 15 by allenwebb@google.com, Jan 18 (4 days ago)

Cc: jorgelo@chromium.org

Comment 16 by jorgelo@chromium.org, Jan 18 (4 days ago)

Ah, this sounds like fun. I'm not surprised the kernel added a way to kill the entire process.

Comment 17 by vapier@chromium.org, Jan 19 (4 days ago)

Summary: Security: Minijail0 hangs with defunct child process due to SECCOMP_RET_KILL_THREAD use (was: Security: Minijail0 hangs with defunct child process)

Sign in to add a comment