1
1
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:
Zbigniew Jędrzejewski-Szmek 2016-10-20 23:41:21 -04:00
parent 24597ee0e6
commit 299a34c11a
5 changed files with 111 additions and 10 deletions

View File

@ -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>

View File

@ -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

View File

@ -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;

View File

@ -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_;

View File

@ -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();