namespace.c revision ecabcf8b6edcc856ec2fd5bd43fc675a8fe04731
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <limits.h>
#include "strv.h"
#include "util.h"
#include "path-util.h"
#include "namespace.h"
#include "missing.h"
#include "execute.h"
#include "loopback-setup.h"
#include "mkdir.h"
#include "dev-setup.h"
#include "def.h"
#include "label.h"
typedef enum MountMode {
/* This is ordered by priority! */
} MountMode;
typedef struct BindMount {
const char *path;
bool done;
bool ignore;
} BindMount;
char **i;
assert(p);
STRV_FOREACH(i, strv) {
(*p)->ignore = false;
(*p)->done = false;
(*p)->ignore = true;
(*i)++;
}
if (!path_is_absolute(*i))
return -EINVAL;
(*p)->path = *i;
(*p)++;
}
return 0;
}
static int mount_path_compare(const void *a, const void *b) {
const BindMount *p = a, *q = b;
/* If the paths are equal, check the mode */
return -1;
return 1;
return 0;
}
/* If the paths are not equal, then order prefixes first */
return 1;
return -1;
return 0;
}
static void drop_duplicates(BindMount *m, unsigned *n) {
assert(m);
assert(n);
/* The first one wins */
continue;
*t = *f;
previous = t;
t++;
}
*n = t - m;
}
static const char devnodes[] =
char temporary_mount[] = "/tmp/namespace-dev-XXXXXX";
const char *d, *dev = NULL, *devpts = NULL, *devshm = NULL, *devkdbus = NULL, *devhugepages = NULL, *devmqueue = NULL, *devlog = NULL, *devptmx = NULL;
int r;
assert(m);
u = umask(0000);
if (!mkdtemp(temporary_mount))
return -errno;
r = -errno;
goto fail;
}
r = -errno;
goto fail;
}
if (r < 0) {
r = -errno;
goto fail;
}
NULSTR_FOREACH(d, devnodes) {
if (r < 0) {
continue;
r = -errno;
goto fail;
}
r = -EINVAL;
goto fail;
}
continue;
if (!dn) {
r = -ENOMEM;
goto fail;
}
if (r < 0) {
r = -errno;
goto fail;
}
}
r = -errno;
goto fail;
}
return 0;
fail:
if (devpts)
if (devshm)
if (devkdbus)
if (devhugepages)
if (devmqueue)
return r;
}
static int mount_kdbus(BindMount *m) {
char temporary_mount[] = "/tmp/kdbus-dev-XXXXXX";
int r;
assert(m);
u = umask(0000);
if (!mkdtemp(temporary_mount)) {
log_error("Failed create temp dir: %m");
return -errno;
}
r = -errno;
goto fail;
}
* bind-mount the custom endpoint over. */
r = -errno;
goto fail;
}
r = -errno;
goto fail;
}
if (r < 0) {
r = -errno;
goto fail;
}
if (!basepath) {
r = -ENOMEM;
goto fail;
}
r = -errno;
goto fail;
}
return 0;
fail:
if (busnode) {
}
return r;
}
static int apply_mount(
BindMount *m,
const char *tmp_dir,
const char *var_tmp_dir) {
const char *what;
int r;
assert(m);
switch (m->mode) {
case INACCESSIBLE:
/* First, get rid of everything that is below if there
* is anything... Then, overmount it with an
* inaccessible directory. */
umount_recursive(m->path, 0);
what = "/run/systemd/inaccessible";
break;
case READONLY:
case READWRITE:
/* Nothing to mount here, we just later toggle the
* MS_RDONLY bit for the mount point */
return 0;
case PRIVATE_TMP:
break;
case PRIVATE_VAR_TMP:
what = var_tmp_dir;
break;
case PRIVATE_DEV:
return mount_dev(m);
case PRIVATE_BUS_ENDPOINT:
return mount_kdbus(m);
default:
assert_not_reached("Unknown mode");
}
if (r >= 0)
return 0;
return r;
}
static int make_read_only(BindMount *m) {
int r;
assert(m);
r = bind_remount_recursive(m->path, true);
r = bind_remount_recursive(m->path, false);
else
r = 0;
return 0;
return r;
}
int setup_namespace(
char** read_write_dirs,
char** read_only_dirs,
char** inaccessible_dirs,
const char* tmp_dir,
const char* var_tmp_dir,
const char* bus_endpoint_path,
bool private_dev,
unsigned mount_flags) {
unsigned n;
int r = 0;
if (mount_flags == 0)
if (unshare(CLONE_NEWNS) < 0)
return -errno;
if (n > 0) {
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
if (tmp_dir) {
m->path = "/tmp";
m->mode = PRIVATE_TMP;
m++;
}
if (var_tmp_dir) {
m->mode = PRIVATE_VAR_TMP;
m++;
}
if (private_dev) {
m->path = "/dev";
m->mode = PRIVATE_DEV;
m++;
}
if (bus_endpoint_path) {
m->path = bus_endpoint_path;
m->mode = PRIVATE_BUS_ENDPOINT;
m++;
}
if (protect_home != PROTECT_HOME_NO) {
r = append_mounts(&m, STRV_MAKE("-/home", "-/run/user", "-/root"), protect_home == PROTECT_HOME_READ_ONLY ? READONLY : INACCESSIBLE);
if (r < 0)
return r;
}
if (protect_system != PROTECT_SYSTEM_NO) {
r = append_mounts(&m, protect_system == PROTECT_SYSTEM_FULL ? STRV_MAKE("/usr", "-/boot", "/etc") : STRV_MAKE("/usr", "-/boot"), READONLY);
if (r < 0)
return r;
}
drop_duplicates(mounts, &n);
}
if (n > 0) {
/* Remount / as SLAVE so that nothing now mounted in the namespace
shows up in the parent */
return -errno;
if (r < 0)
goto fail;
}
r = make_read_only(m);
if (r < 0)
goto fail;
}
}
/* Remount / as the desired mode. Not that this will not
* reestablish propagation from our side to the host, since
* what's disconnected is disconnected. */
r = -errno;
goto fail;
}
return 0;
fail:
if (n > 0) {
if (m->done)
}
return r;
}
_cleanup_free_ char *x = NULL;
char bid[SD_ID128_STRING_MAX];
int r;
/* We include the boot id in the directory so that after a
* reboot we can easily identify obsolete directories. */
r = sd_id128_get_boot(&boot_id);
if (r < 0)
return r;
x = strjoin(prefix, "/systemd-private-", sd_id128_to_string(boot_id, bid), "-", id, "-XXXXXX", NULL);
if (!x)
return -ENOMEM;
RUN_WITH_UMASK(0077)
if (!mkdtemp(x))
return -errno;
RUN_WITH_UMASK(0000) {
char *y;
y = strappenda(x, "/tmp");
return -errno;
}
*path = x;
x = NULL;
return 0;
}
char *a, *b;
int r;
if (r < 0)
return r;
if (r < 0) {
char *t;
t = strappenda(a, "/tmp");
rmdir(t);
rmdir(a);
free(a);
return r;
}
*tmp_dir = a;
*var_tmp_dir = b;
return 0;
}
union {
} control = {};
.msg_control = &control,
.msg_controllen = sizeof(control),
};
int r;
assert(netns_storage_socket[0] >= 0);
/* We use the passed socketpair as a storage buffer for our
* namespace reference fd. Whatever process runs this first
* shall create a new namespace, all others should just join
* it. To serialize that we use a file lock on the socket
* pair.
*
* It's a bit crazy, but hey, works great! */
return -errno;
r = -errno;
goto fail;
}
/* Nothing stored yet, so let's create a new namespace */
if (unshare(CLONE_NEWNET) < 0) {
r = -errno;
goto fail;
}
if (netns < 0) {
r = -errno;
goto fail;
}
r = 1;
} else {
/* Yay, found something, so let's join the namespace */
}
}
r = -errno;
goto fail;
}
r = 0;
}
r = -errno;
goto fail;
}
fail:
return r;
}
static const char *const protect_home_table[_PROTECT_HOME_MAX] = {
[PROTECT_HOME_NO] = "no",
[PROTECT_HOME_YES] = "yes",
[PROTECT_HOME_READ_ONLY] = "read-only",
};
static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = {
[PROTECT_SYSTEM_NO] = "no",
[PROTECT_SYSTEM_YES] = "yes",
[PROTECT_SYSTEM_FULL] = "full",
};