Skip to content

Commit 97efc44

Browse files
committed
sysroot: Handle invisible sysroot
1 parent d58d564 commit 97efc44

File tree

2 files changed

+162
-27
lines changed

2 files changed

+162
-27
lines changed

src/libostree/ostree-sysroot-private.h

+10
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ struct OstreeSysroot
6969
GLnxLockFile lock;
7070

7171
OstreeSysrootLoadState loadstate;
72+
/*
73+
* XXX: It's very bad that mount namespaces are per thread, not per process.
74+
* In a multi-threading environment, it's troublesome to ensure current thread is always in the ns.
75+
* So, do not use OstreeSysroot from another thread if you want mount namespace.
76+
*/
7277
gboolean mount_namespace_in_use; /* TRUE if caller has told us they used CLONE_NEWNS */
7378
gboolean root_is_ostree_booted; /* TRUE if sysroot is / and we are booted via ostree */
7479
/* The device/inode for / and /etc, used to detect booted deployment */
@@ -114,8 +119,13 @@ struct OstreeSysroot
114119
// Relative to /boot, consumed by ostree-boot-complete.service
115120
#define _OSTREE_FINALIZE_STAGED_FAILURE_PATH "ostree/finalize-failure.stamp"
116121

122+
gboolean _ostree_sysroot_ensure_visible (OstreeSysroot *self, GError **error);
123+
117124
gboolean _ostree_sysroot_ensure_writable (OstreeSysroot *self, GError **error);
118125

126+
gboolean
127+
_ostree_sysroot_enter_mount_namespace (OstreeSysroot *self, GError **error);
128+
119129
void _ostree_sysroot_emit_journal_msg (OstreeSysroot *self, const char *msg);
120130

121131
gboolean _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion,

src/libostree/ostree-sysroot.c

+152-27
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,25 @@ ostree_sysroot_new_default (void)
227227
return ostree_sysroot_new (NULL);
228228
}
229229

230+
static gboolean
231+
_ostree_in_root_mount_namespace (gboolean *out_val, GError **error)
232+
{
233+
/* glnx_readlinkat_malloc does not use cancellable acually. */
234+
g_autofree char *mntns_pid1
235+
= glnx_readlinkat_malloc (AT_FDCWD, "/proc/1/ns/mnt", NULL, error);
236+
if (!mntns_pid1)
237+
return glnx_prefix_error (error, "Reading /proc/1/ns/mnt");
238+
/* mount namespace is per-thread, not per-process */
239+
g_autofree char *cur_thread = g_strdup_printf ("/proc/%d/ns/mnt", gettid ());
240+
g_autofree char *mntns_cur
241+
= glnx_readlinkat_malloc (AT_FDCWD, cur_thread, NULL, error);
242+
if (!mntns_cur)
243+
return glnx_prefix_error (error, "Reading %s", cur_thread);
244+
245+
*out_val = g_str_equal (mntns_pid1, mntns_cur);
246+
return TRUE;
247+
}
248+
230249
/**
231250
* ostree_sysroot_set_mount_namespace_in_use:
232251
*
@@ -251,9 +270,41 @@ ostree_sysroot_set_mount_namespace_in_use (OstreeSysroot *self)
251270
/* Must be before we're loaded, as otherwise we'd have to close/reopen all our
252271
fds, e.g. the repo */
253272
g_return_if_fail (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_LOADED);
273+
gboolean in_root;
274+
g_autoptr (GError) local_error = NULL;
275+
g_assert (_ostree_in_root_mount_namespace (&in_root, &local_error) && !in_root);
254276
self->mount_namespace_in_use = TRUE;
255277
}
256278

279+
gboolean
280+
_ostree_sysroot_enter_mount_namespace (OstreeSysroot *self, GError **error)
281+
{
282+
/* Do nothing if we're not privileged */
283+
if (getuid () != 0)
284+
return TRUE;
285+
286+
/* We also assume operating on non-booted roots won't have a readonly sysroot */
287+
if (!self->root_is_ostree_booted)
288+
return TRUE;
289+
290+
// If the mount namespaces are the same, we need to unshare().
291+
gboolean in_root;
292+
g_return_val_if_fail (_ostree_in_root_mount_namespace (&in_root, error), FALSE);
293+
if (in_root)
294+
{
295+
if (unshare (CLONE_NEWNS) < 0)
296+
return glnx_throw_errno_prefix (error, "Failed to invoke unshare(CLONE_NEWNS)");
297+
298+
/* Ensure what we do in our mount namespace do not leak to outside */
299+
if (mount (NULL, "/", NULL, MS_PRIVATE | MS_REC | MS_SILENT, NULL) < 0)
300+
return glnx_throw_errno_prefix (error, "Failed to set the mount propagation to private");
301+
}
302+
303+
ostree_sysroot_set_mount_namespace_in_use (self);
304+
305+
return TRUE;
306+
}
307+
257308
/**
258309
* ostree_sysroot_initialize_with_mount_namespace:
259310
*
@@ -284,32 +335,7 @@ ostree_sysroot_initialize_with_mount_namespace (OstreeSysroot *self, GCancellabl
284335
if (!ostree_sysroot_initialize (self, error))
285336
return FALSE;
286337

287-
/* Do nothing if we're not privileged */
288-
if (getuid () != 0)
289-
return TRUE;
290-
291-
/* We also assume operating on non-booted roots won't have a readonly sysroot */
292-
if (!self->root_is_ostree_booted)
293-
return TRUE;
294-
295-
g_autofree char *mntns_pid1
296-
= glnx_readlinkat_malloc (AT_FDCWD, "/proc/1/ns/mnt", cancellable, error);
297-
if (!mntns_pid1)
298-
return glnx_prefix_error (error, "Reading /proc/1/ns/mnt");
299-
g_autofree char *mntns_self
300-
= glnx_readlinkat_malloc (AT_FDCWD, "/proc/self/ns/mnt", cancellable, error);
301-
if (!mntns_self)
302-
return glnx_prefix_error (error, "Reading /proc/self/ns/mnt");
303-
304-
// If the mount namespaces are the same, we need to unshare().
305-
if (strcmp (mntns_pid1, mntns_self) == 0)
306-
{
307-
if (unshare (CLONE_NEWNS) < 0)
308-
return glnx_throw_errno_prefix (error, "Failed to invoke unshare(CLONE_NEWNS)");
309-
}
310-
311-
ostree_sysroot_set_mount_namespace_in_use (self);
312-
return TRUE;
338+
return _ostree_sysroot_enter_mount_namespace (self, error);
313339
}
314340

315341
/**
@@ -374,11 +400,94 @@ remount_writable (const char *path, gboolean *did_remount, GError **error)
374400
return TRUE;
375401
}
376402

403+
static gboolean
404+
_ostree_sysroot_invisible (OstreeSysroot *self, gboolean *out_val, GError **error)
405+
{
406+
if (!self->root_is_ostree_booted)
407+
return FALSE;
408+
409+
if (!glnx_fstatat_allow_noent (self->sysroot_fd, "ostree/repo", NULL, 0, error))
410+
return FALSE;
411+
412+
if (errno == 0)
413+
{
414+
*out_val = FALSE;
415+
return TRUE;
416+
}
417+
418+
// root_is_ostree_booted is true so we can use AT_FDCWD here
419+
if (!glnx_fstatat_allow_noent (AT_FDCWD, OTCORE_RUN_OSTREE_PRIVATE "/sysroot-ns", NULL, 0, error))
420+
return FALSE;
421+
422+
if (errno != 0)
423+
{
424+
*out_val = FALSE;
425+
return TRUE;
426+
}
427+
428+
*out_val = TRUE;
429+
return TRUE;
430+
}
431+
432+
/* Make /sysroot visible */
433+
gboolean
434+
_ostree_sysroot_ensure_visible (OstreeSysroot *self, GError **error)
435+
{
436+
if (!ostree_sysroot_initialize (self, error))
437+
return FALSE;
438+
439+
/* Do nothing if no mount namespace is in use */
440+
if (!self->mount_namespace_in_use)
441+
return TRUE;
442+
443+
/* If we aren't operating on a booted system, then we don't
444+
* do anything with mounts.
445+
*/
446+
if (!self->root_is_ostree_booted)
447+
return TRUE;
448+
449+
gboolean invisible;
450+
if (!_ostree_sysroot_invisible (self, &invisible, error))
451+
return FALSE;
452+
/* Handle invisible sysroot */
453+
if (invisible)
454+
{
455+
glnx_autofd int sysroot_ns_fd = -1;
456+
if (!glnx_openat_rdonly (AT_FDCWD, OTCORE_RUN_OSTREE_PRIVATE "/sysroot-ns", TRUE, &sysroot_ns_fd, error))
457+
return FALSE;
458+
459+
g_autofree char *cur_ns = g_strdup_printf ("/proc/%d/ns/mnt", gettid ());
460+
glnx_autofd int cur_ns_fd = -1;
461+
if (!glnx_openat_rdonly (AT_FDCWD, cur_ns, TRUE, &cur_ns_fd, error))
462+
return FALSE;
463+
464+
if (setns (sysroot_ns_fd, CLONE_NEWNS) < 0)
465+
return glnx_throw_errno_prefix (error, "setns");
466+
467+
glnx_autofd int tree_fd = (int)syscall (SYS_open_tree, AT_FDCWD, "/", 1 /* OPEN_TREE_CLONE */ | O_CLOEXEC);
468+
if (tree_fd < 0)
469+
return glnx_throw_errno_prefix (error, "open_tree");
470+
471+
if (setns (cur_ns_fd, CLONE_NEWNS) < 0)
472+
abort (); // it's unsafe to continue if we cannot switch back
473+
474+
if (syscall (SYS_move_mount, tree_fd, "", AT_FDCWD, "/sysroot", 4 /* MOVE_MOUNT_F_EMPTY_PATH */) < 0)
475+
return glnx_throw_errno_prefix (error, "move_mount");
476+
}
477+
478+
/* Now close and reopen our file descriptors */
479+
ostree_sysroot_unload (self);
480+
if (!ensure_sysroot_fd (self, error))
481+
return FALSE;
482+
483+
return TRUE;
484+
}
485+
377486
/* Remount /sysroot read-write if necessary */
378487
gboolean
379488
_ostree_sysroot_ensure_writable (OstreeSysroot *self, GError **error)
380489
{
381-
if (!ostree_sysroot_initialize (self, error))
490+
if (!_ostree_sysroot_ensure_visible (self, error))
382491
return FALSE;
383492

384493
/* Do nothing if no mount namespace is in use */
@@ -1063,6 +1172,22 @@ ostree_sysroot_initialize (OstreeSysroot *self, GError **error)
10631172
g_debug ("root_is_ostree_booted: %d", self->root_is_ostree_booted);
10641173
self->loadstate = OSTREE_SYSROOT_LOAD_STATE_INIT;
10651174
}
1175+
else
1176+
{
1177+
return TRUE;
1178+
}
1179+
1180+
gboolean invisible;
1181+
if (!_ostree_sysroot_invisible (self, &invisible, error))
1182+
return FALSE;
1183+
1184+
if (invisible)
1185+
{
1186+
if (!_ostree_sysroot_enter_mount_namespace (self, error))
1187+
return FALSE;
1188+
if (!_ostree_sysroot_ensure_visible (self, error))
1189+
return FALSE;
1190+
}
10661191

10671192
return TRUE;
10681193
}

0 commit comments

Comments
 (0)