mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-22 13:33:56 +03:00
detect-virt: add --private-users switch to check if a userns is active
Various things don't work when we're running in a user namespace, but it's pretty hard to reliably detect if that is true. A function is added which looks at /proc/self/uid_map and returns false if the default "0 0 UINT32_MAX" is found, and true if it finds anything else. This misses the case where an 1:1 mapping with the full range was used, but I don't know how to distinguish this case. 'systemd-detect-virt --private-users' is very similar to 'systemd-detect-virt --chroot', but we check for a user namespace instead.
This commit is contained in:
parent
24597ee0e6
commit
299a34c11a
@ -50,7 +50,8 @@
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>systemd-detect-virt <arg choice="opt" rep="repeat">OPTIONS</arg></command>
|
||||
<command>systemd-detect-virt</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
@ -217,6 +218,16 @@
|
||||
environment or not.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--private-users</option></term>
|
||||
|
||||
<listitem><para>Detect whether invoked in a user namespace. In this mode, no
|
||||
output is written, but the return value indicates whether the process was invoked
|
||||
inside of a user namespace or not. See
|
||||
<citerefentry project='man-pages'><refentrytitle>user_namespaces</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||
for more information.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-q</option></term>
|
||||
<term><option>--quiet</option></term>
|
||||
@ -243,7 +254,8 @@
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry>
|
||||
<citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>namespaces</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
|
@ -908,7 +908,8 @@
|
||||
<varname>systemd-nspawn</varname>,
|
||||
<varname>docker</varname>,
|
||||
<varname>rkt</varname> to test
|
||||
against a specific implementation. See
|
||||
against a specific implementation, or
|
||||
<varname>private-users</varname> to check whether we are running in a user namespace. See
|
||||
<citerefentry><refentrytitle>systemd-detect-virt</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
for a full list of known virtualization technologies and their
|
||||
identifiers. If multiple virtualization technologies are
|
||||
|
@ -485,6 +485,76 @@ int detect_virtualization(void) {
|
||||
return r;
|
||||
}
|
||||
|
||||
static int userns_has_mapping(const char *name) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
size_t n_allocated = 0;
|
||||
ssize_t n;
|
||||
uint32_t a, b, c;
|
||||
int r;
|
||||
|
||||
f = fopen(name, "re");
|
||||
if (!f) {
|
||||
log_debug_errno(errno, "Failed to open %s: %m", name);
|
||||
return errno == -ENOENT ? false : -errno;
|
||||
}
|
||||
|
||||
n = getline(&buf, &n_allocated, f);
|
||||
if (n < 0) {
|
||||
if (feof(f)) {
|
||||
log_debug("%s is empty, we're in an uninitialized user namespace", name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return log_debug_errno(errno, "Failed to read %s: %m", name);
|
||||
}
|
||||
|
||||
r = sscanf(buf, "%"PRIu32" %"PRIu32" %"PRIu32, &a, &b, &c);
|
||||
if (r < 3)
|
||||
return log_debug_errno(errno, "Failed to parse %s: %m", name);
|
||||
|
||||
if (a == 0 && b == 0 && c == UINT32_MAX) {
|
||||
/* The kernel calls mappings_overlap() and does not allow overlaps */
|
||||
log_debug("%s has a full 1:1 mapping", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Anything else implies that we are in a user namespace */
|
||||
log_debug("Mapping found in %s, we're in a user namespace", name);
|
||||
return true;
|
||||
}
|
||||
|
||||
int running_in_userns(void) {
|
||||
_cleanup_free_ char *line = NULL;
|
||||
int r;
|
||||
|
||||
r = userns_has_mapping("/proc/self/uid_map");
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
r = userns_has_mapping("/proc/self/gid_map");
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
/* "setgroups" file was added in kernel v3.18-rc6-15-g9cc46516dd. It is also
|
||||
* possible to compile a kernel without CONFIG_USER_NS, in which case "setgroups"
|
||||
* also does not exist. We cannot distinguish those two cases, so assume that
|
||||
* we're running on a stripped-down recent kernel, rather than on an old one,
|
||||
* and if the file is not found, return false.
|
||||
*/
|
||||
r = read_one_line_file("/proc/self/setgroups", &line);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "/proc/self/setgroups: %m");
|
||||
return r == -ENOENT ? false : r;
|
||||
}
|
||||
|
||||
truncate_nl(line);
|
||||
r = streq(line, "deny");
|
||||
/* See user_namespaces(7) for a description of this "setgroups" contents. */
|
||||
log_debug("/proc/self/setgroups contains \"%s\", %s user namespace", line, r ? "in" : "not in");
|
||||
return r;
|
||||
}
|
||||
|
||||
int running_in_chroot(void) {
|
||||
int ret;
|
||||
|
||||
|
@ -67,6 +67,7 @@ int detect_vm(void);
|
||||
int detect_container(void);
|
||||
int detect_virtualization(void);
|
||||
|
||||
int running_in_userns(void);
|
||||
int running_in_chroot(void);
|
||||
|
||||
const char *virtualization_to_string(int v) _const_;
|
||||
|
@ -31,6 +31,7 @@ static enum {
|
||||
ONLY_VM,
|
||||
ONLY_CONTAINER,
|
||||
ONLY_CHROOT,
|
||||
ONLY_PRIVATE_USERS,
|
||||
} arg_mode = ANY_VIRTUALIZATION;
|
||||
|
||||
static void help(void) {
|
||||
@ -41,6 +42,7 @@ static void help(void) {
|
||||
" -c --container Only detect whether we are run in a container\n"
|
||||
" -v --vm Only detect whether we are run in a VM\n"
|
||||
" -r --chroot Detect whether we are run in a chroot() environment\n"
|
||||
" --private-users Only detect whether we are running in a user namespace\n"
|
||||
" -q --quiet Don't output anything, just set return value\n"
|
||||
, program_invocation_short_name);
|
||||
}
|
||||
@ -48,7 +50,8 @@ static void help(void) {
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_PRIVATE_USERS,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@ -57,6 +60,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "container", no_argument, NULL, 'c' },
|
||||
{ "vm", no_argument, NULL, 'v' },
|
||||
{ "chroot", no_argument, NULL, 'r' },
|
||||
{ "private-users", no_argument, NULL, ARG_PRIVATE_USERS },
|
||||
{ "quiet", no_argument, NULL, 'q' },
|
||||
{}
|
||||
};
|
||||
@ -85,6 +89,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_mode = ONLY_CONTAINER;
|
||||
break;
|
||||
|
||||
case ARG_PRIVATE_USERS:
|
||||
arg_mode = ONLY_PRIVATE_USERS;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
arg_mode = ONLY_VM;
|
||||
break;
|
||||
@ -151,6 +159,15 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
return r ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
|
||||
case ONLY_PRIVATE_USERS:
|
||||
r = running_in_userns();
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to check for user namespace: %m");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return r ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
|
||||
case ANY_VIRTUALIZATION:
|
||||
default:
|
||||
r = detect_virtualization();
|
||||
|
Loading…
Reference in New Issue
Block a user