![]() |
Jailing programs started from RC scripts |
![]() |
While porting Cardano Node I've got an idea to make the daemon run under a thin jail for added security. By “thin” I mean a minimal chroot environment (in contrary to full-system jails created by Bastille or iocage) that gets set up right before the daemon starts and destroyed after its shutdown. This post summarizes the knowledge I mined during the process of writing a RC service file for Cardano Node.
jail(8)
The jail command is used to create, modify and destroy jails. Creating a jail
usually means running some program specified by command or exec.start parameters.
When the jailed process exits (and all children processes spawned by it) the jail
itself gets destroyed. The path passed in either of these parameters is resolved
against jail's root, not the host filesystem.
Another important parameter is called path. It sets the directory that will be
used as a root for the jail.
Now we're ready to run a simple program under a jail:
mkdir /tmp/myjail
cp /rescue/echo /tmp/myjail/
jail -c path=/tmp/myjail command=/echo Hello from jail
Hello from jailLibrary dependencies
Why did I use echo program from the /rescue/ directory for the running example?
Well, let’s try the usual /bin/echo:
cp /bin/echo /tmp/myjail/
jail -c path=/tmp/myjail command=/echo Hello from jail
ELF interpreter /libexec/ld-elf.so.1 not found, error 2
jail: /echo Hello from jail: exited on signal 6Most programs in the wild are dynamically linked which means they need all
libraries they are linked to and the runtime linker to start. So, to make the
dynamic echo work we’d need to put all this stuff into the chroot too:
mkdir /tmp/myjail/libexec
cp /libexec/ld-elf.so.1 /tmp/myjail/libexec/
mkdir /tmp/myjail/lib
cp /lib/libc.so.7 /tmp/myjail/lib/
jail -c path=/tmp/myjail command=/echo Hello from jail
Hello from jailThe ldd utility can be used to find out which libraries are required by a given
executable:
ldd /usr/local/bin/cardano-node
/usr/local/bin/cardano-node:
libssl.so.111 => /usr/lib/libssl.so.111 (0x200000)
libcrypto.so.111 => /lib/libcrypto.so.111 (0x200000)
libz.so.6 => /lib/libz.so.6 (0x200000)
libutil.so.9 => /lib/libutil.so.9 (0x200000)
libgmp.so.10 => /usr/local/lib/libgmp.so.10 (0x200000)
libm.so.5 => /lib/libm.so.5 (0x200000)
librt.so.1 => /usr/lib/librt.so.1 (0x200000)
libdl.so.1 => /usr/lib/libdl.so.1 (0x200000)
libffi.so.8 => /usr/local/lib/libffi.so.8 (0x200000)
libthr.so.3 => /lib/libthr.so.3 (0x200000)
libc.so.7 => /lib/libc.so.7 (0x200000)Instead of copying all of them by hand the following shell incantation can be used:
ldd ${command} | cut -s -d " " -f 3 | grep -E '^(/lib|/usr)' | sort -u | xargs -I % cp % ${jail_root}/lib/devfs
A lot of programs make access to device pseudo-files under the /dev/ directory.
Missing /dev/random may cause obscure runtime errors, which are hard to diagnose.
To provide an access to the /dev/ stuff for the jailed program the devfs
filesystem has to be mounted inside the jail:
mkdir /tmp/myjail/dev
mount -o ruleset=4 -t devfs devfs /tmp/myjail/devThe ruleset=4 parameter corresponds to the [devfsrules_jail=4] section of
the /etc/defaults/devfs.rules file. This ruleset defines a minimal set of device
pseudo-files that should be available for a common jail.
Don't forget to unmount this path after the jail is destroyed:
umount /tmp/myjail/devAccessing parts of the host filesystem
Jailed program can't access any paths on the host filesystem on their own. To allow them to do so the loopback filesystem is used:
mkdir /tmp/not_in_jail
mkdir /tmp/myjail/in_jail
mount_nullfs /tmp/not_in_jail /tmp/myjail/in_jailNow the jail can access the contents of /tmp/not_in_jail directory as /in_jail.
Roughly speaking, mount_nullfs acts as ln -s but with ability to span chroot
boundaries. Files can be mounted with mount_nullfs just like directories.
The cardano_node RC script uses this trick to hide the fact that the daemon runs
under the jail. To do that the config and log directories provided by the user
are null-mounted into the jail to make them reachable by the daemon.
Running under a different user
Setting exec.jail_user jail parameter will cause the starting command to run
under specified user. If exec.system_jail_user is also present, the user
information is taken from the host /etc/passwd. Thanks to this parameter, no
in-jail user setup should be performed each time the jail starts.
Jail networking
To allow the jail to listen for and accept incoming connections it is sufficient
to pass ip4=inherit, ip6=inherit and host=inherit jail parameters during
its creation.
Wrapping up
There are a lot more parameters worth mentioning, but man 8 jail will do it
better. For a complete example of the jailed service script see
${PORTSDIR}/net-p2p/cardano-node/files/cardano_node.in.



