s6: the s6-fdholderd program
Keywords: s6 s6-fdholderd fd-holding fd-holder fd unix socket activation server daemon

s6
Software
skarnet.org

# The `s6-fdholderd` program

`s6-fdholderd` is the serving part of the s6-fdholder-daemon fd-holding server. It assumes that its stdin is a bound and listening Unix domain socket; it accepts connections from clients connecting to that socket, and stores and retrieves file descriptors on their behalf.

## Interface

         s6-fdholderd [ -1 ] [ -v verbosity ] [ -c maxconn ] [ -n maxfds ] [ -t clienttimeout ] [ -T lameducktimeout ] [ -i rulesdir | -x rulesfile ]

- s6-fdholderd accepts connections from clients to an already bound and listening SOCK_STREAM Unix domain socket which is its standard input.
- Depending on the verbosity level, it logs what it does to stderr.
- It runs until killed by a signal (normally SIGTERM). When s6-fdholderd is killed, all the fds it is currently holding are lost; if they need to be preserved, the admin should make sure to transfer them beforehand.
- Client connections are short-lived. Clients generally perform one operation, then disconnect.
- Possible operations include:
  - Storing a file descriptor
  - Retrieving a file descriptor
  - Deleting a file descriptor
  - Listing stored file descriptor identifiers
  - Getting the whole server state
  - Setting the whole server state, or more accurately adding a set of file descriptors to the already existing state

## Options

- `-1` : write a newline to stdout, and close stdout, right before entering the client-accepting loop. If stdout is suitably redirected, this can be used by monitoring programs to check when the server is accepting connections. See this page for more information on readiness notification.
- `-v verbosity` : be more or less verbose. verbosity can be 0 (quiet), 1 (normal), or 2 or more (verbose).
- `-c maxconn` : accept at most maxconn concurrent connections. Default is 16. It is impossible to set it higher than the value of the S6_FDHOLDER_MAX macro, i.e. 256. Client connections to this server are short-lived, so this number needs not be too high. Every client connection eats up one available file descriptor, so it is best for maxconn to be as small as possible.
- `-n maxfds` : store at most maxfds file descriptors. Default is 1000. It is impossible to set it higher than the number of files that can be opened by the s6-fdholderd process, minus a few descriptors needed for correct operation. Before running s6-fdholderd, make sure to properly adjust the number of openable files of the current process.
- `-t clienttimeout` : disconnect a client if it's in the middle of an operation and it has not written or read any data in clienttimeout milliseconds. By default, clienttimeout is 0, which means infinite.
- `-T lameducktimeout` : give clients lameducktimeout milliseconds to finish their current operation before exiting after receiving a SIGTERM. By default, lameducktimeout is 0, which means infinite.
- `-x rulesfile` : read access rights configuration from CDB file rulesfile.
- `-i rulesdir` : read access rights configuration from the filesystem in directory rulesdir.

## Signals

- SIGTERM: enter lameduck mode, then exit when no more operation is pending.
- SIGHUP: reopen rulesfile, if s6-fdholderd has been run with the `-x` option. It is not necessary to send s6-fdholderd a SIGHUP when the `-i` option is used instead: configuration changes in the filesystem are automatically picked up.

## Identifiers

- Every file descriptor is stored in the s6-fdholderd daemon via the s6-fdholder-store program, with an identifier. That identifier is a zero-terminated character string, containing 1 to 255 characters.
- Any non-null character can be used in an identifier. Non-printable or special characters will be quoted when printed by s6-fdholder-list. However, it is recommended to only use reasonable characters in identifiers: clients should be able to know at a glance what file descriptor is represented by an identifier. Identifiers have no special meaning to the server.
- A good convention is to use `unix:path/to/socket` for Unix domain sockets and `protocol:ip:port` for INET domain sockets.
- An identifier is chosen by the storing client, within the limits of what the server authorizes it to use.
- The retrieving client must know the exact identifier corresponding to a descriptor to be able to retrieve that descriptor. It must also be authorized by the server.
- When an identifier is in use, it cannot be used again to store another descriptor. However, once the descriptor has been deleted or has expired, it is possible to reuse the same identifier.

## Configuration

Before running s6-fdholderd (or its wrapper s6-fdholder-daemon), it is necessary to configure it. This is done by a series of rules, or ruleset, stored in either a rulesfile in the CDB format, or in a rulesdir, i.e. a directory in the filesystem following a certain format. s6-fdholderd will refuse to run if neither the `-i` nor the `-x` option has been provided.

Rulesets can be converted between the rulesdir and rulesfile formats with the s6-accessrules-cdb-from-fs and s6-accessrules-fs-from-cdb conversion tools.

### Rules format

The rules file, or rules directory, follows the s6 accessrules format for uid and gid checking. For every connecting client, s6-fdholderd matches the uid and gid of the client against the provided ruleset, and determines what the client is authorized to do.

By default, no client is allowed to do anything - not even connect to the server. Even `root`, the super-user, will be denied access. That is why it is essential to create a sensible ruleset prior to running the server in order to do anything useful.

The various rights that a client can have are the following (using a rulesdir as an example, but a rulesfile works the same way):

- Connect to the server. This is a prerequisite for doing anything. It will allow a client to perform "public" operations, ones that do not require specific access rights other than connecting. (There are no such operations for now, but it could change in the future; for now, when you allow a client to connect to the server, make sure to give him other rights too.) This right is given if an `allow` file is found in one of the subdirectories checked by s6_accessrules_keycheck_uidgid. For instance, to allow everyone to connect, touch `rulesdir/uid/default/allow`.

The other rights are defined in the "environment" part of the ruleset:

- File descriptor storage rights. This will be checked for storage and deletion of individual file descriptors. This right is given if a non-empty file named `S6_FDHOLDER_STORE_REGEX` is found in the `env/` subdirectory of one of the subdirectories checked by s6_accessrules_keycheck_uidgid. This file should contain a single line, which will be interpreted as an Extended Regular Expression by s6-fdholderd; the regular expression describes the set of identifiers that the client is allowed to use to store file descriptors. For instance, `^unix:/tmp/` indicates that a client that matches this rule will be allowed to store or delete file descriptors using any identifier starting with `unix:/tmp/`.
- File descriptor retrieval rights. This will be checked for retrieval of individual file descriptors. This right is given if a non-empty file named `S6_FDHOLDER_RETRIEVE_REGEX` is found in the `env/` subdirectory of one of the subdirectories checked by s6_accessrules_keycheck_uidgid. This file should contain a single line, which will be interpreted as an Extended Regular Expression by s6-fdholderd; the regular expression describes the set of identifiers that the client is allowed to use to retrieve file descriptors. For instance, `^unix:/tmp/` indicates that a client that matches this rule will be allowed to retrieve file descriptors that are identified by strings starting with `unix:/tmp/`.
- Listing rights. This will be checked for clients wanting to list the identifiers of the descriptors currently stored in the server. This right is given if a non-empty file named `S6_FDHOLDER_LIST` is found in the `env/` subdirectory of one of the subdirectories checked by s6_accessrules_keycheck_uidgid.
- Dump reading rights. This will be checked for clients wanting to copy the whole state of the server. This right is given if a non-empty file named `S6_FDHOLDER_GETDUMP` is found is the `env/` subdirectory of one of the subdirectories checked by s6_accessrules_keycheck_uidgid. This is very powerful: you should only give this right to `root`, or to a dedicated uid that is only used to perform dump transfers.
- Dump writing rights. This will be checked for clients wanting to copy an entire set of file descriptors into the server. This right is given if a non-empty file named `S6_FDHOLDER_SETDUMP` is found is the `env/` subdirectory of one of the subdirectories checked by s6_accessrules_keycheck_uidgid. This is very powerful: you should only give this right to `root`, or to a dedicated uid that is only used to perform dump transfers.

### Configuration examples

Assuming you want to run an s6-fdholderd daemon in the `/service/fdholder` directory with the `-i rules` option, you should:

- Prepare the rules directory: `mkdir /service/fdholder/rules ; cd /service/fdholder/rules ; mkdir uid gid`
- Allow a few users, or everyone, to connect. To allow root to connect: `mkdir uid/0 ; touch uid/0/allow`. To allow everyone to connect: `mkdir uid/default ; touch uid/default/allow`.

Depending on your policy, you should now give certain rights to certain users or groups. For instance:

- To allow user number 50 to perform dump transfers from and to this server: `mkdir -p uid/50/env ; touch uid/50/allow ; echo > uid/50/env/S6_FDHOLDER_GETDUMP ; echo > uid/50/env/S6_FDHOLDER_SETDUMP`
- To allow user number 72 to store a descriptor under the name `foobar` and only this name: `mkdir -p uid/72/env ; touch uid/72/allow ; echo '^foobar$' > uid/72/env/S6_FDHOLDER_STORE_REGEX`
- To allow users having 23 as their primary group number to retrieve file descriptors with an identifier containing `foo`, then one character, then `bar`: `mkdir -p gid/23/env ; touch gid/23/allow ; echo foo.bar > gid/23/env/S6_FDHOLDER_RETRIEVE_REGEX`
- To allow the same users to list all identifiers: `echo > gid/23/env/S6_FDHOLDER_LIST`
- To allow everyone to dump entire states into the server (never do this! it's only an example): `mkdir -p uid/default/env ; touch uid/default/allow ; echo > uid/default/env/S6_FDHOLDER_SETDUMP`.

## Notes

- s6-fdholderd is meant to be execve'd into by a program that gets the listening socket. That program is normally s6-ipcserver-socketbinder, which creates the socket itself; but it can be a different one if the socket is to be obtained by another means, for instance if it has been retrieved from another fd-holding daemon.
- s6-fdholderd will store any open file descriptor, without discriminating on its type. However, it makes more sense to store certain file descriptor types than others: for instance, Unix domain or INET domain sockets, or named pipes, are good candidates for fd-holding. On the other hand, it makes little sense to fd-hold regular files, and if done anyway, the results can be surprising, because the read/write file offset is stored with the descriptor, and no automatic rewind is performed by the daemon.
- Despite there being access control for listing, the security of the system should not depend on a client not knowing what identifier a certain descriptor is stored under. If you need to hold descriptors that only a few programs are supposed to access, you can always run a separate s6-fdholderd instance in a private directory with a configuration tailored to your needs - and you can even make the name of the listening socket private. s6-fdholderd is lightweight, you can start as many instances as you need, and you can run them as long as you need then kill them with SIGTERM.
- s6-fdholderd pre-allocates its storage at start, in the stack. It uses a small amount of heap memory for communication with a client, but frees it as soon as the client disconnects. It should never run out of memory in normal usage, even if used intensively.