New option to create init process
- Dominant language
- C++
- Stars
- 4.1k
- Forks
- 371
- Avg merge
- 4d 7h
- Merged PRs (30d)
- 7
Description
Hi all,
I'm involved in a project where nsjail is used to isolate processes in a Linux system and it runs with the default `ONCE` mode. Part of the system testing involves running stress-ng in sequence mode inside the nsjail container. In this case stress-ng runs some tests that spawn zombies whose are never reaped since there is no init process running inside the container, leading to file description exhaustion. While digging in the nsjail code I've noticed that nsjail in `EXECVE` mode, already creates an init process in the container. However `EXECVE` mode might not always be desirable as for instance the execve'd target process would not run in an isolated pid namespace.
Therefore I thought to add an `--init` option that would spawn an init reaper process inside the container for the other modes as well. A similar feature is provided for instance by [docker run --init](https://docs.docker.com/reference/cli/docker/container/run/#init) which relies on [tini](https://github.com/krallin/tini/) (see also this [write up on tini](https://github.com/krallin/tini/issues/8#issuecomment-146135930) for other uses cases where this feature could be useful).
I came up with this draft patch https://github.com/mattmart3/nsjail/commit/2b53c1ab14d8857424112f95fc04e0bf1dbfa1ed that only adds the feature for the `ONCE` mode. It first calls `unshare()` on the nsjail parent process, then spawns the `init` process and only then it spawns the child for the actual target process. In this way both the `init` process and the target process run in the isolated environment. I tried to enable it for the `LISTEN` and `RERUN` modes as well, but after the first instance it doesn't work anymore, I'm not sure why but it looks like it has something to do with how the namespace setup affects the parent nsjail process that called `unshare()`.
Following an example using a simple program that creates zombies:
```c zombies.c
#include
#include
#include
#include
int main(void)
{
pid_t pid = fork();
if (pid == 0) {
for (int i = 0; i < 10; i++) {
pid_t pid = fork();
if (pid == 0) {
printf("[grandchild] exit\n");
exit(0);
}
}
printf("[child] exit\n");
exit(0);
}
printf("[parent] reap child and sleep for 10 secs\n");
int status;
wait(&status);
sleep(10);
printf("[parent] exit\n");
exit(0);
}
```
Launching njail without `--init`
```
> ./nsjail --chroot / -- /usr/bin/zombies
[I][2026-04-16T18:12:01+0200] Mode: STANDALONE_ONCE
[I][2026-04-16T18:12:01+0200] Jail parameters: hostname:'NSJAIL', chroot:'/', process:'/usr/bin/zombies', bind:[::]:0, max_conns:0, max_conns_per_ip:0, time_limit:600, daemonize:false, clone_newnet:true, clone_newuser:true, clone_newns:true, clone_newpid:true, clone_newipc:true, clone_newuts:true, cgroupv2:false, keep_caps:false, disable_no_new_privs:false, max_cpus:0
[I][2026-04-16T18:12:01+0200] Mount: '/' -> '/' type:'' options:'' dir:true
[I][2026-04-16T18:12:01+0200] Mount: '/proc' type:'proc' options:'' dir:true
[I][2026-04-16T18:12:01+0200] Uid map: inside_uid:1000 outside_uid:1000 count:1 newuidmap:false
[I][2026-04-16T18:12:01+0200] Gid map: inside_gid:1000 outside_gid:1000 count:1 newgidmap:false
[I][2026-04-16T18:12:01+0200] Executing '/usr/bin/zombies' for '[STANDALONE MODE]'
[parent] reap child and sleep for 10 secs
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[child] exit
[grandchild] exit
[grandchild] exit
```
Zombies are not reaped:
```
> ps ax -o ppid,pid,command | grep 'nsjail\|zombies'
196270 242439 ./nsjail --chroot / -- /usr/bin/zombies
242439 242440 /usr/bin/zombies
242440 242442 [zombies]
242440 242443 [zombies]
242440 242444 [zombies]
242440 242445 [zombies]
242440 242446 [zombies]
242440 242447 [zombies]
242440 242448 [zombies]
242440 242449 [zombies]
242440 242450 [zombies]
242440 242451 [zombies]
```
Launching nsjail with `--init`
```
> ./nsjail --chroot / --init -- /usr/bin/zombies 1
[I][2026-04-16T18:13:32+0200] Mode: STANDALONE_ONCE
[I][2026-04-16T18:13:32+0200] Jail parameters: hostname:'NSJAIL', chroot:'/', process:'/usr/bin/zombies', bind:[::]:0, max_conns:0, max_conns_per_ip:0, time_limit:600, daemonize:false, clone_newnet:true, clone_newuser:true, clone_newns:true, clone_newpid:true, clone_newipc:true, clone_newuts:true, cgroupv2:false, keep_caps:false, disable_no_new_privs:false, max_cpus:0
[I][2026-04-16T18:13:32+0200] Mount: '/' -> '/' type:'' options:'' dir:true
[I][2026-04-16T18:13:32+0200] Mount: '/proc' type:'proc' options:'' dir:true
[I][2026-04-16T18:13:32+0200] Uid map: inside_uid:1000 outside_uid:1000 count:1 newuidmap:false
[I][2026-04-16T18:13:32+0200] Gid map: inside_gid:1000 outside_gid:1000 count:1 newgidmap:false
[I][2026-04-16T18:13:32+0200] Executing '/usr/bin/zombies' for '[STANDALONE MODE]'
[parent] reap child and sleep for 10 secs
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[grandchild] exit
[child] exit
```
Zombies are reaped:
```
> ps ax -o ppid,pid,command | grep 'nsjail\|zombies'
196270 243217 ./nsjail --chroot / --init -- /usr/bin/zombies
243217 243218 ./nsjail --chroot / --init -- /usr/bin/zombies
243217 243219 /usr/bin/zombies
```
Would a feature like this be considered? I would really appreciate some feedback and your thoughts on this. Also please consider that this was the first time I looked on how container creation works at this level, so I might definitely have missed some important detail.
As a side note, in my patch I kept the logic to avoid creating the init process if the `--disable_clone_newpid` option is provided. This was introduced in https://github.com/google/nsjail/issues/57, even though I am not sure that is actually necessary (see my last recent comment on that issue).
Contributor guide
Assessment
This issue has not been assessed yet.