nspawn: Add --restrict-address-families= option

Add a new --restrict-address-families= command line option and
corresponding RestrictAddressFamilies= setting for .nspawn files to
restrict which socket address families may be used inside a container.

Many address families such as AF_VSOCK and AF_NETLINK are not
network-namespaced, so restricting access to them in containers
improves isolation. The option supports allowlist and denylist modes
(via ~ prefix), as well as "none" to block all families, matching the
semantics of RestrictAddressFamilies= in unit files.

The address family parsing logic is extracted into a shared
parse_address_families() helper in parse-helpers.c, which is now also
used by config_parse_address_families() in load-fragment.c.

This is currently opt-in. In a future version, the default will be
changed to restrict address families to AF_INET, AF_INET6 and AF_UNIX.
This commit is contained in:
Daan De Meyer
2026-04-13 11:14:11 +02:00
parent 4c9ce728e7
commit 4bbdc8a6a2
14 changed files with 300 additions and 164 deletions
+11 -58
View File
@@ -10,7 +10,6 @@
#include "sd-bus.h"
#include "sd-messages.h"
#include "af-list.h"
#include "all-units.h"
#include "alloc-util.h"
#include "bpf-program.h"
@@ -3474,72 +3473,26 @@ int config_parse_address_families(
void *userdata) {
ExecContext *c = data;
bool invert = false;
bool is_allowlist = c->address_families_allow_list;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
if (isempty(rvalue)) {
/* Empty assignment resets the list */
c->address_families = set_free(c->address_families);
c->address_families_allow_list = false;
r = parse_address_families(rvalue, &c->address_families, &is_allowlist);
/* Copy back unconditionally: parse_address_families() may have partially populated
* c->address_families before failing, so keep is_allowlist in sync with that state. */
c->address_families_allow_list = is_allowlist;
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse address family, ignoring: %s", rvalue);
return 0;
}
if (streq(rvalue, "none")) {
/* Forbid all address families. */
c->address_families = set_free(c->address_families);
c->address_families_allow_list = true;
return 0;
}
if (rvalue[0] == '~') {
invert = true;
rvalue++;
}
if (!c->address_families) {
c->address_families = set_new(NULL);
if (!c->address_families)
return log_oom();
c->address_families_allow_list = !invert;
}
for (const char *p = rvalue;;) {
_cleanup_free_ char *word = NULL;
int af;
r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Invalid syntax, ignoring: %s", rvalue);
return 0;
}
if (r == 0)
return 0;
af = af_from_name(word);
if (af < 0) {
log_syntax(unit, LOG_WARNING, filename, line, af,
"Failed to parse address family, ignoring: %s", word);
continue;
}
/* If we previously wanted to forbid an address family and now
* we want to allow it, then just remove it from the list.
*/
if (!invert == c->address_families_allow_list) {
r = set_put(c->address_families, INT_TO_PTR(af));
if (r < 0)
return log_oom();
} else
set_remove(c->address_families, INT_TO_PTR(af));
}
return 0;
}
#endif