Files
finit/doc/build.md
T
Joachim Wiberg c28acf21a1 test: fuzz target for the message parser
__msg_parse() turns bytes off a socket into pointers, before anything
has vouched for the peer, and it is the only place in libink that
does.  It had no test of its own beyond whatever the other tests
happened to send it, all of it well-formed.

The target checks the parser's contract, not merely that it survived.
A header field must point into the header field array, and terminate
inside it, and the parse must never claim more bytes than it was
handed.  Crash-only would pass a parser that walked into the body and
returned fields from there, since those bytes were handed over too.
The expected bounds are derived from the raw header rather than from
the parser, so the two have to agree independently.

Every input is copied into an allocation sized to it first.  Reading
past the end of a roomy buffer stays inside the allocation and the
sanitizer never sees it; against an exact one the same read is a
fault, which is where the sharpest findings come from.

Under libFuzzer it is an ordinary fuzz target and named files replay,
which is how a find gets reproduced.  With no arguments it runs a
fixed sweep -- every truncation, every single-byte corruption, every
value of the length that decides where the header ends, and seeded
garbage -- so the suite covers the same contract on every build,
without clang or a corpus in the tree.  It takes 40 ms.

CI fuzzes it properly on every pull request, keeps the crashers, and
carries the corpus between runs so it reaches deeper over time than
any single run can.  Note that clang links the fuzzer runtime against
the newest GCC tree it finds, so the libstdc++ headers have to match
that one and not the default compiler, which is worth saying since
installing the obvious package leaves you exactly where you started.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00

8.9 KiB

Building Finit

Finit comes with a traditional configure script to control features and optional plugins to enable. It depends on three external libraries:

Important

Most free/open source software packages that use configure default to install to /usr/local. However, some Linux distributions do no longer search that path for installed software, e.g. Fedora and Alpine Linux. To get finit's configure script to find its dependencies you have to help the pkg-config tool a bit if you do not change the default prefix path:

PKG_CONFIG_LIBDIR=/usr/local/lib/pkgconfig ./configure

The configure script checks for all dependencies, including the correct version of the above mentioned libraries. Currently required versions:

  • libite v2.2.0, or later
  • libuEv v2.2.0, or later
  • libConfuse v3.3, or later

Configure

Below are a few of the main switches to configure:

  • --prefix=..: Usually you want to set this to /usr, default is the GNU default: /usr/local

  • --exec-prefix=..: This you want to set to the empty string, or /, to ensure the programs finit and initctl are installed to the proper locations. Linux expects an "init" in /sbin, default: --prefix

  • --sysconfdir=..: follows --prefix, you likely want it to be /etc

  • --localstatedir=..: follows --prefix, you likely want /var

  • --enable-static: Build Finit statically. The plugins will be built-ins (.o files) and all external libraries, except the C library will be linked statically. Privileged D-Bus methods then accept only root, see Authorization

  • --enable-kernel-cmdline: Enable Finit pre-4.1 parsing of init args from /proc/cmdline, this is not recommended since Finit may be running as the init for container apps that can see the host's /proc filesystem

  • --disable-dbus: Opt out of Finit's built-in D-Bus support, enabled by default. See D-Bus Integration for what it provides. Not to be confused with --disable-dbus-plugin below, which only governs starting an external dbus-daemon

  • --enable-alsa-utils-plugin: Enable the optional alsa-utils.so sound plugin.

  • --disable-dbus-plugin: Drop the dbus.so plugin, which launches dbus-daemon at boot. Enabled by default; the plugin does nothing on a system that has no dbus-daemon installed, so opting out is only needed to keep init from starting a bus on a system that has one. Unrelated to the built-in bus, see --disable-dbus above.

  • --enable-resolvconf-plugin: Enable the resolvconf.so optional plugin.

  • --enable-x11-common-plugin: Enable the optional X Window x11-common.so plugin.

  • --with-sulogin: Enable bundled sulogin program. Default is to use the system sulogin(8). The sulogin shipped with Finit allows password-less login if the root user is disabled or has no password at all.

For more configure flags, see ./configure --help

Note

The configure script is not available in the GIT sources. It is however included in (officially supported) released tarballs. The idea is that you should not need GNU autotools to build, only the above mentioned dependencies, a POSIX shell, a C compiler and make. Any contributing to Finit can generate it from configure.ac using the autogen.sh script.

Example

First, unpack the archive:

$ tar xf finit-4.3.tar.gz
$ cd finit-4.3/

Then configure, build and install:

$ ./configure --prefix=/usr                 --exec-prefix=         \
              --sysconfdir=/etc             --localstatedir=/var   \
              --with-keventd                --with-watchdog
$ make
.
.
.
$ DESTDIR=/tmp/finit make install

In this example the finit-4.3.tar.gz archive is unpacked to the user's home directory, configured, built and installed to a temporary staging directory. The environment variable DESTDIR controls the destination directory when installing, very useful for building binary standalone packages.

Finit 4.1 and later can detect if it runs on an embedded system, or a system that use BusyBox tools instead of udev & C:o. On such systems mdev instead of udev is used. However, remember to also change the Linux config to:

CONFIG_UEVENT_HELPER_PATH="/sbin/mdev"

Tip

If you run into problems starting Finit, take a look at finit.c. One of the most common problems is a custom Linux kernel build that lack CONFIG_DEVTMPFS. Another is too much cruft in the system /etc/fstab.

Testing

make check runs the test suite in a private namespace, so it is safe on a running system. It needs unshare and, on Ubuntu, unprivileged user namespaces enabled:

sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
make check

The D-Bus message parser is the only place in Finit where bytes off a socket become pointers, so it also has a fuzz target. make check runs it as a fixed sweep, which takes milliseconds and needs nothing beyond the normal build. To fuzz it properly, build it with clang and libFuzzer:

clang -fsanitize=fuzzer,address -DLINK_FUZZ_LIBFUZZER -D_GNU_SOURCE \
      -I libink -I . -o fuzz-msg-parse                              \
      test/src/fuzz-msg-parse.c libink/*.c
mkdir -p .fuzz-corpus
./fuzz-msg-parse .fuzz-corpus -max_total_time=300

Give it a corpus directory as above and it saves what it learns there, so the next run picks up where this one left off instead of starting cold. Nothing writes to it unless you name it: without the argument libFuzzer keeps everything in memory and the run leaves only crashes behind. make distclean clears the corpus and the target.

Once a corpus has grown, most of it reaches code some earlier input already reached. Minimise it:

./fuzz-msg-parse -merge=1 .fuzz-corpus-min .fuzz-corpus
rm -rf .fuzz-corpus && mv .fuzz-corpus-min .fuzz-corpus

Run ./configure first, the target needs the generated config.h. If the link fails with cannot find -lstdc++, install the libstdc++ headers matching the newest GCC on the system, not the default one: clang picks the newest tree it finds, and that is the one that needs them. CI runs this target on every pull request, carrying its corpus between runs so it reaches deeper over time.

Feed a file back to the target to reproduce a find:

./fuzz-msg-parse crash-3f2a...

Running

Having successfully built Finit it is now be time to take it for a test drive. The make install attempts to set up finit as the system system init, /sbin/init, but this is usually a symlink pointing to the current init.

So either change the symlink, or change your boot loader (GRUB, LOADLIN, LILO, U-Boot/Barebox or RedBoot) configuration to append the following to the kernel command line:

append="init=/sbin/finit"

Remember to also set up an initial /etc/finit.conf before rebooting!

Recovery

To rescue a system with Finit, append the following to the kernel command line:

append="init=/sbin/finit rescue"

This tells Finit to start in a very limited recovery mode, no services are loaded, no filesystems are mounted or checked, and no networking is enabled. The default Finit rescue mode configuration is installed into /lib/finit/rescue.conf, which can be safely removed or changed.

By default the a root shell, without login, is started.

Important

In rescue mode initctl will not work, the same goes for reboot, shutdown, and poweroff commands, provided they are the Finit versions of these commands. Use the -f flag to force the action.

Debugging

Edit, or append to, the kernel command line: remove quiet to enable kernel messages and add finit.debug to enable Finit debug messages.

append="init=/sbin/finit -- finit.debug"

Notice the -- separator.

To debug startup issues, in particular issues with getty/login, add the following to your Finit .conf file:

tty board {
    runlevel = "12345789"
    notty    = true
    noclear  = true
}

The notty option ensures reusing the stdin/stdout set up by the kernel. Remember, this is only for debugging and would leave your production system potentially wide open.

There is also a rescue shell available, in case Finit crashes and the kernel usually reboots: configure --enable-emergency-shell. However, the behavior of Finit is severely limited when this is enabled, so use it only for debugging start up issues when Finit crashes.

Caution

None of these options should be enabled on production systems since they can potentially give a user root access.