Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 57 additions & 2 deletions src/libcrun/linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ struct private_data_s
/* Set once the process root is the container rootfs and the host root
can no longer be reached by path. */
bool host_root_switched;
/* The rootfs path on the host, used to recognize bind mount sources
inside the container rootfs. */
char *host_rootfs;
};

struct linux_namespace_s
Expand Down Expand Up @@ -215,6 +218,7 @@ cleanup_private_data (void *private_data)
free (p->container_notify_socket_path);
free (p->external_descriptors);
free (p->maskdir_proc_path);
free (p->host_rootfs);
free (p);
}

Expand Down Expand Up @@ -2891,6 +2895,33 @@ get_bind_mount_locked_flags (int source_mountfd, const char *source, runtime_spe
return sfs.f_flags & (MS_RDONLY | MS_NOSUID | MS_NODEV | MS_NOEXEC) & ~cleared;
}

/* If PATH is lexically inside the container rootfs ROOTFS, return the part
of it relative to ROOTFS, otherwise NULL. Such a bind mount source refers
to what is in the container rootfs at the time of the mount, including the
mounts done before it, so it cannot be opened beforehand. */
static const char *
get_path_in_rootfs (const char *rootfs, const char *path)
{
size_t len;

if (rootfs == NULL || path == NULL)
return NULL;

len = strlen (rootfs);
while (len > 0 && rootfs[len - 1] == '/')
len--;

/* With "/" as the rootfs, every path is in it, and there is nothing to
do differently. */
if (len == 0)
return NULL;

if (strncmp (path, rootfs, len) != 0 || (path[len] != '/' && path[len] != '\0'))
return NULL;

return consume_slashes (path + len);
}

static int
process_single_mount (libcrun_container_t *container, const char *rootfs,
runtime_spec_schema_defs_mount *mount,
Expand Down Expand Up @@ -2931,6 +2962,21 @@ process_single_mount (libcrun_container_t *container, const char *rootfs,
if (type == NULL && (flags & MS_BIND) == 0)
return crun_make_error (err, 0, "invalid mount type for `%s`", mount->destination);

if ((flags & MS_BIND) && source_mountfd < 0 && ! (extra_flags & OPTION_COPY_SYMLINK))
{
const char *rel_source = get_path_in_rootfs (get_private_data (container)->host_rootfs, mount->source);

if (rel_source)
{
unsigned long propagation = (flags & (MS_SHARED | MS_SLAVE | MS_UNBINDABLE)) ? 0 : MS_PRIVATE;

source_mountfd = get_bind_mount (get_private_data (container)->rootfsfd, rel_source, (flags & MS_REC) != 0,
false, (extra_flags & OPTION_SRC_NOFOLLOW) != 0, propagation, err);
if (UNLIKELY (source_mountfd < 0))
return crun_error_wrap (err, "open mount source `%s`", mount->source);
}
}

if ((flags & MS_BIND) && mount->source && has_mount_flag_options (mount)
&& (get_private_data (container)->unshare_flags & CLONE_NEWUSER))
flags |= get_bind_mount_locked_flags (source_mountfd, mount->source, mount);
Expand Down Expand Up @@ -4077,6 +4123,9 @@ setup_mount_namespace (libcrun_container_t *container, bool no_pivot, char **roo
if (UNLIKELY (ret < 0))
return ret;

free (get_private_data (container)->host_rootfs);
get_private_data (container)->host_rootfs = xstrdup (*rootfs);

/* Pre-create mounts and cache paths before pivot_root,
while the host file system is still reachable. */
for (i = 0; i < def->mounts_len; i++)
Expand Down Expand Up @@ -4132,7 +4181,8 @@ setup_mount_namespace (libcrun_container_t *container, bool no_pivot, char **roo
&& i < get_private_data (container)->n_copy_symlink_targets
&& get_private_data (container)->copy_symlink_targets[i];

if (mount_fds && def->mounts[i]->source != NULL && ! has_cached_target)
if (mount_fds && def->mounts[i]->source != NULL && ! has_cached_target
&& get_path_in_rootfs (*rootfs, def->mounts[i]->source) == NULL)
{
libcrun_error_t tmp_err = NULL;
unsigned long propagation = MS_PRIVATE;
Expand Down Expand Up @@ -5904,6 +5954,7 @@ prepare_and_send_mount_mounts (libcrun_container_t *container, pid_t pid, int sy
runtime_spec_schema_config_schema *def = container->container_def;
cleanup_close_map struct libcrun_fd_map *mount_fds = NULL;
bool has_userns = (get_private_data (container)->unshare_flags & CLONE_NEWUSER) ? true : false;
cleanup_free char *rootfs = NULL;
size_t how_many = 0;
size_t i;
int ret;
Expand All @@ -5913,6 +5964,9 @@ prepare_and_send_mount_mounts (libcrun_container_t *container, pid_t pid, int sy

mount_fds = make_libcrun_fd_map (def->mounts_len);

if (def->root && def->root->path)
rootfs = realpath (def->root->path, NULL);

if (! has_userns)
{
int is_in_userns = check_running_in_user_namespace (err);
Expand All @@ -5937,7 +5991,8 @@ prepare_and_send_mount_mounts (libcrun_container_t *container, pid_t pid, int sy
Skip copy-symlink mounts: open_tree follows the symlink, losing the
link itself; let process_single_mount handle them. */
if (mount_fd < 0 && (has_mappings || has_userns) && is_bind_mount (def->mounts[i], &recursive, &nofollow)
&& ! mount_option_exists (def->mounts[i], "copy-symlink"))
&& ! mount_option_exists (def->mounts[i], "copy-symlink")
&& get_path_in_rootfs (rootfs, def->mounts[i]->source) == NULL)
{
unsigned long propagation = 0;

Expand Down
36 changes: 36 additions & 0 deletions tests/test_mounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,41 @@ def test_mount_bind_to_rootfs():
_, _ = run_and_get_output(conf, hide_stderr=True)
return 0

def test_mount_bind_source_in_rootfs():
# A bind mount source inside the container rootfs refers to what is
# there at the time of the mount, including the mounts done before it.
conf = base_config()
conf['process']['args'] = ['/init', 'cat', '/b/file']
add_all_namespaces(conf)
srcdir = tempfile.mkdtemp(dir=get_tests_root())
with open(os.path.join(srcdir, "file"), "w") as f:
f.write("hello")

def prepare_rootfs(rootfs):
rootfs = os.path.realpath(rootfs)
os.makedirs(os.path.join(rootfs, "a"))
os.makedirs(os.path.join(rootfs, "b"))
# The absolute rootfs path is known only now, so add the mounts to the
# config file that is already written.
config_path = os.path.join(os.path.dirname(rootfs), "config.json")
with open(config_path) as f:
config = json.load(f)
config['mounts'] += [
{"destination": "/a", "type": "bind", "source": srcdir, "options": ["bind"]},
{"destination": "/b", "type": "bind", "source": os.path.join(rootfs, "a"), "options": ["bind"]},
]
with open(config_path, "w") as f:
json.dump(config, f)

try:
out, _ = run_and_get_output(conf, hide_stderr=True, callback_prepare_rootfs=prepare_rootfs)
finally:
shutil.rmtree(srcdir)
if "hello" not in out:
sys.stderr.write("# unexpected output: %s\n" % out)
return -1
return 0

def test_mount_tmpfs_to_rootfs():
# tmpcopyup on "/" is rejected: after pivot_root is moved before mounts,
# there is no original rootfs content to copy from.
Expand Down Expand Up @@ -1569,6 +1604,7 @@ def test_bind_host_root():
"mount-symlink-not-existing" : test_mount_symlink_not_existing,
"mount-dev" : test_mount_dev,
"mount-bind-to-rootfs": test_mount_bind_to_rootfs,
"mount-bind-source-in-rootfs": test_mount_bind_source_in_rootfs,
"mount-tmpfs-to-rootfs": test_mount_tmpfs_to_rootfs,
"mount-nodev" : test_mount_nodev,
"mount-path-with-multiple-slashes" : test_mount_path_with_multiple_slashes,
Expand Down
Loading