diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in
index f3ca748862..801bda0939 100644
--- a/catalog/systemd.catalog.in
+++ b/catalog/systemd.catalog.in
@@ -460,6 +460,18 @@ this directory become inaccessible. To see those over-mounted files,
please manually mount the underlying file system to a secondary
location.
+-- 1edabb4eda2a49c19bc0206f24b43889
+Subject: Mount point path contains symlinks
+Defined-By: systemd
+Support: %SUPPORT_URL%
+
+The path @WHERE@ is specified as a mount point path (second field in /etc/fstab
+or Where= field in systemd unit file) and is not canonical, i.e. contains one
+or more symlinks as path elements. This is generally not supported and such
+mount attempts are refused, because the mount table information exposed by the
+kernel and the requested path would deviate once established. Please
+canonicalize paths before requesting a mount to be established.
+
-- 24d8d4452573402496068381a6312df2
Subject: A virtual machine or container has been started
Defined-By: systemd
diff --git a/man/systemd-mount.xml b/man/systemd-mount.xml
index d0eb9678d7..9723af64d6 100644
--- a/man/systemd-mount.xml
+++ b/man/systemd-mount.xml
@@ -340,6 +340,24 @@
+
+
+
+
+ Controls whether the specified path shall be canonicalized on the client side before
+ requesting the operation or not. Takes a boolean parameter, defaults to true. Note that for
+ non-local operation (i.e. when or -- are used)
+ canonicalization is implicitly turned off.
+
+ Canonicalization of path entails resolving of symlinks, .. path elements
+ and LABEL=/UUID= style device node expansion. If
+ canonicalization is disabled and the path contains a symlink element, .., or a
+ LABEL=/UUID=/… expansion the operation will fail.
+
+
+
+
+
diff --git a/src/core/automount.c b/src/core/automount.c
index ac81875442..5ac5dec8d9 100644
--- a/src/core/automount.c
+++ b/src/core/automount.c
@@ -556,7 +556,7 @@ static void automount_enter_waiting(Automount *a) {
set_clear(a->tokens);
- r = unit_fail_if_noncanonical(UNIT(a), a->where);
+ r = unit_fail_if_noncanonical_mount_path(UNIT(a), a->where);
if (r < 0)
goto fail;
diff --git a/src/core/mount.c b/src/core/mount.c
index d7557bf389..361d8e0318 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -8,11 +8,13 @@
#include "sd-messages.h"
#include "alloc-util.h"
+#include "chase.h"
#include "dbus-mount.h"
#include "dbus-unit.h"
#include "device.h"
#include "exec-credential.h"
#include "exit-status.h"
+#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
#include "fstab-util.h"
@@ -1183,22 +1185,32 @@ static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParamete
}
static void mount_enter_mounting(Mount *m) {
- MountParameters *p;
- bool source_is_dir = true;
+ _cleanup_close_ int fd = -EBADF;
+ _cleanup_free_ char *fn = NULL;
int r;
assert(m);
- r = unit_fail_if_noncanonical(UNIT(m), m->where);
- if (r < 0)
+ /* Validate that the path we are overmounting does not contain any symlinks, because if it does, we
+ * couldn't support that reasonably: the mounts in /proc/self/mountinfo would not be recognizable to
+ * us anymore. */
+ fd = chase_and_open_parent(m->where, /* root= */ NULL, CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755, &fn);
+ if (fd == -EREMCHG) {
+ r = unit_log_noncanonical_mount_path(UNIT(m), m->where);
goto fail;
+ }
+ if (fd < 0) {
+ log_unit_error_errno(UNIT(m), fd, "Failed to resolve parent of mount point '%s': %m", m->where);
+ goto fail;
+ }
- p = get_mount_parameters_fragment(m);
+ MountParameters *p = get_mount_parameters_fragment(m);
if (!p) {
r = log_unit_warning_errno(UNIT(m), SYNTHETIC_ERRNO(ENOENT), "No mount parameters to operate on.");
goto fail;
}
+ bool source_is_dir = true;
if (mount_is_bind(p)) {
r = is_dir(p->what, /* follow = */ true);
if (r < 0 && r != -ENOENT)
@@ -1207,10 +1219,11 @@ static void mount_enter_mounting(Mount *m) {
source_is_dir = false;
}
- if (source_is_dir)
- r = mkdir_p_label(m->where, m->directory_mode);
- else
- r = touch_file(m->where, /* parents = */ true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
+ r = make_mount_point_inode_from_mode(
+ fd,
+ fn,
+ source_is_dir ? S_IFDIR : S_IFREG,
+ m->directory_mode);
if (r < 0 && r != -EEXIST)
log_unit_warning_errno(UNIT(m), r, "Failed to create mount point '%s', ignoring: %m", m->where);
diff --git a/src/core/unit.c b/src/core/unit.c
index 3f4432f655..b784342c08 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -5125,14 +5125,28 @@ void unit_warn_if_dir_nonempty(Unit *u, const char* where) {
"WHERE=%s", where);
}
-int unit_fail_if_noncanonical(Unit *u, const char* where) {
- _cleanup_free_ char *canonical_where = NULL;
+int unit_log_noncanonical_mount_path(Unit *u, const char *where) {
+ assert(u);
+ assert(where);
+
+ /* No need to mention "." or "..", they would already have been rejected by unit_name_from_path() */
+ log_unit_struct(u, LOG_ERR,
+ "MESSAGE_ID=" SD_MESSAGE_NON_CANONICAL_MOUNT_STR,
+ LOG_UNIT_INVOCATION_ID(u),
+ LOG_UNIT_MESSAGE(u, "Mount path %s is not canonical (contains a symlink).", where),
+ "WHERE=%s", where);
+
+ return -ELOOP;
+}
+
+int unit_fail_if_noncanonical_mount_path(Unit *u, const char* where) {
int r;
assert(u);
assert(where);
- r = chase(where, NULL, CHASE_NONEXISTENT, &canonical_where, NULL);
+ _cleanup_free_ char *canonical_where = NULL;
+ r = chase(where, /* root= */ NULL, CHASE_NONEXISTENT, &canonical_where, /* ret_fd= */ NULL);
if (r < 0) {
log_unit_debug_errno(u, r, "Failed to check %s for symlinks, ignoring: %m", where);
return 0;
@@ -5142,14 +5156,7 @@ int unit_fail_if_noncanonical(Unit *u, const char* where) {
if (path_equal(where, canonical_where))
return 0;
- /* No need to mention "." or "..", they would already have been rejected by unit_name_from_path() */
- log_unit_struct(u, LOG_ERR,
- "MESSAGE_ID=" SD_MESSAGE_OVERMOUNTING_STR,
- LOG_UNIT_INVOCATION_ID(u),
- LOG_UNIT_MESSAGE(u, "Mount path %s is not canonical (contains a symlink).", where),
- "WHERE=%s", where);
-
- return -ELOOP;
+ return unit_log_noncanonical_mount_path(u, where);
}
bool unit_is_pristine(Unit *u) {
diff --git a/src/core/unit.h b/src/core/unit.h
index 45b7d72b7a..4b19d8f0f7 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -974,7 +974,8 @@ static inline PidRef* unit_main_pid(Unit *u) {
}
void unit_warn_if_dir_nonempty(Unit *u, const char* where);
-int unit_fail_if_noncanonical(Unit *u, const char* where);
+int unit_log_noncanonical_mount_path(Unit *u, const char *where);
+int unit_fail_if_noncanonical_mount_path(Unit *u, const char* where);
int unit_test_start_limit(Unit *u);
diff --git a/src/machine/machined-core.c b/src/machine/machined-core.c
index 90fd91f134..5c6d225051 100644
--- a/src/machine/machined-core.c
+++ b/src/machine/machined-core.c
@@ -230,11 +230,11 @@ int machine_get_addresses(Machine *machine, struct local_address **ret_addresses
/* except_fds = */ NULL,
/* n_except_fds = */ 0,
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL,
- /* pidns_fd = */ -1,
- /* mntns_fd = */ -1,
+ /* pidns_fd = */ -EBADF,
+ /* mntns_fd = */ -EBADF,
netns_fd,
- /* userns_fd = */ -1,
- /* root_fd = */ -1,
+ /* userns_fd = */ -EBADF,
+ /* root_fd = */ -EBADF,
&child);
if (r < 0)
return log_debug_errno(r, "Failed to fork(): %m");
@@ -347,8 +347,8 @@ int machine_get_os_release(Machine *machine, char ***ret_os_release) {
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL,
pidns_fd,
mntns_fd,
- /* netns_fd = */ -1,
- /* userns_fd = */ -1,
+ /* netns_fd = */ -EBADF,
+ /* userns_fd = */ -EBADF,
root_fd,
&child);
if (r < 0)
diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c
index d1b4773ce8..27fd318fb0 100644
--- a/src/mount/mount-tool.c
+++ b/src/mount/mount-tool.c
@@ -75,6 +75,7 @@ static bool arg_fsck = true;
static bool arg_aggressive_gc = false;
static bool arg_tmpfs = false;
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
+static bool arg_canonicalize = true;
STATIC_DESTRUCTOR_REGISTER(arg_mount_what, freep);
STATIC_DESTRUCTOR_REGISTER(arg_mount_where, freep);
@@ -90,14 +91,14 @@ static int parse_where(const char *input, char **ret_where) {
assert(input);
assert(ret_where);
- if (arg_transport == BUS_TRANSPORT_LOCAL) {
- r = chase(input, NULL, CHASE_NONEXISTENT, ret_where, NULL);
+ if (arg_transport == BUS_TRANSPORT_LOCAL && arg_canonicalize) {
+ r = chase(input, /* root= */ NULL, CHASE_NONEXISTENT, ret_where, /* ret_fd= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to make path %s absolute: %m", input);
} else {
if (!path_is_absolute(input))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Path must be absolute when operating remotely: %s",
+ "Path must be absolute when operating remotely or when canonicalization is turned off: %s",
input);
r = path_simplify_alloc(input, ret_where);
@@ -119,8 +120,9 @@ static int help(void) {
printf("systemd-mount [OPTIONS...] WHAT [WHERE]\n"
"systemd-mount [OPTIONS...] --tmpfs [NAME] WHERE\n"
"systemd-mount [OPTIONS...] --list\n"
- "%s [OPTIONS...] %sWHAT|WHERE...\n\n"
- "%sEstablish a mount or auto-mount point transiently.%s\n\n"
+ "%1$s [OPTIONS...] %7$sWHAT|WHERE...\n"
+ "\n%5$sEstablish a mount or auto-mount point transiently.%6$s\n"
+ "\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --no-block Do not wait until operation finished\n"
@@ -149,12 +151,16 @@ static int help(void) {
" -u --umount Unmount mount points\n"
" -G --collect Unload unit after it stopped, even when failed\n"
" -T --tmpfs Create a new tmpfs on the mount point\n"
- "\nSee the %s for details.\n",
+ " --canonicalize=BOOL Controls whether to canonicalize path before\n"
+ " operation\n"
+ "\nSee the %2$s for details.\n",
program_invocation_short_name,
- streq(program_invocation_short_name, "systemd-umount") ? "" : "--umount ",
+ link,
+ ansi_underline(),
+ ansi_normal(),
ansi_highlight(),
ansi_normal(),
- link);
+ streq(program_invocation_short_name, "systemd-umount") ? "" : "--umount ");
return 0;
}
@@ -181,6 +187,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_BIND_DEVICE,
ARG_LIST,
ARG_JSON,
+ ARG_CANONICALIZE,
};
static const struct option options[] = {
@@ -213,6 +220,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "collect", no_argument, NULL, 'G' },
{ "tmpfs", no_argument, NULL, 'T' },
{ "json", required_argument, NULL, ARG_JSON },
+ { "canonicalize", required_argument, NULL, ARG_CANONICALIZE },
{},
};
@@ -374,6 +382,13 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_CANONICALIZE:
+ r = parse_boolean_argument("--canonicalize=", optarg, &arg_canonicalize);
+ if (r < 0)
+ return r;
+
+ break;
+
case '?':
return -EINVAL;
@@ -441,21 +456,21 @@ static int parse_argv(int argc, char *argv[]) {
if (!arg_mount_what)
return log_oom();
- } else if (arg_transport == BUS_TRANSPORT_LOCAL) {
+ } else if (arg_transport == BUS_TRANSPORT_LOCAL && arg_canonicalize) {
_cleanup_free_ char *u = NULL;
u = fstab_node_to_udev_node(argv[optind]);
if (!u)
return log_oom();
- r = chase(u, NULL, 0, &arg_mount_what, NULL);
+ r = chase(u, /* root= */ NULL, /* flags= */ 0, &arg_mount_what, /* ret_fd= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to make path %s absolute: %m", u);
} else {
if (!path_is_absolute(argv[optind]))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Path must be absolute when operating remotely: %s",
+ "Path must be absolute when operating remotely or when canonicalization is turned off: %s",
argv[optind]);
r = path_simplify_alloc(argv[optind], &arg_mount_what);
@@ -1044,9 +1059,13 @@ static int action_umount(
int argc,
char **argv) {
- int r, r2 = 0;
+ int r, ret = 0;
- if (arg_transport != BUS_TRANSPORT_LOCAL) {
+ assert(bus);
+ assert(argv);
+ assert(argc > optind);
+
+ if (arg_transport != BUS_TRANSPORT_LOCAL || !arg_canonicalize) {
for (int i = optind; i < argc; i++) {
_cleanup_free_ char *p = NULL;
@@ -1054,46 +1073,52 @@ static int action_umount(
if (r < 0)
return r;
- r = stop_mounts(bus, p);
- if (r < 0)
- r2 = r;
+ RET_GATHER(ret, stop_mounts(bus, p));
}
- return r2;
+ return ret;
}
for (int i = optind; i < argc; i++) {
_cleanup_free_ char *u = NULL, *p = NULL;
- struct stat st;
u = fstab_node_to_udev_node(argv[i]);
if (!u)
return log_oom();
- r = chase(u, NULL, 0, &p, NULL);
+ _cleanup_close_ int fd = -EBADF;
+ r = chase(u, /* root= */ NULL, 0, &p, &fd);
if (r < 0) {
- r2 = log_error_errno(r, "Failed to make path %s absolute: %m", argv[i]);
+ RET_GATHER(ret, log_error_errno(r, "Failed to make path %s absolute: %m", u));
continue;
}
- if (stat(p, &st) < 0)
+ struct stat st;
+ if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Can't stat %s (from %s): %m", p, argv[i]);
- if (S_ISBLK(st.st_mode))
- r = umount_by_device_node(bus, p);
- else if (S_ISREG(st.st_mode))
- r = umount_loop(bus, p);
- else if (S_ISDIR(st.st_mode))
- r = stop_mounts(bus, p);
+ r = is_mount_point_at(fd, /* filename= */ NULL, /* flags= */ 0);
+ fd = safe_close(fd); /* before continuing make sure the dir is not keeping anything busy */
+ if (r > 0)
+ RET_GATHER(ret, stop_mounts(bus, p));
else {
- log_error("Invalid file type: %s (from %s)", p, argv[i]);
- r = -EINVAL;
- }
+ /* This can realistically fail on pre-5.8 kernels that do not tell us via statx() if
+ * something is a mount point, hence handle this gracefully, and go by type as we did
+ * in pre-v258 times. */
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine if '%s' is a mount point, ignoring: %m", u);
- if (r < 0)
- r2 = r;
+ if (S_ISBLK(st.st_mode))
+ RET_GATHER(ret, umount_by_device_node(bus, p));
+ else if (S_ISREG(st.st_mode))
+ RET_GATHER(ret, umount_loop(bus, p));
+ else if (S_ISDIR(st.st_mode))
+ RET_GATHER(ret, stop_mounts(bus, p));
+ else
+ RET_GATHER(ret, log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid file type: %s (from %s)", p, argv[i]));
+ }
}
- return r2;
+ return ret;
}
static int acquire_mount_type(sd_device *d) {
diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c
index 75a18051bc..3ff15a2214 100644
--- a/src/shared/mount-util.c
+++ b/src/shared/mount-util.c
@@ -958,10 +958,7 @@ static int mount_in_namespace_legacy(
/* Second, we mount the source file or directory to a directory inside of our MS_SLAVE playground. */
mount_tmp = strjoina(mount_slave, "/mount");
- if (flags & MOUNT_IN_NAMESPACE_IS_IMAGE)
- r = mkdir_p(mount_tmp, 0700);
- else
- r = make_mount_point_inode_from_stat(chased_src_st, mount_tmp, 0700);
+ r = make_mount_point_inode_from_mode(AT_FDCWD, mount_tmp, (flags & MOUNT_IN_NAMESPACE_IS_IMAGE) ? S_IFDIR : chased_src_st->st_mode, 0700);
if (r < 0) {
log_debug_errno(r, "Failed to create temporary mount point %s: %m", mount_tmp);
goto finish;
@@ -1038,8 +1035,18 @@ static int mount_in_namespace_legacy(
goto finish;
}
- r = namespace_fork("(sd-bindmnt)", "(sd-bindmnt-inner)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM,
- pidns_fd, mntns_fd, -1, -1, root_fd, &child);
+ r = namespace_fork(
+ "(sd-bindmnt)",
+ "(sd-bindmnt-inner)",
+ /* except_fds= */ NULL,
+ /* n_except_fds= */ 0,
+ FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM,
+ pidns_fd,
+ mntns_fd,
+ /* netns_fd= */ -EBADF,
+ /* userns_fd= */ -EBADF,
+ root_fd,
+ &child);
if (r < 0)
goto finish;
if (r == 0) {
@@ -1047,12 +1054,15 @@ static int mount_in_namespace_legacy(
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
- if (flags & MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY) {
- if (!(flags & MOUNT_IN_NAMESPACE_IS_IMAGE)) {
- (void) mkdir_parents(dest, 0755);
- (void) make_mount_point_inode_from_stat(chased_src_st, dest, 0700);
- } else
- (void) mkdir_p(dest, 0755);
+ _cleanup_close_ int dest_fd = -EBADF;
+ _cleanup_free_ char *dest_fn = NULL;
+ r = chase(dest, /* root= */ NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME|((flags & MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY) ? CHASE_MKDIR_0755 : 0), &dest_fn, &dest_fd);
+ if (r < 0)
+ log_debug_errno(r, "Failed to pin parent directory of mount '%s', ignoring: %m", dest);
+ else if (flags & MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY) {
+ r = make_mount_point_inode_from_mode(dest_fd, dest_fn, (flags & MOUNT_IN_NAMESPACE_IS_IMAGE) ? S_IFDIR : chased_src_st->st_mode, 0700);
+ if (r < 0)
+ log_debug_errno(r, "Failed to make mount point inode of mount '%s', ignoring: %m", dest);
}
/* Fifth, move the mount to the right place inside */
@@ -1066,7 +1076,7 @@ static int mount_in_namespace_legacy(
if (!mount_inside)
report_errno_and_exit(errno_pipe_fd[1], log_oom_debug());
- r = mount_nofollow_verbose(LOG_DEBUG, mount_inside, dest, NULL, MS_MOVE, NULL);
+ r = mount_nofollow_verbose(LOG_DEBUG, mount_inside, dest_fd >= 0 ? FORMAT_PROC_FD_PATH(dest_fd) : dest, /* fstype= */ NULL, MS_MOVE, /* options= */ NULL);
if (r < 0)
report_errno_and_exit(errno_pipe_fd[1], r);
@@ -1237,8 +1247,14 @@ static int mount_in_namespace(
if (r == 0) {
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
+ _cleanup_close_ int dest_fd = -EBADF;
+ _cleanup_free_ char *dest_fn = NULL;
+ r = chase(dest, /* root= */ NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME|((flags & MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY) ? CHASE_MKDIR_0755 : 0), &dest_fn, &dest_fd);
+ if (r < 0)
+ report_errno_and_exit(errno_pipe_fd[1], r);
+
if (flags & MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY)
- (void) mkdir_parents(dest, 0755);
+ (void) make_mount_point_inode_from_mode(dest_fd, dest_fn, img ? S_IFDIR : st.st_mode, 0700);
if (img) {
DissectImageFlags f =
@@ -1258,12 +1274,8 @@ static int mount_in_namespace(
/* uid_range= */ UID_INVALID,
/* userns_fd= */ -EBADF,
f);
- } else {
- if (flags & MOUNT_IN_NAMESPACE_MAKE_FILE_OR_DIRECTORY)
- (void) make_mount_point_inode_from_stat(&st, dest, 0700);
-
+ } else
r = mount_exchange_graceful(new_mount_fd, dest, /* mount_beneath= */ true);
- }
report_errno_and_exit(errno_pipe_fd[1], r);
}
@@ -1688,17 +1700,17 @@ int bind_mount_submounts(
return ret;
}
-int make_mount_point_inode_from_stat(const struct stat *st, const char *dest, mode_t mode) {
- assert(st);
+int make_mount_point_inode_from_mode(int dir_fd, const char *dest, mode_t source_mode, mode_t target_mode) {
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(dest);
- if (S_ISDIR(st->st_mode))
- return mkdir_label(dest, mode);
+ if (S_ISDIR(source_mode))
+ return mkdirat_label(dir_fd, dest, target_mode & 07777);
else
- return RET_NERRNO(mknod(dest, S_IFREG|(mode & ~0111), 0));
+ return RET_NERRNO(mknodat(dir_fd, dest, S_IFREG|(target_mode & 07666), 0)); /* Mask off X bit */
}
-int make_mount_point_inode_from_path(const char *source, const char *dest, mode_t mode) {
+int make_mount_point_inode_from_path(const char *source, const char *dest, mode_t access_mode) {
struct stat st;
assert(source);
@@ -1707,7 +1719,7 @@ int make_mount_point_inode_from_path(const char *source, const char *dest, mode_
if (stat(source, &st) < 0)
return -errno;
- return make_mount_point_inode_from_stat(&st, dest, mode);
+ return make_mount_point_inode_from_mode(AT_FDCWD, dest, st.st_mode, access_mode);
}
int trigger_automount_at(int dir_fd, const char *path) {
diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h
index 895fe2b2cd..fa55c9d32c 100644
--- a/src/shared/mount-util.h
+++ b/src/shared/mount-util.h
@@ -178,8 +178,8 @@ int bind_mount_submounts(
const char *source,
const char *target);
-/* Creates a mount point (not parents) based on the source path or stat - ie, a file or a directory */
-int make_mount_point_inode_from_stat(const struct stat *st, const char *dest, mode_t mode);
+/* Creates a mount point (without any parents) based on the source path or mode - i.e., a file or a directory */
+int make_mount_point_inode_from_mode(int dir_fd, const char *dest, mode_t source_mode, mode_t target_mode);
int make_mount_point_inode_from_path(const char *source, const char *dest, mode_t mode);
int trigger_automount_at(int dir_fd, const char *path);
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index 9b1839d43a..92c60c49ae 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -297,22 +297,28 @@ static int move_submounts(const char *src, const char *dst) {
assert_se(suffix = path_startswith(m->path, src));
+ if (fstat(m->mount_fd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat %s: %m", m->path);
+
t = path_join(dst, suffix);
if (!t)
return log_oom();
- if (fstat(m->mount_fd, &st) < 0)
- return log_error_errno(errno, "Failed to stat %s: %m", m->path);
-
- r = mkdir_parents(t, 0755);
+ _cleanup_free_ char *fn = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ r = chase(t, /* root= */ NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755, &fn, &fd);
if (r < 0)
- return log_error_errno(r, "Failed to create parent directories of %s: %m", t);
+ return log_error_errno(r, "Failed to create and pin parent directory of %s: %m", t);
- r = make_mount_point_inode_from_stat(&st, t, 0755);
+ r = make_mount_point_inode_from_mode(fd, fn, st.st_mode, 0755);
if (r < 0 && r != -EEXIST)
return log_error_errno(r, "Failed to create mountpoint %s: %m", t);
- r = mount_follow_verbose(LOG_ERR, m->path, t, NULL, MS_BIND|MS_REC, NULL);
+ _cleanup_close_ int child_fd = openat(fd, fn, O_PATH|O_CLOEXEC);
+ if (child_fd < 0)
+ return log_error_errno(errno, "Failed to pin mountpoint %s: %m", t);
+
+ r = mount_follow_verbose(LOG_ERR, m->path, FORMAT_PROC_FD_PATH(child_fd), /* fstype= */ NULL, MS_BIND|MS_REC, /* options= */ NULL);
if (r < 0)
return r;
diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h
index 441f4e6888..cc30add200 100644
--- a/src/systemd/sd-messages.h
+++ b/src/systemd/sd-messages.h
@@ -190,6 +190,8 @@ _SD_BEGIN_DECLARATIONS;
#define SD_MESSAGE_OVERMOUNTING SD_ID128_MAKE(1d,ee,03,69,c7,fc,47,36,b7,09,9b,38,ec,b4,6e,e7)
#define SD_MESSAGE_OVERMOUNTING_STR SD_ID128_MAKE_STR(1d,ee,03,69,c7,fc,47,36,b7,09,9b,38,ec,b4,6e,e7)
+#define SD_MESSAGE_NON_CANONICAL_MOUNT SD_ID128_MAKE(1e,da,bb,4e,da,2a,49,c1,9b,c0,20,6f,24,b4,38,89)
+#define SD_MESSAGE_NON_CANONICAL_MOUNT_STR SD_ID128_MAKE_STR(1e,da,bb,4e,da,2a,49,c1,9b,c0,20,6f,24,b4,38,89)
#define SD_MESSAGE_UNIT_OOMD_KILL SD_ID128_MAKE(d9,89,61,1b,15,e4,4c,9d,bf,31,e3,c8,12,56,e4,ed)
#define SD_MESSAGE_UNIT_OOMD_KILL_STR SD_ID128_MAKE_STR(d9,89,61,1b,15,e4,4c,9d,bf,31,e3,c8,12,56,e4,ed)
diff --git a/src/test/test-mount-util.c b/src/test/test-mount-util.c
index 5fb4d23f3e..33220abe71 100644
--- a/src/test/test-mount-util.c
+++ b/src/test/test-mount-util.c
@@ -266,9 +266,9 @@ TEST(make_mount_point_inode) {
assert_se(rmdir(dst_dir) == 0);
assert_se(stat(src_file, &st) == 0);
- assert_se(make_mount_point_inode_from_stat(&st, dst_file, 0755) >= 0);
+ assert_se(make_mount_point_inode_from_mode(AT_FDCWD, dst_file, st.st_mode, 0755) >= 0);
assert_se(stat(src_dir, &st) == 0);
- assert_se(make_mount_point_inode_from_stat(&st, dst_dir, 0755) >= 0);
+ assert_se(make_mount_point_inode_from_mode(AT_FDCWD, dst_dir, st.st_mode, 0755) >= 0);
assert_se(stat(dst_dir, &st) == 0);
assert_se(S_ISDIR(st.st_mode));
diff --git a/test/units/TEST-74-AUX-UTILS.mount.sh b/test/units/TEST-74-AUX-UTILS.mount.sh
index ecfdb0640f..85539452a0 100755
--- a/test/units/TEST-74-AUX-UTILS.mount.sh
+++ b/test/units/TEST-74-AUX-UTILS.mount.sh
@@ -39,3 +39,21 @@ systemd-mount --type=overlay --options="lowerdir=/etc,upperdir=$WORK_DIR/upper,w
touch "$WORK_DIR/overlay/foo"
test -e "$WORK_DIR/upper/foo"
systemd-umount "$WORK_DIR/overlay"
+
+# Validate that we cannot mount through a symlink or ..
+mkdir "$WORK_DIR"/flurb
+ln -s flurb "$WORK_DIR"/knarb
+systemd-mount --canonicalize=no --tmpfs "$WORK_DIR"/flurb/shlum
+systemd-umount "$WORK_DIR/"/flurb/shlum
+(! systemd-mount --canonicalize=no --tmpfs "$WORK_DIR"/knarb/shlum)
+systemd-mount --canonicalize=yes --tmpfs "$WORK_DIR"/knarb/shlum
+systemd-umount "$WORK_DIR/"/flurb/shlum
+(! systemd-mount --canonicalize=no --tmpfs "$WORK_DIR"/flurb/../flurb/shlum)
+systemd-mount --canonicalize=yes --tmpfs "$WORK_DIR"/flurb/../flurb/shlum
+systemd-umount "$WORK_DIR/"/flurb/shlum
+
+# Validate that we can correctly create dir and reg files inodes if needed
+systemd-mount --tmpfs "$WORK_DIR"/flurb/shlum/some/more/dirs
+systemd-umount "$WORK_DIR/"/flurb/shlum/some/more/dirs
+systemd-mount /bin/ls "$WORK_DIR"/flurb/shlum/some/more/dirs/file -o bind
+systemd-umount "$WORK_DIR/"/flurb/shlum/some/more/dirs/file