Compare commits

..

12 Commits

Author SHA1 Message Date
Zbigniew Jędrzejewski-Szmek 5961d35a5b NEWS: add blurb about os-release and $container_host_* 2020-07-09 00:02:55 +02:00
Zbigniew Jędrzejewski-Szmek 55aacd502b
Merge pull request #15891 from bluca/host_os_release
Container Interface: expose the host's os-release metadata to nspawn and portable guests
2020-07-08 23:52:13 +02:00
Zbigniew Jędrzejewski-Szmek 48c190822b Merge pull request #16405 from sipraga/master 2020-07-08 22:32:04 +02:00
Zbigniew Jędrzejewski-Szmek 02b0109af5
Merge pull request #15955 from anitazha/nullorempty
core: check null_or_empty_path for masked units instead of /dev/null
2020-07-08 22:18:17 +02:00
Alvin Šipraga 0d0de133f0 network: add support for MACVLAN source mode
Add support for creating a MACVLAN interface in "source" mode by
specifying Mode=source in the [MACVLAN] section of a .netdev file.

A list of allowed MAC addresses for the corresponding MACVLAN can also
be specified with the SourceMACAddress= option of the [MACVLAN] section.

An example .netdev file:

    [NetDev]
    Name=macvlan0
    Kind=macvlan
    MACAddress=02:DE:AD:BE:EF:00

    [MACVLAN]
    Mode=source
    SourceMACAddress=02:AB:AB:AB:AB:01 02:CD:CD:CD:CD:01
    SourceMACAddress=02:EF:EF:EF:EF:01

The same keys can also be specified in [MACVTAP] for MACVTAP kinds of
interfaces, with the same semantics.
2020-07-08 18:01:52 +02:00
Zbigniew Jędrzejewski-Szmek 4276749dd3 shared/install: do not require /dev/null to be present in chroots
This partially undoes the parent commit. We follow the symlink and
if it appears to be a symlink to /dev/null, even if /dev/null is not
present, we treat it as such. The addition of creation of /dev/null
in the test is reverted.
2020-07-05 20:06:22 +02:00
Anita Zhang 640f3b143d core: check null_or_empty for masked units instead of /dev/null
There's some inconsistency in the what is considered a masked unit:
some places (i.e. load-fragment.c) use `null_or_empty()` while others
check if the file path is symlinked to "/dev/null". Since the latter
doesn't account for things like non-absolute symlinks to "/dev/null",
this commit switches the check for "/dev/null" to use `null_or_empty_path()`
2020-07-03 02:33:50 -07:00
Luca Boccassi 73083ca238 portabled: implement container host os-release interface 2020-06-23 12:58:21 +01:00
Luca Boccassi e1bb4b0d1d nspawn: implement container host os-release interface 2020-06-23 12:58:21 +01:00
Luca Boccassi 34e0d56ce2 Container interface: document exposing the host's os-release
In order to allow applications to detect the host OS version or other
metadata, ask container managers to expose the os-release files as
read-only bind mounts.
For systemd-nspawn, we will also expose ID, BUILD_ID, VERSION_ID and
VARIANT_ID as lowercase environment variables prefixed by the
container_host_ string.
2020-06-23 12:57:05 +01:00
Luca Boccassi b3b1a08a56 nspawn: use mkdir_p_safe instead of homegrown version 2020-06-23 12:57:05 +01:00
Luca Boccassi 17b99e377b basic/mkdir: introduce safe recursive variants
Add mkdir_p_safe and mkdir_parents_safe. Will be used by nspawn.
2020-06-23 12:57:05 +01:00
25 changed files with 276 additions and 98 deletions

8
NEWS
View File

@ -516,6 +516,14 @@ CHANGES WITH 246:
https://systemd.io/JOURNAL_FILE_FORMAT https://systemd.io/JOURNAL_FILE_FORMAT
* The interface for containers (https://systemd.io/CONTAINER_INTERFACE)
has been extended by a set of environment variables that expose
select fields from the host's os-release file to the container
payload. Similarly, host's os-release files can be mounted into the
container underneath /run/hosts. Together, those mechanisms provide a
standardized way to expose information about the host to the
container payload. Both interfaces are implemented in systemd-nspawn.
* All D-Bus services shipped in systemd now implement the generic * All D-Bus services shipped in systemd now implement the generic
LogControl1 D-Bus API which allows clients to change log level + LogControl1 D-Bus API which allows clients to change log level +
target of the service during runtime. target of the service during runtime.

View File

@ -121,6 +121,16 @@ manager, please consider supporting the following interfaces.
`container_ttys=pts/7 pts/8 pts/14` it will spawn three additional login `container_ttys=pts/7 pts/8 pts/14` it will spawn three additional login
gettys on ptys 7, 8, and 14. gettys on ptys 7, 8, and 14.
4. To allow applications to detect the OS version and other metadata of the host
running the container manager, if this is considered desirable, please parse
the host's `/etc/os-release` and set a `$container_host_<key>=<VALUE>`
environment variable for the ID fields described by the [os-release
interface](https://www.freedesktop.org/software/systemd/man/os-release.html), eg:
`$container_host_id=debian`
`$container_host_build_id=2020-06-15`
`$container_host_variant_id=server`
`$container_host_version_id=10`
## Advanced Integration ## Advanced Integration
1. Consider syncing `/etc/localtime` from the host file system into the 1. Consider syncing `/etc/localtime` from the host file system into the

View File

@ -339,6 +339,13 @@
name in order to avoid name clashes. Applications name in order to avoid name clashes. Applications
reading this file must ignore unknown fields. Example: reading this file must ignore unknown fields. Example:
<literal>DEBIAN_BTS="debbugs://bugs.debian.org/"</literal></para> <literal>DEBIAN_BTS="debbugs://bugs.debian.org/"</literal></para>
<para>Container and sandbox runtime managers may make the host's
identification data available to applications by providing the host's
<filename>/etc/os-release</filename> and
<filename>/usr/lib/os-release</filename> as respectively
<filename>/run/host/etc/os-release</filename> and
<filename>/run/host/usr/lib/os-release</filename>.</para>
</refsect1> </refsect1>
<refsect1> <refsect1>

View File

@ -499,11 +499,22 @@
<para>The MACVLAN mode to use. The supported options are <para>The MACVLAN mode to use. The supported options are
<literal>private</literal>, <literal>private</literal>,
<literal>vepa</literal>, <literal>vepa</literal>,
<literal>bridge</literal>, and <literal>bridge</literal>,
<literal>passthru</literal>. <literal>passthru</literal>, and
<literal>source</literal>.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>SourceMACAddress=</varname></term>
<listitem>
<para>A whitespace-separated list of remote hardware addresses allowed on the MACVLAN. This
option only has an effect in source mode. Use full colon-, hyphen- or dot-delimited
hexadecimal. This option may appear more than once, in which case the lists are merged. If
the empty string is assigned to this option, the list of hardware addresses defined prior
to this is reset. Defaults to unset.</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@ -10,6 +10,7 @@
#include "mkdir.h" #include "mkdir.h"
#include "selinux-util.h" #include "selinux-util.h"
#include "smack-util.h" #include "smack-util.h"
#include "user-util.h"
int mkdir_label(const char *path, mode_t mode) { int mkdir_label(const char *path, mode_t mode) {
int r; int r;
@ -50,9 +51,9 @@ int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirF
} }
int mkdir_parents_label(const char *path, mode_t mode) { int mkdir_parents_label(const char *path, mode_t mode) {
return mkdir_parents_internal(NULL, path, mode, mkdir_label); return mkdir_parents_internal(NULL, path, mode, UID_INVALID, UID_INVALID, 0, mkdir_label);
} }
int mkdir_p_label(const char *path, mode_t mode) { int mkdir_p_label(const char *path, mode_t mode) {
return mkdir_p_internal(NULL, path, mode, mkdir_label); return mkdir_p_internal(NULL, path, mode, UID_INVALID, UID_INVALID, 0, mkdir_label);
} }

View File

@ -93,7 +93,7 @@ int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags f
return mkdir_safe_internal(path, mode, uid, gid, flags, mkdir_errno_wrapper); return mkdir_safe_internal(path, mode, uid, gid, flags, mkdir_errno_wrapper);
} }
int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir) {
const char *p, *e; const char *p, *e;
int r; int r;
@ -136,34 +136,54 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mk
if (prefix && path_startswith(prefix, t)) if (prefix && path_startswith(prefix, t))
continue; continue;
if (uid == UID_INVALID && gid == UID_INVALID && flags == 0) {
r = _mkdir(t, mode); r = _mkdir(t, mode);
if (r < 0 && r != -EEXIST) if (r < 0 && r != -EEXIST)
return r; return r;
} else {
r = mkdir_safe_internal(t, mode, uid, gid, flags, _mkdir);
if (r < 0 && r != -EEXIST)
return r;
}
} }
} }
int mkdir_parents(const char *path, mode_t mode) { int mkdir_parents(const char *path, mode_t mode) {
return mkdir_parents_internal(NULL, path, mode, mkdir_errno_wrapper); return mkdir_parents_internal(NULL, path, mode, UID_INVALID, UID_INVALID, 0, mkdir_errno_wrapper);
} }
int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { int mkdir_parents_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
return mkdir_parents_internal(prefix, path, mode, uid, gid, flags, mkdir_errno_wrapper);
}
int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir) {
int r; int r;
/* Like mkdir -p */ /* Like mkdir -p */
assert(_mkdir != mkdir); assert(_mkdir != mkdir);
r = mkdir_parents_internal(prefix, path, mode, _mkdir); r = mkdir_parents_internal(prefix, path, mode, uid, gid, flags, _mkdir);
if (r < 0) if (r < 0)
return r; return r;
if (uid == UID_INVALID && gid == UID_INVALID && flags == 0) {
r = _mkdir(path, mode); r = _mkdir(path, mode);
if (r < 0 && (r != -EEXIST || is_dir(path, true) <= 0)) if (r < 0 && (r != -EEXIST || is_dir(path, true) <= 0))
return r; return r;
} else {
r = mkdir_safe_internal(path, mode, uid, gid, flags, _mkdir);
if (r < 0 && r != -EEXIST)
return r;
}
return 0; return 0;
} }
int mkdir_p(const char *path, mode_t mode) { int mkdir_p(const char *path, mode_t mode) {
return mkdir_p_internal(NULL, path, mode, mkdir_errno_wrapper); return mkdir_p_internal(NULL, path, mode, UID_INVALID, UID_INVALID, 0, mkdir_errno_wrapper);
}
int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
return mkdir_p_internal(prefix, path, mode, uid, gid, flags, mkdir_errno_wrapper);
} }

View File

@ -12,15 +12,17 @@ int mkdir_errno_wrapper(const char *pathname, mode_t mode);
int mkdirat_errno_wrapper(int dirfd, const char *pathname, mode_t mode); int mkdirat_errno_wrapper(int dirfd, const char *pathname, mode_t mode);
int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags); int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags);
int mkdir_parents(const char *path, mode_t mode); int mkdir_parents(const char *path, mode_t mode);
int mkdir_parents_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags);
int mkdir_p(const char *path, mode_t mode); int mkdir_p(const char *path, mode_t mode);
int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags);
/* mandatory access control(MAC) versions */ /* mandatory access control(MAC) versions */
int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags); int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags);
int mkdir_parents_label(const char *path, mode_t mode); int mkdir_parents_label(const char *path, mode_t mod);
int mkdir_p_label(const char *path, mode_t mode); int mkdir_p_label(const char *path, mode_t mode);
/* internally used */ /* internally used */
typedef int (*mkdir_func_t)(const char *pathname, mode_t mode); typedef int (*mkdir_func_t)(const char *pathname, mode_t mode);
int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir); int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir);
int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir);
int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir);

View File

@ -5955,7 +5955,7 @@ const char *unit_label_path(const Unit *u) {
return NULL; return NULL;
/* If a unit is masked, then don't read the SELinux label of /dev/null, as that really makes no sense */ /* If a unit is masked, then don't read the SELinux label of /dev/null, as that really makes no sense */
if (path_equal(p, "/dev/null")) if (null_or_empty_path(p) > 0)
return NULL; return NULL;
return p; return p;

View File

@ -93,9 +93,20 @@ static const NLType rtnl_link_info_data_ipvlan_types[] = {
[IFLA_IPVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, [IFLA_IPVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 },
}; };
static const NLType rtnl_macvlan_macaddr_types[] = {
[IFLA_MACVLAN_MACADDR] = { .type = NETLINK_TYPE_ETHER_ADDR },
};
static const NLTypeSystem rtnl_macvlan_macaddr_type_system = {
.count = ELEMENTSOF(rtnl_macvlan_macaddr_types),
.types = rtnl_macvlan_macaddr_types,
};
static const NLType rtnl_link_info_data_macvlan_types[] = { static const NLType rtnl_link_info_data_macvlan_types[] = {
[IFLA_MACVLAN_MODE] = { .type = NETLINK_TYPE_U32 }, [IFLA_MACVLAN_MODE] = { .type = NETLINK_TYPE_U32 },
[IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, [IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 },
[IFLA_MACVLAN_MACADDR_MODE] = { .type = NETLINK_TYPE_U32 },
[IFLA_MACVLAN_MACADDR_DATA] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_macvlan_macaddr_type_system },
}; };
static const NLType rtnl_link_info_data_bridge_types[] = { static const NLType rtnl_link_info_data_bridge_types[] = {

View File

@ -23,6 +23,29 @@ static int netdev_macvlan_fill_message_create(NetDev *netdev, Link *link, sd_net
assert(m); assert(m);
if (m->mode == NETDEV_MACVLAN_MODE_SOURCE && !set_isempty(m->match_source_mac)) {
Iterator i;
const struct ether_addr *mac_addr;
r = sd_netlink_message_append_u32(req, IFLA_MACVLAN_MACADDR_MODE, MACVLAN_MACADDR_SET);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACVLAN_MACADDR_MODE attribute: %m");
r = sd_netlink_message_open_container(req, IFLA_MACVLAN_MACADDR_DATA);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not open IFLA_MACVLAN_MACADDR_DATA container: %m");
SET_FOREACH(mac_addr, m->match_source_mac, i) {
r = sd_netlink_message_append_ether_addr(req, IFLA_MACVLAN_MACADDR, mac_addr);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACVLAN_MACADDR attribute: %m");
}
r = sd_netlink_message_close_container(req);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not close IFLA_MACVLAN_MACADDR_DATA container: %m");
}
if (m->mode != _NETDEV_MACVLAN_MODE_INVALID) { if (m->mode != _NETDEV_MACVLAN_MODE_INVALID) {
r = sd_netlink_message_append_u32(req, IFLA_MACVLAN_MODE, m->mode); r = sd_netlink_message_append_u32(req, IFLA_MACVLAN_MODE, m->mode);
if (r < 0) if (r < 0)
@ -32,6 +55,21 @@ static int netdev_macvlan_fill_message_create(NetDev *netdev, Link *link, sd_net
return 0; return 0;
} }
static void macvlan_done(NetDev *n) {
MacVlan *m;
assert(n);
if (n->kind == NETDEV_KIND_MACVLAN)
m = MACVLAN(n);
else
m = MACVTAP(n);
assert(m);
set_free_free(m->match_source_mac);
}
static void macvlan_init(NetDev *n) { static void macvlan_init(NetDev *n) {
MacVlan *m; MacVlan *m;
@ -50,6 +88,7 @@ static void macvlan_init(NetDev *n) {
const NetDevVTable macvtap_vtable = { const NetDevVTable macvtap_vtable = {
.object_size = sizeof(MacVlan), .object_size = sizeof(MacVlan),
.init = macvlan_init, .init = macvlan_init,
.done = macvlan_done,
.sections = NETDEV_COMMON_SECTIONS "MACVTAP\0", .sections = NETDEV_COMMON_SECTIONS "MACVTAP\0",
.fill_message_create = netdev_macvlan_fill_message_create, .fill_message_create = netdev_macvlan_fill_message_create,
.create_type = NETDEV_CREATE_STACKED, .create_type = NETDEV_CREATE_STACKED,
@ -59,6 +98,7 @@ const NetDevVTable macvtap_vtable = {
const NetDevVTable macvlan_vtable = { const NetDevVTable macvlan_vtable = {
.object_size = sizeof(MacVlan), .object_size = sizeof(MacVlan),
.init = macvlan_init, .init = macvlan_init,
.done = macvlan_done,
.sections = NETDEV_COMMON_SECTIONS "MACVLAN\0", .sections = NETDEV_COMMON_SECTIONS "MACVLAN\0",
.fill_message_create = netdev_macvlan_fill_message_create, .fill_message_create = netdev_macvlan_fill_message_create,
.create_type = NETDEV_CREATE_STACKED, .create_type = NETDEV_CREATE_STACKED,

View File

@ -5,11 +5,13 @@ typedef struct MacVlan MacVlan;
#include "macvlan-util.h" #include "macvlan-util.h"
#include "netdev.h" #include "netdev.h"
#include "set.h"
struct MacVlan { struct MacVlan {
NetDev meta; NetDev meta;
MacVlanMode mode; MacVlanMode mode;
Set *match_source_mac;
}; };
DEFINE_NETDEV_CAST(MACVLAN, MacVlan); DEFINE_NETDEV_CAST(MACVLAN, MacVlan);

View File

@ -52,7 +52,9 @@ VLAN.MVRP, config_parse_tristate,
VLAN.LooseBinding, config_parse_tristate, 0, offsetof(VLan, loose_binding) VLAN.LooseBinding, config_parse_tristate, 0, offsetof(VLan, loose_binding)
VLAN.ReorderHeader, config_parse_tristate, 0, offsetof(VLan, reorder_hdr) VLAN.ReorderHeader, config_parse_tristate, 0, offsetof(VLan, reorder_hdr)
MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode)
MACVLAN.SourceMACAddress, config_parse_hwaddrs, 0, offsetof(MacVlan, match_source_mac)
MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode)
MACVTAP.SourceMACAddress, config_parse_hwaddrs, 0, offsetof(MacVlan, match_source_mac)
IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode) IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode)
IPVLAN.Flags, config_parse_ipvlan_flags, 0, offsetof(IPVlan, flags) IPVLAN.Flags, config_parse_ipvlan_flags, 0, offsetof(IPVlan, flags)
IPVTAP.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode) IPVTAP.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode)

View File

@ -487,59 +487,6 @@ int mount_sysfs(const char *dest, MountSettingsMask mount_settings) {
MS_BIND|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT|extra_flags, NULL); MS_BIND|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT|extra_flags, NULL);
} }
static int mkdir_userns(const char *path, mode_t mode, uid_t uid_shift) {
int r;
assert(path);
r = mkdir_errno_wrapper(path, mode);
if (r < 0 && r != -EEXIST)
return r;
if (uid_shift == UID_INVALID)
return 0;
if (lchown(path, uid_shift, uid_shift) < 0)
return -errno;
return 0;
}
static int mkdir_userns_p(const char *prefix, const char *path, mode_t mode, uid_t uid_shift) {
const char *p, *e;
int r;
assert(path);
if (prefix && !path_startswith(path, prefix))
return -ENOTDIR;
/* create every parent directory in the path, except the last component */
p = path + strspn(path, "/");
for (;;) {
char t[strlen(path) + 1];
e = p + strcspn(p, "/");
p = e + strspn(e, "/");
/* Is this the last component? If so, then we're done */
if (*p == 0)
break;
memcpy(t, path, e - path);
t[e-path] = 0;
if (prefix && path_startswith(prefix, t))
continue;
r = mkdir_userns(t, mode, uid_shift);
if (r < 0)
return r;
}
return mkdir_userns(path, mode, uid_shift);
}
int mount_all(const char *dest, int mount_all(const char *dest,
MountSettingsMask mount_settings, MountSettingsMask mount_settings,
uid_t uid_shift, uid_t uid_shift,
@ -616,6 +563,10 @@ int mount_all(const char *dest,
MOUNT_FATAL|MOUNT_MKDIR }, MOUNT_FATAL|MOUNT_MKDIR },
{ "tmpfs", "/run", "tmpfs", "mode=755" TMPFS_LIMITS_RUN, MS_NOSUID|MS_NODEV|MS_STRICTATIME, { "tmpfs", "/run", "tmpfs", "mode=755" TMPFS_LIMITS_RUN, MS_NOSUID|MS_NODEV|MS_STRICTATIME,
MOUNT_FATAL|MOUNT_MKDIR }, MOUNT_FATAL|MOUNT_MKDIR },
{ "/usr/lib/os-release", "/run/host/usr/lib/os-release", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV,
MOUNT_FATAL|MOUNT_MKDIR|MOUNT_TOUCH },
{ "/etc/os-release", "/run/host/etc/os-release", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV,
MOUNT_MKDIR|MOUNT_TOUCH },
#if HAVE_SELINUX #if HAVE_SELINUX
{ "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND,
@ -636,6 +587,7 @@ int mount_all(const char *dest,
for (k = 0; k < ELEMENTSOF(mount_table); k++) { for (k = 0; k < ELEMENTSOF(mount_table); k++) {
_cleanup_free_ char *where = NULL, *options = NULL; _cleanup_free_ char *where = NULL, *options = NULL;
const char *o; const char *o;
struct stat source_st;
bool fatal = FLAGS_SET(mount_table[k].mount_settings, MOUNT_FATAL); bool fatal = FLAGS_SET(mount_table[k].mount_settings, MOUNT_FATAL);
if (in_userns != FLAGS_SET(mount_table[k].mount_settings, MOUNT_IN_USERNS)) if (in_userns != FLAGS_SET(mount_table[k].mount_settings, MOUNT_IN_USERNS))
@ -661,10 +613,26 @@ int mount_all(const char *dest,
return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where);
if (r > 0) if (r > 0)
continue; continue;
/* Shortcut for optional bind mounts: if the source can't be found skip ahead to avoid creating
* empty and unused directories. */
if (!fatal && FLAGS_SET(mount_table[k].mount_settings, MOUNT_MKDIR) && FLAGS_SET(mount_table[k].flags, MS_BIND)) {
r = stat(mount_table[k].what, &source_st);
if (r < 0) {
if (errno == ENOENT)
continue;
return log_error_errno(errno, "Failed to stat %s: %m", mount_table[k].what);
}
}
} }
if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_MKDIR)) { if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_MKDIR)) {
r = mkdir_userns_p(dest, where, 0755, (use_userns && !in_userns) ? uid_shift : UID_INVALID); uid_t u = (use_userns && !in_userns) ? uid_shift : UID_INVALID;
if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_TOUCH))
r = mkdir_parents_safe(dest, where, 0755, u, u, 0);
else
r = mkdir_p_safe(dest, where, 0755, u, u, 0);
if (r < 0 && r != -EEXIST) { if (r < 0 && r != -EEXIST) {
if (fatal && r != -EROFS) if (fatal && r != -EROFS)
return log_error_errno(r, "Failed to create directory %s: %m", where); return log_error_errno(r, "Failed to create directory %s: %m", where);
@ -676,6 +644,14 @@ int mount_all(const char *dest,
if (r != -EROFS) if (r != -EROFS)
continue; continue;
} }
if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_TOUCH)) {
r = touch(where);
if (r < 0 && r != -EEXIST) {
if (fatal)
return log_error_errno(r, "Failed to create mount point %s: %m", where);
log_debug_errno(r, "Failed to create mount point %s: %m", where);
}
}
} }
o = mount_table[k].options; o = mount_table[k].options;

View File

@ -17,6 +17,7 @@ typedef enum MountSettingsMask {
MOUNT_ROOT_ONLY = 1 << 6, /* if set, only root mounts are mounted */ MOUNT_ROOT_ONLY = 1 << 6, /* if set, only root mounts are mounted */
MOUNT_NON_ROOT_ONLY = 1 << 7, /* if set, only non-root mounts are mounted */ MOUNT_NON_ROOT_ONLY = 1 << 7, /* if set, only non-root mounts are mounted */
MOUNT_MKDIR = 1 << 8, /* if set, make directory to mount over first */ MOUNT_MKDIR = 1 << 8, /* if set, make directory to mount over first */
MOUNT_TOUCH = 1 << 9, /* if set, touch file to mount over first */
} MountSettingsMask; } MountSettingsMask;
typedef enum CustomMountType { typedef enum CustomMountType {

View File

@ -2931,7 +2931,8 @@ static int inner_child(
int kmsg_socket, int kmsg_socket,
int rtnl_socket, int rtnl_socket,
int master_pty_socket, int master_pty_socket,
FDSet *fds) { FDSet *fds,
char **os_release_pairs) {
_cleanup_free_ char *home = NULL; _cleanup_free_ char *home = NULL;
char as_uuid[ID128_UUID_STRING_MAX]; char as_uuid[ID128_UUID_STRING_MAX];
@ -3190,7 +3191,7 @@ static int inner_child(
if (asprintf((char **)(envp + n_env++), "NOTIFY_SOCKET=%s", NSPAWN_NOTIFY_SOCKET_PATH) < 0) if (asprintf((char **)(envp + n_env++), "NOTIFY_SOCKET=%s", NSPAWN_NOTIFY_SOCKET_PATH) < 0)
return log_oom(); return log_oom();
env_use = strv_env_merge(2, envp, arg_setenv); env_use = strv_env_merge(3, envp, arg_setenv, os_release_pairs);
if (!env_use) if (!env_use)
return log_oom(); return log_oom();
@ -3316,6 +3317,7 @@ static int outer_child(
FDSet *fds, FDSet *fds,
int netns_fd) { int netns_fd) {
_cleanup_strv_free_ char **os_release_pairs = NULL;
_cleanup_close_ int fd = -1; _cleanup_close_ int fd = -1;
const char *p; const char *p;
pid_t pid; pid_t pid;
@ -3337,6 +3339,10 @@ static int outer_child(
log_debug("Outer child is initializing."); log_debug("Outer child is initializing.");
r = load_os_release_pairs_with_prefix("/", "container_host_", &os_release_pairs);
if (r < 0)
log_debug_errno(r, "Failed to read os-release from host for container, ignoring: %m");
if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0) if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0)
return log_error_errno(errno, "PR_SET_PDEATHSIG failed: %m"); return log_error_errno(errno, "PR_SET_PDEATHSIG failed: %m");
@ -3602,7 +3608,7 @@ static int outer_child(
return log_error_errno(r, "Failed to join network namespace: %m"); return log_error_errno(r, "Failed to join network namespace: %m");
} }
r = inner_child(barrier, directory, secondary, kmsg_socket, rtnl_socket, master_pty_socket, fds); r = inner_child(barrier, directory, secondary, kmsg_socket, rtnl_socket, master_pty_socket, fds, os_release_pairs);
if (r < 0) if (r < 0)
_exit(EXIT_FAILURE); _exit(EXIT_FAILURE);

View File

@ -701,6 +701,7 @@ static int install_chroot_dropin(
"[Service]\n", "[Service]\n",
IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "RootDirectory=" : "RootImage=", image_path, "\n" IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "RootDirectory=" : "RootImage=", image_path, "\n"
"Environment=PORTABLE=", basename(image_path), "\n" "Environment=PORTABLE=", basename(image_path), "\n"
"BindReadOnlyPaths=-/etc/os-release:/run/host/etc/os-release /usr/lib/os-release:/run/host/usr/lib/os-release\n"
"LogExtraFields=PORTABLE=", basename(image_path), "\n", "LogExtraFields=PORTABLE=", basename(image_path), "\n",
NULL)) NULL))

View File

@ -1236,8 +1236,7 @@ static int unit_file_load(
"%s: unit type %s cannot be templated, ignoring.", path, unit_type_to_string(type)); "%s: unit type %s cannot be templated, ignoring.", path, unit_type_to_string(type));
if (!(flags & SEARCH_LOAD)) { if (!(flags & SEARCH_LOAD)) {
r = lstat(path, &st); if (lstat(path, &st) < 0)
if (r < 0)
return -errno; return -errno;
if (null_or_empty(&st)) if (null_or_empty(&st))
@ -1324,26 +1323,40 @@ static int unit_file_load_or_readlink(
const char *path, const char *path,
const char *root_dir, const char *root_dir,
SearchFlags flags) { SearchFlags flags) {
_cleanup_free_ char *resolved = NULL;
_cleanup_free_ char *target = NULL; struct stat st;
int r; int r;
r = unit_file_load(c, info, path, root_dir, flags); r = unit_file_load(c, info, path, root_dir, flags);
if (r != -ELOOP || (flags & SEARCH_DROPIN)) if (r != -ELOOP || (flags & SEARCH_DROPIN))
return r; return r;
/* This is a symlink, let's read it. */ r = chase_symlinks(path, root_dir, CHASE_WARN | CHASE_NONEXISTENT, &resolved, NULL);
if (r >= 0 &&
root_dir &&
path_equal_ptr(path_startswith(resolved, root_dir), "dev/null"))
/* When looking under root_dir, we can't expect /dev/ to be mounted,
* so let's see if the path is a (possibly dangling) symlink to /dev/null. */
info->type = UNIT_FILE_TYPE_MASKED;
else if (r > 0 &&
stat(resolved, &st) >= 0 &&
null_or_empty(&st))
info->type = UNIT_FILE_TYPE_MASKED;
else {
_cleanup_free_ char *target = NULL;
const char *bn;
UnitType a, b;
/* This is a symlink, let's read it. We read the link again, because last time
* we followed the link until resolution, and here we need to do one step. */
r = readlink_malloc(path, &target); r = readlink_malloc(path, &target);
if (r < 0) if (r < 0)
return r; return r;
if (path_equal(target, "/dev/null"))
info->type = UNIT_FILE_TYPE_MASKED;
else {
const char *bn;
UnitType a, b;
bn = basename(target); bn = basename(target);
if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) { if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) {

View File

@ -9,6 +9,7 @@ static const char* const macvlan_mode_table[_NETDEV_MACVLAN_MODE_MAX] = {
[NETDEV_MACVLAN_MODE_VEPA] = "vepa", [NETDEV_MACVLAN_MODE_VEPA] = "vepa",
[NETDEV_MACVLAN_MODE_BRIDGE] = "bridge", [NETDEV_MACVLAN_MODE_BRIDGE] = "bridge",
[NETDEV_MACVLAN_MODE_PASSTHRU] = "passthru", [NETDEV_MACVLAN_MODE_PASSTHRU] = "passthru",
[NETDEV_MACVLAN_MODE_SOURCE] = "source",
}; };
DEFINE_STRING_TABLE_LOOKUP(macvlan_mode, MacVlanMode); DEFINE_STRING_TABLE_LOOKUP(macvlan_mode, MacVlanMode);

View File

@ -8,6 +8,7 @@ typedef enum MacVlanMode {
NETDEV_MACVLAN_MODE_VEPA = MACVLAN_MODE_VEPA, NETDEV_MACVLAN_MODE_VEPA = MACVLAN_MODE_VEPA,
NETDEV_MACVLAN_MODE_BRIDGE = MACVLAN_MODE_BRIDGE, NETDEV_MACVLAN_MODE_BRIDGE = MACVLAN_MODE_BRIDGE,
NETDEV_MACVLAN_MODE_PASSTHRU = MACVLAN_MODE_PASSTHRU, NETDEV_MACVLAN_MODE_PASSTHRU = MACVLAN_MODE_PASSTHRU,
NETDEV_MACVLAN_MODE_SOURCE = MACVLAN_MODE_SOURCE,
_NETDEV_MACVLAN_MODE_MAX, _NETDEV_MACVLAN_MODE_MAX,
_NETDEV_MACVLAN_MODE_INVALID = -1 _NETDEV_MACVLAN_MODE_INVALID = -1
} MacVlanMode; } MacVlanMode;

View File

@ -117,3 +117,33 @@ int load_os_release_pairs(const char *root, char ***ret) {
return load_env_file_pairs(f, p, ret); return load_env_file_pairs(f, p, ret);
} }
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) {
_cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL;
char **p, **q;
int r;
r = load_os_release_pairs(root, &os_release_pairs);
if (r < 0)
return r;
STRV_FOREACH_PAIR(p, q, os_release_pairs) {
char *line;
// We strictly return only the four main ID fields and ignore the rest
if (!STR_IN_SET(*p, "ID", "VERSION_ID", "BUILD_ID", "VARIANT_ID"))
continue;
ascii_strlower(*p);
line = strjoin(prefix, *p, "=", *q);
if (!line)
return -ENOMEM;
r = strv_consume(&os_release_pairs_prefixed, line);
if (r < 0)
return r;
}
*ret = TAKE_PTR(os_release_pairs_prefixed);
return 0;
}

View File

@ -10,3 +10,4 @@ int fopen_os_release(const char *root, char **ret_path, FILE **ret_file);
int parse_os_release(const char *root, ...) _sentinel_; int parse_os_release(const char *root, ...) _sentinel_;
int load_os_release_pairs(const char *root, char ***ret); int load_os_release_pairs(const char *root, char ***ret);
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);

View File

@ -4,6 +4,7 @@
#include "alloc-util.h" #include "alloc-util.h"
#include "fileio.h" #include "fileio.h"
#include "fs-util.h"
#include "install.h" #include "install.h"
#include "mkdir.h" #include "mkdir.h"
#include "rm-rf.h" #include "rm-rf.h"
@ -23,6 +24,7 @@ static void test_basic_mask_and_enable(const char *root) {
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", NULL) == -ENOENT); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", NULL) == -ENOENT);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", NULL) == -ENOENT); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", NULL) == -ENOENT);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", NULL) == -ENOENT); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", NULL) == -ENOENT);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "e.service", NULL) == -ENOENT);
p = strjoina(root, "/usr/lib/systemd/system/a.service"); p = strjoina(root, "/usr/lib/systemd/system/a.service");
assert_se(write_string_file(p, assert_se(write_string_file(p,
@ -150,6 +152,22 @@ static void test_basic_mask_and_enable(const char *root) {
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ALIAS); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ALIAS);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ALIAS); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ALIAS);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ALIAS); assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ALIAS);
/* Test masking with relative symlinks */
p = strjoina(root, "/usr/lib/systemd/system/e.service");
assert_se(symlink("../../../../../../dev/null", p) >= 0);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "e.service", NULL) >= 0);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "e.service", &state) >= 0 && state == UNIT_FILE_MASKED);
assert_se(unlink(p) == 0);
assert_se(symlink("/usr/../dev/null", p) >= 0);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "e.service", NULL) >= 0);
assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "e.service", &state) >= 0 && state == UNIT_FILE_MASKED);
assert_se(unlink(p) == 0);
} }
static void test_linked_units(const char *root) { static void test_linked_units(const char *root) {

View File

@ -16,7 +16,7 @@ test_create_image() {
mask_supporting_services mask_supporting_services
../create-busybox-container $initdir/testsuite-13.nc-container ../create-busybox-container $initdir/testsuite-13.nc-container
initdir="$initdir/testsuite-13.nc-container" dracut_install nc ip initdir="$initdir/testsuite-13.nc-container" dracut_install nc ip md5sum
) )
} }

View File

@ -6,6 +6,7 @@ Id=
GVRP= GVRP=
[MACVLAN] [MACVLAN]
Mode= Mode=
SourceMACAddress=
[WireGuard] [WireGuard]
ListenPort= ListenPort=
PrivateKey= PrivateKey=
@ -14,6 +15,7 @@ FwMark=
FirewallMark= FirewallMark=
[MACVTAP] [MACVTAP]
Mode= Mode=
SourceMACAddress=
[Match] [Match]
Architecture= Architecture=
Host= Host=

View File

@ -60,6 +60,18 @@ function check_notification_socket {
systemd-nspawn --register=no -D /testsuite-13.nc-container -U /bin/sh -x -c "$_cmd" systemd-nspawn --register=no -D /testsuite-13.nc-container -U /bin/sh -x -c "$_cmd"
} }
function check_os_release {
local _cmd='. /tmp/os-release
if [ -n "${ID:+set}" ] && [ "${ID}" != "${container_host_id}" ]; then exit 1; fi
if [ -n "${VERSION_ID:+set}" ] && [ "${VERSION_ID}" != "${container_host_version_id}" ]; then exit 1; fi
if [ -n "${BUILD_ID:+set}" ] && [ "${BUILD_ID}" != "${container_host_build_id}" ]; then exit 1; fi
if [ -n "${VARIANT_ID:+set}" ] && [ "${VARIANT_ID}" != "${container_host_variant_id}" ]; then exit 1; fi
cd /tmp; (cd /run/host/usr/lib; md5sum os-release) | md5sum -c
'
systemd-nspawn --register=no -D /testsuite-13.nc-container --bind=/etc/os-release:/tmp/os-release /bin/sh -x -e -c "$_cmd"
}
function run { function run {
if [[ "$1" = "yes" && "$is_v2_supported" = "no" ]]; then if [[ "$1" = "yes" && "$is_v2_supported" = "no" ]]; then
printf "Unified cgroup hierarchy is not supported. Skipping.\n" >&2 printf "Unified cgroup hierarchy is not supported. Skipping.\n" >&2
@ -144,6 +156,8 @@ check_norbind
check_notification_socket check_notification_socket
check_os_release
for api_vfs_writable in yes no network; do for api_vfs_writable in yes no network; do
run no no $api_vfs_writable run no no $api_vfs_writable
run yes no $api_vfs_writable run yes no $api_vfs_writable