mstack: introduce "mstack" concept

This commit is contained in:
Lennart Poettering
2026-02-19 15:05:15 +01:00
parent a9f6ba0496
commit 8343032a86
7 changed files with 1388 additions and 1 deletions
+2 -1
View File
@@ -77,7 +77,8 @@ REQUIREMENTS:
≥ 6.10 for fcntl(F_DUPFD_QUERY), unprivileged linkat(AT_EMPTY_PATH),
and block device 'partscan' sysfs attribute
≥ 6.12 for AT_HANDLE_MNT_ID_UNIQUE
≥ 6.13 for PIDFD_GET_INFO and {set,remove}xattrat()
≥ 6.13 for PIDFD_GET_INFO and {set,remove}xattrat() and
FSCONFIG_SET_FD support for overlayfs layers
≥ 6.16 for coredump pattern '%F' (pidfd) specifier and SO_PASSRIGHTS
✅ systemd utilizes several new kernel APIs, but will fall back gracefully
+1
View File
@@ -132,6 +132,7 @@ shared_sources = files(
'module-util.c',
'mount-setup.c',
'mount-util.c',
'mstack.c',
'net-condition.c',
'netif-naming-scheme.c',
'netif-sriov.c',
+1187
View File
File diff suppressed because it is too large Load Diff
+64
View File
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "discover-image.h"
#include "shared-forward.h"
typedef enum MStackFlags {
MSTACK_MKDIR = 1 << 0, /* when mounting, create top-level inode to mount on top */
MSTACK_RDONLY = 1 << 1,
} MStackFlags;
typedef enum MStackMountType {
MSTACK_ROOT, /* optional "root" entry used as root, with the layer@/rw layers only used for /usr/ */
MSTACK_LAYER, /* "layer@…" entries that are the lower (read-only) layers of an overlayfs stack */
MSTACK_RW, /* "rw" entry that is the upper (writable) layer of an overlayfs stack (contains two subdirs: 'data' + 'work') */
MSTACK_BIND, /* "bind@…" entries that are (writable) bind mounted on top of the overlayfs */
MSTACK_ROBIND, /* "robind@…" similar, but read-only */
_MSTACK_MOUNT_TYPE_MAX,
_MSTACK_MOUNT_TYPE_INVALID = -EINVAL,
} MStackMountType;
typedef struct MStackMount {
MStackMountType mount_type;
char *what;
int what_fd;
int mount_fd;
char *sort_key;
char *where;
ImageType image_type;
DissectedImage *dissected_image;
} MStackMount;
typedef struct MStack {
char *path;
MStackMount *mounts;
size_t n_mounts;
bool has_tmpfs_root; /* If true, we need a throw-away tmpfs as root */
bool has_overlayfs; /* Indicates whether we need overlayfs (i.e. if there are more than a single layer */
MStackMount *root_mount; /* If there's a MOUNT_BIND/MOUNT_ROBIND/MOUNT_ROOT mount, this points to it */
int root_mount_fd;
int usr_mount_fd;
} MStack;
#define MSTACK_INIT \
(MStack) { \
.root_mount_fd = -EBADF, \
.usr_mount_fd = -EBADF, \
}
MStack *mstack_free(MStack *mstack);
DEFINE_TRIVIAL_CLEANUP_FUNC(MStack*, mstack_free);
int mstack_load(const char *dir, int dir_fd, MStack **ret);
int mstack_open_images(MStack *mstack, int userns_fd, const ImagePolicy *image_policy, const ImageFilter *image_filter, MStackFlags flags);
int mstack_make_mounts(MStack *mstack, const char *temp_mount_dir, MStackFlags flags);
int mstack_bind_mounts(MStack *mstack, const char *where, int where_fd, MStackFlags flags, int *ret_root_fd);
/* The four calls above in one */
int mstack_apply(const char *dir, int dir_fd, const char *where, const char *temp_mount_dir, int userns_fd, const ImagePolicy *image_policy, const ImageFilter *image_filter, MStackFlags flags, int *ret_root_fd);
int mstack_is_read_only(MStack *mstack);
int mstack_is_foreign_uid_owned(MStack *mstack);
DECLARE_STRING_TABLE_LOOKUP_TO_STRING(mstack_mount_type, MStackMountType);
+2
View File
@@ -53,6 +53,7 @@ typedef struct Condition Condition;
typedef struct ConfigSection ConfigSection;
typedef struct ConfigTableItem ConfigTableItem;
typedef struct CPUSet CPUSet;
typedef struct DissectedImage DissectedImage;
typedef struct DnsAnswer DnsAnswer;
typedef struct DnsPacket DnsPacket;
typedef struct DnsQuestion DnsQuestion;
@@ -64,6 +65,7 @@ typedef struct FDSet FDSet;
typedef struct Fido2HmacSalt Fido2HmacSalt;
typedef struct GroupRecord GroupRecord;
typedef struct Image Image;
typedef struct ImageFilter ImageFilter;
typedef struct ImagePolicy ImagePolicy;
typedef struct InstallChange InstallChange;
typedef struct InstallInfo InstallInfo;
+1
View File
@@ -149,6 +149,7 @@ simple_tests += files(
'test-mkdir.c',
'test-modhex.c',
'test-mountpoint-util.c',
'test-mstack.c',
'test-namespace-util.c',
'test-net-naming-scheme.c',
'test-notify-recv.c',
+131
View File
@@ -0,0 +1,131 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/mount.h>
#include <sys/stat.h>
#include "capability-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "mountpoint-util.h"
#include "mstack.h"
#include "path-util.h"
#include "process-util.h"
#include "rm-rf.h"
#include "tests.h"
#include "tmpfile-util.h"
#include "virt.h"
static bool overlayfs_set_fd_lowerdir_plus_supported(void) {
int r;
_cleanup_close_ int sb_fd = fsopen("overlay", FSOPEN_CLOEXEC);
if (sb_fd < 0 && (ERRNO_IS_NOT_SUPPORTED(errno) || errno == ENODEV))
return false;
ASSERT_OK_ERRNO(sb_fd);
_cleanup_close_ int layer_fd = open("/", O_DIRECTORY|O_CLOEXEC);
ASSERT_OK_ERRNO(layer_fd);
r = RET_NERRNO(fsconfig(sb_fd, FSCONFIG_SET_FD, "lowerdir+", /* value= */ NULL, layer_fd));
if (r < 0 && (ERRNO_IS_NEG_NOT_SUPPORTED(r) || r == -EINVAL))
return false;
ASSERT_OK_ERRNO(r);
return true;
}
TEST(mstack) {
_cleanup_(rm_rf_physical_and_freep) char *t = NULL;
_cleanup_close_ int tfd = -EBADF;
int r;
tfd = mkdtemp_open("/tmp/mstack-what-XXXXXX", O_PATH, &t);
ASSERT_OK(tfd);
ASSERT_OK_ERRNO(mkdirat(tfd, "rw", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "rw/data", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "rw/data/check1", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "layer@0", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "layer@0/check2", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "layer@0/zzz", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "layer@1", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "layer@1/check3", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "layer@0/yyy", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "bind@zzz", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "bind@zzz/check4", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "robind@yyy", 0755));
ASSERT_OK_ERRNO(mkdirat(tfd, "robind@yyy/check5", 0755));
_cleanup_(mstack_freep) MStack *mstack = NULL;
ASSERT_OK(mstack_load(t, tfd, &mstack));
ASSERT_OK_ZERO(mstack_is_read_only(mstack));
ASSERT_OK_ZERO(mstack_is_foreign_uid_owned(mstack));
if (!have_effective_cap(CAP_SYS_ADMIN))
return (void) log_tests_skipped("not attaching mstack, lacking privs");
if (!mount_new_api_supported())
return (void) log_tests_skipped("kernel does not support new mount API, skipping mstack attachment test.");
if (!overlayfs_set_fd_lowerdir_plus_supported())
return (void) log_tests_skipped("overlayfs does not support FSCONFIG_SET_FD with lowerdir+, skipping mstack attachment test.");
if (running_in_chroot() > 0) /* we cannot disable mount prop if we are in a chroot without the root inode being a proper mount point */
return (void) log_tests_skipped("running in chroot(), skipping mstack attachment test.");
mstack = mstack_free(mstack);
/* For with a new mountns */
r = pidref_safe_fork("(mstack-test", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, /* ret= */ NULL);
ASSERT_OK(r);
if (r == 0) {
MStackFlags flags = 0;
/* Close the original temporary fd, it still points to an inode of the original mountns,
* which we cannot use to generate mounts from */
tfd = safe_close(tfd);
{
ASSERT_OK(mstack_load(t, -EBADF, &mstack));
ASSERT_OK(mstack_open_images(
mstack,
/* userns_fd= */ -EBADF,
/* image_policy= */ NULL,
/* image_filter= */ NULL,
flags));
_cleanup_(rmdir_and_freep) char *m = NULL;
ASSERT_OK(mkdtemp_malloc("/tmp/mstack-temporary-XXXXXX", &m));
ASSERT_OK(mstack_make_mounts(mstack, m, flags));
_cleanup_(rmdir_and_freep) char *w = NULL;
ASSERT_OK(mkdtemp_malloc("/tmp/mstack-where-XXXXXX", &w));
_cleanup_close_ int rfd = -EBADF;
ASSERT_OK(mstack_bind_mounts(mstack, w, /* where_fd= */ -EBADF, flags, &rfd));
_cleanup_close_ int ofd = open(w, O_PATH|O_CLOEXEC);
ASSERT_OK_ERRNO(ofd);
ASSERT_OK_ERRNO(faccessat(ofd, "check1", F_OK, AT_SYMLINK_NOFOLLOW));
ASSERT_OK_ERRNO(faccessat(ofd, "check2/", F_OK, AT_SYMLINK_NOFOLLOW));
ASSERT_OK_ERRNO(faccessat(ofd, "check3/", F_OK, AT_SYMLINK_NOFOLLOW));
ASSERT_OK_ERRNO(faccessat(ofd, "zzz/check4/", F_OK, AT_SYMLINK_NOFOLLOW));
ASSERT_OK_ERRNO(faccessat(ofd, "yyy/check5/", F_OK, AT_SYMLINK_NOFOLLOW));
_cleanup_free_ char *j = ASSERT_PTR(path_join(w, "zzz"));
ASSERT_OK_ERRNO(umount2(j, MNT_DETACH));
_cleanup_free_ char *jj = ASSERT_PTR(path_join(w, "yyy"));
ASSERT_OK_ERRNO(umount2(jj, MNT_DETACH));
ASSERT_OK_ERRNO(umount2(w, MNT_DETACH));
}
mstack = mstack_free(mstack);
_exit(EXIT_SUCCESS);
}
}
DEFINE_TEST_MAIN(LOG_INFO);