Skip to content

Commit be2a0ec

Browse files
committed
Add support for Linux capabilities
Implement Linux capability support for services, allowing them to run with minimal required privileges instead of running as root. This uses the modern IAB (Inheritable, Ambient, Bounding) API from libcap.
1 parent c23952c commit be2a0ec

File tree

7 files changed

+267
-3
lines changed

7 files changed

+267
-3
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- name: Install dependencies
3535
run: |
3636
sudo apt-get -y update
37-
sudo apt-get -y install pkg-config tree jq libuev-dev libite-dev
37+
sudo apt-get -y install pkg-config tree jq libuev-dev libite-dev libcap-dev
3838
- uses: actions/checkout@v4
3939
- name: Static Finit
4040
run: |

configure.ac

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ AC_ARG_ENABLE(cgroup,
7272
AS_HELP_STRING([--disable-cgroup], [Disable cgroup v2 support, default: autodetect from /sys/fs/cgroup]),,[
7373
enable_cgroup=yes])
7474

75+
AC_ARG_ENABLE(libcap,
76+
AS_HELP_STRING([--disable-libcap], [Disable Linux capabilities support]),,[
77+
enable_libcap=yes])
78+
7579
AC_ARG_ENABLE(redirect,
7680
AS_HELP_STRING([--disable-redirect], [Disable redirection of service output to /dev/null]),,[
7781
enable_redirect=yes])
@@ -204,6 +208,16 @@ AS_IF([test "x$enable_kernel_logging" = "xyes"], [
204208
AS_IF([test "x$enable_cgroup" = "xyes"], [
205209
AC_DEFINE(CGROUP2_ENABLED, 1, [Autodetect cgroup v2 support from /sys/fs/cgroup])])
206210

211+
AS_IF([test "x$enable_libcap" = "xyes"], [
212+
AC_CHECK_LIB([cap], [cap_from_text], [
213+
AC_DEFINE(HAVE_LIBCAP, 1, [Have libcap for Linux capabilities support])
214+
LIBS="$LIBS -lcap"
215+
], [
216+
AC_MSG_WARN([libcap not found, capabilities support disabled])
217+
enable_libcap=no
218+
])
219+
])
220+
207221
AS_IF([test "x$enable_fastboot" = "xyes"], [
208222
AC_DEFINE(FAST_BOOT, 1, [Skip fsck check on filesystems listed in /etc/fstab])])
209223

@@ -419,6 +433,7 @@ Optional features:
419433
Built-in logrotate....: $enable_logrotate
420434
Replacement libsystemd: $with_libsystemd
421435
Use cgroup v2.........: $enable_cgroup
436+
Use libcap............: $enable_libcap
422437
Parse kernel cmdline..: $enable_kernel_cmdline
423438
Keep kernel logging...: $enable_kernel_logging
424439
Skip fsck check.......: $enable_fastboot

doc/config/capabilities.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
Linux Capabilities
2+
==================
3+
4+
Finit supports Linux capabilities, allowing services to run with minimal
5+
required privileges instead of running as root. This significantly improves
6+
system security by following the principle of least privilege.
7+
8+
## Overview
9+
10+
Linux capabilities divide the traditional root privileges into distinct units
11+
that can be independently granted to processes. For example, a web server only
12+
needs the capability to bind to privileged ports (< 1024), not full root access.
13+
14+
Finit uses the modern IAB (Inheritable, Ambient, Bounding) API from libcap,
15+
which is the same approach used by other modern service managers like dinit.
16+
17+
## Basic Usage
18+
19+
Capabilities are specified using the `caps:` directive in service configuration:
20+
21+
```conf
22+
service [2345] name:nginx \
23+
@www-data:www-data \
24+
caps:^cap_net_bind_service \
25+
/usr/sbin/nginx -g 'daemon off;' \
26+
-- Web server
27+
```
28+
29+
This example allows nginx to bind to privileged ports (like 80 and 443) while
30+
running as the unprivileged `www-data` user.
31+
32+
## IAB Format
33+
34+
The capability string uses the IAB (Inheritable, Ambient, Bounding) format
35+
with the following prefixes:
36+
37+
- `^` **Ambient** (and Inheritable) - **Recommended for most use cases**
38+
- Capabilities survive across `exec()` calls
39+
- Automatically raised to effective after exec
40+
- Example: `^cap_net_bind_service`
41+
42+
- `%` **Inheritable** only
43+
- Requires the executed binary to have matching file capabilities
44+
- Less common, more complex setup
45+
- Example: `%cap_net_admin`
46+
47+
- `!` **Bounding** - Block capability from bounding set
48+
- Prevents the service from ever acquiring this capability
49+
- Useful for security hardening
50+
- Example: `!cap_sys_admin`
51+
52+
Multiple capabilities can be specified as a comma-separated list:
53+
54+
```conf
55+
caps:^cap_net_raw,^cap_net_admin,^cap_net_bind_service
56+
```
57+
58+
## Common Use Cases
59+
60+
### Web Server (Privileged Ports)
61+
62+
Allow a web server to bind to ports 80 and 443 without running as root:
63+
64+
```conf
65+
service [2345] name:webserver \
66+
@www-data:www-data \
67+
caps:^cap_net_bind_service \
68+
/usr/sbin/nginx -g 'daemon off;'
69+
```
70+
71+
### Network Monitoring (Raw Sockets)
72+
73+
Allow packet capture without root privileges:
74+
75+
```conf
76+
service [2345] name:tcpdump \
77+
@tcpdump \
78+
caps:^cap_net_raw,^cap_net_admin \
79+
/usr/sbin/tcpdump -i eth0 -w /var/log/capture.pcap
80+
```
81+
82+
### NTP Daemon (System Time)
83+
84+
Allow time synchronization without full root:
85+
86+
```conf
87+
service [2345] name:ntpd \
88+
@ntp \
89+
caps:^cap_sys_time,^cap_sys_nice \
90+
/usr/sbin/ntpd -n
91+
```
92+
93+
## Available Capabilities
94+
95+
Common capabilities include (see `man 7 capabilities` for the complete list):
96+
97+
- `cap_chown` - Make arbitrary changes to file UIDs and GIDs
98+
- `cap_dac_override` - Bypass file read, write, and execute permission checks
99+
- `cap_dac_read_search` - Bypass file read permission checks
100+
- `cap_fowner` - Bypass permission checks on operations that normally require filesystem UID
101+
- `cap_kill` - Bypass permission checks for sending signals
102+
- `cap_net_admin` - Perform various network-related operations
103+
- `cap_net_bind_service` - Bind to privileged ports (< 1024)
104+
- `cap_net_raw` - Use RAW and PACKET sockets
105+
- `cap_setgid` - Make arbitrary manipulations of process GIDs
106+
- `cap_setuid` - Make arbitrary manipulations of process UIDs
107+
- `cap_sys_admin` - Perform system administration operations (very powerful!)
108+
- `cap_sys_module` - Load and unload kernel modules
109+
- `cap_sys_nice` - Raise process nice value and change scheduling
110+
- `cap_sys_time` - Set system clock
111+
112+
## Security Best Practices
113+
114+
1. **Use the minimum required capabilities**
115+
- Only grant what the service actually needs
116+
- Don't grant `cap_sys_admin` unless absolutely necessary
117+
118+
2. **Always specify a user**
119+
- Always use `@user` to drop to a non-root user
120+
- Capabilities work best when combined with user separation
121+
122+
3. **Use ambient capabilities (`^`)**
123+
- The `^` prefix ensures capabilities survive exec()
124+
- Simpler than setting file capabilities on binaries
125+
126+
4. **Block dangerous capabilities**
127+
- Use `!` to explicitly block capabilities you don't want
128+
- Example: `!cap_sys_admin,!cap_sys_module`
129+
130+
5. **Test with `getpcaps`**
131+
- After starting a service, verify its capabilities:
132+
```bash
133+
getpcaps $(pidof nginx)
134+
```
135+
- Should show only the capabilities you granted
136+
137+
## Verification
138+
139+
After configuring a service with capabilities, verify it works correctly:
140+
141+
```bash
142+
# Start the service
143+
initctl start webserver
144+
145+
# Check the process capabilities
146+
getpcaps $(pidof nginx)
147+
148+
# Should show something like:
149+
# 12345: cap_net_bind_service=eip
150+
151+
# Verify the user
152+
ps -o user,pid,cmd -p $(pidof nginx)
153+
154+
# Should show the service running as the specified user
155+
```
156+
157+
## Requirements
158+
159+
- Linux kernel 4.3+ (for ambient capabilities support)
160+
- libcap library installed
161+
- Finit built with `--enable-libcap`
162+
163+
## Limitations
164+
165+
- Capabilities are only applied when both `@user` and `caps:` are specified
166+
- The service must drop to a non-root user for capabilities to be effective
167+
- Some very old binaries may not work correctly with ambient capabilities
168+
- File system capabilities are not managed by Finit (use `setcap` for that)
169+
170+
## See Also
171+
172+
- `man 7 capabilities` - Linux capabilities overview
173+
- `man 3 cap_iab` - IAB capability API documentation
174+
- `man 8 setcap` - Set file capabilities
175+
- `man 8 getcap` - Query file capabilities
176+
- `man 1 capsh` - Capability shell wrapper

doc/config/service-opts.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ started at any time by running `initctl start <service>`.
4242

4343
Other run/task/service options are:
4444

45+
* `caps:...` -- see the [Linux Capabilities](capabilities.md) section
4546
* `cgroups:...` -- see the [Cgroups](cgroups.md) section
4647
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
4748
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output)

doc/features.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,38 @@ unnecessary overhead, which can be removed at build-time using:
130130
configure --enable-auto-reload
131131

132132

133+
**Linux Capabilities**
134+
135+
Finit supports Linux capabilities, allowing services to run with minimal
136+
required privileges instead of running as root. This improves security by
137+
following the principle of least privilege.
138+
139+
```conf
140+
service [2345] name:nginx \
141+
www-data:www-data \
142+
caps:^cap_net_bind_service \
143+
/usr/sbin/nginx -g 'daemon off;'
144+
```
145+
146+
In this example, nginx runs as the unprivileged `www-data` user but retains
147+
the ability to bind to privileged ports (80, 443) through the
148+
`cap_net_bind_service` capability.
149+
150+
The `caps:` directive uses the IAB (Inheritable, Ambient, Bounding) format:
151+
- `^` = Ambient (recommended) - capabilities survive exec()
152+
- `%` = Inheritable only - requires file capabilities
153+
- `!` = Bounding - block from acquiring capability
154+
155+
Multiple capabilities can be specified as comma-separated:
156+
157+
```conf
158+
caps:^cap_net_raw,^cap_net_admin,!cap_sys_admin
159+
```
160+
161+
See the [Linux Capabilities](config/capabilities.md) section for detailed
162+
information, examples, and security best practices.
163+
164+
133165
**Cgroups**
134166

135167
Finit supports cgroups v2 and comes with the following default groups in

src/service.c

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
# include <lite/lite.h>
4040
#endif
4141
#include <wordexp.h>
42+
#ifdef HAVE_LIBCAP
43+
# include <sys/capability.h>
44+
#endif
4245

4346
#include "cgroup.h"
4447
#include "client.h"
@@ -521,6 +524,36 @@ static void compose_cmdline(svc_t *svc, char *buf, size_t len)
521524
}
522525
}
523526

527+
static void set_uid(uid_t uid, svc_t *svc)
528+
{
529+
#ifdef HAVE_LIBCAP
530+
if (cap_setuid(uid)) {
531+
err(1, "%s: failed cap_setuid(%d)", svc_ident(svc, NULL, 0), uid);
532+
return;
533+
}
534+
535+
/* After dropping privileges, set the specific capabilities we need */
536+
if (svc->capabilities[0]) {
537+
cap_iab_t cap_iab = cap_iab_from_text(svc->capabilities);
538+
if (!cap_iab) {
539+
err(1, "%s: failed parsing capabilities '%s'",
540+
svc_ident(svc, NULL, 0), svc->capabilities);
541+
return;
542+
}
543+
544+
if (cap_iab_set_proc(cap_iab) != 0) {
545+
cap_free(cap_iab);
546+
err(1, "%s: failed setting capabilities",
547+
svc_ident(svc, NULL, 0));
548+
}
549+
cap_free(cap_iab);
550+
}
551+
#else
552+
if (setuid(uid))
553+
err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid);
554+
#endif
555+
}
556+
524557
static pid_t service_fork(svc_t *svc)
525558
{
526559
pid_t pid;
@@ -552,8 +585,7 @@ static pid_t service_fork(svc_t *svc)
552585
}
553586

554587
if (uid >= 0) {
555-
if (setuid(uid))
556-
err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid);
588+
set_uid(uid, svc);
557589

558590
/* Set default path for regular users */
559591
if (uid > 0)
@@ -1666,6 +1698,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
16661698
char *ready_script = NULL, *conflict = NULL;
16671699
char *reload_script = NULL, *stop_script = NULL;
16681700
char *cleanup_script = NULL;
1701+
char *caps = NULL;
16691702
char ident[MAX_IDENT_LEN];
16701703
char *ifstmt = NULL;
16711704
char *notify = NULL;
@@ -1781,6 +1814,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
17811814
stop_script = arg;
17821815
else if (MATCH_CMD(cmd, "env:", arg))
17831816
env = arg;
1817+
else if (MATCH_CMD(cmd, "caps:", arg))
1818+
caps = arg;
17841819
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
17851820
else if (MATCH_CMD(cmd, "cgroup", arg))
17861821
cgroup = arg;
@@ -2004,6 +2039,10 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
20042039
parse_env(svc, env);
20052040
else
20062041
memset(svc->env, 0, sizeof(svc->env));
2042+
if (caps)
2043+
strlcpy(svc->capabilities, caps, sizeof(svc->capabilities));
2044+
else
2045+
memset(svc->capabilities, 0, sizeof(svc->capabilities));
20072046
if (file)
20082047
strlcpy(svc->file, file, sizeof(svc->file));
20092048
else

src/svc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ typedef struct svc {
191191
/* Identity */
192192
char username[MAX_USER_LEN];
193193
char group[MAX_USER_LEN];
194+
char capabilities[MAX_CMD_LEN];
194195

195196
/* Command, arguments and service description */
196197
char cmd[MAX_CMD_LEN];

0 commit comments

Comments
 (0)