mstack: introduce "mstack" concept
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user