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 which relies on tini (see also this write up on tini for other uses cases where this feature could be useful).
I came up with this draft patch mattmart3@2b53c1a 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:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
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] <defunct>
242440 242443 [zombies] <defunct>
242440 242444 [zombies] <defunct>
242440 242445 [zombies] <defunct>
242440 242446 [zombies] <defunct>
242440 242447 [zombies] <defunct>
242440 242448 [zombies] <defunct>
242440 242449 [zombies] <defunct>
242440 242450 [zombies] <defunct>
242440 242451 [zombies] <defunct>
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 #57, even though I am not sure that is actually necessary (see my last recent comment on that issue).
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
ONCEmode. 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 inEXECVEmode, already creates an init process in the container. HoweverEXECVEmode 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
--initoption 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 which relies on tini (see also this write up on tini for other uses cases where this feature could be useful).I came up with this draft patch mattmart3@2b53c1a that only adds the feature for the
ONCEmode. It first callsunshare()on the nsjail parent process, then spawns theinitprocess and only then it spawns the child for the actual target process. In this way both theinitprocess and the target process run in the isolated environment. I tried to enable it for theLISTENandRERUNmodes 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 calledunshare().Following an example using a simple program that creates zombies:
Launching njail without
--initZombies are not reaped:
Launching nsjail with
--initZombies are reaped:
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_newpidoption is provided. This was introduced in #57, even though I am not sure that is actually necessary (see my last recent comment on that issue).