Skip to content

Commit fee38d4

Browse files
authored
Merge pull request #458 from aanderse/master
Add support for Linux capabilities
2 parents 0c85ea8 + be2a0ec commit fee38d4

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)