Browse Source

unshare: add --keep-caps option

Add the --keep-caps option to unshare to preserve capabilities that
are granted when creating a new user namespace. This allows the child
process to retain privilege within the new user namespace without also
being UID 0.
pull/864/head
James Peach 4 years ago
parent
commit
cef4decf04
  1. 1
      include/Makemodule.am
  2. 34
      include/caputils.h
  3. 1
      lib/Makemodule.am
  4. 45
      lib/caputils.c
  5. 41
      sys-utils/setpriv.c
  6. 4
      sys-utils/unshare.1
  7. 51
      sys-utils/unshare.c

1
include/Makemodule.am

@ -7,6 +7,7 @@ dist_noinst_HEADERS += \
include/carefulputc.h \
include/cctype.h \
include/c.h \
include/caputils.h \
include/closestream.h \
include/colors.h \
include/color-names.h \

34
include/caputils.h

@ -0,0 +1,34 @@
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef CAPUTILS_H
#define CAPUTILS_H
#include <linux/capability.h>
#ifndef PR_CAP_AMBIENT
# define PR_CAP_AMBIENT 47
# define PR_CAP_AMBIENT_IS_SET 1
# define PR_CAP_AMBIENT_RAISE 2
# define PR_CAP_AMBIENT_LOWER 3
#endif
extern int capset(cap_user_header_t header, cap_user_data_t data);
extern int capget(cap_user_header_t header, const cap_user_data_t data);
extern int cap_last_cap(void);
#endif /* CAPUTILS_H */

1
lib/Makemodule.am

@ -31,6 +31,7 @@ libcommon_la_SOURCES = \
if LINUX
libcommon_la_SOURCES += \
lib/caputils.c \
lib/linux_version.c \
lib/loopdev.c
endif

45
lib/caputils.c

@ -0,0 +1,45 @@
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include "caputils.h"
#include "pathnames.h"
int cap_last_cap(void)
{
/* CAP_LAST_CAP is untrustworthy. */
static int ret = -1;
int matched;
FILE *f;
if (ret != -1)
return ret;
f = fopen(_PATH_PROC_CAPLASTCAP, "r");
if (!f) {
ret = CAP_LAST_CAP; /* guess */
return ret;
}
matched = fscanf(f, "%d", &ret);
fclose(f);
if (matched != 1)
ret = CAP_LAST_CAP; /* guess */
return ret;
}

41
sys-utils/setpriv.c

@ -32,6 +32,7 @@
#include <unistd.h>
#include "c.h"
#include "caputils.h"
#include "closestream.h"
#include "nls.h"
#include "optutils.h"
@ -48,13 +49,6 @@
# define PR_GET_NO_NEW_PRIVS 39
#endif
#ifndef PR_CAP_AMBIENT
# define PR_CAP_AMBIENT 47
# define PR_CAP_AMBIENT_IS_SET 1
# define PR_CAP_AMBIENT_RAISE 2
# define PR_CAP_AMBIENT_LOWER 3
#endif
#define SETPRIV_EXIT_PRIVERR 127 /* how we exit when we fail to set privs */
/* The shell to set SHELL env.variable if none is given in the user's passwd entry. */
@ -161,31 +155,6 @@ static void __attribute__((__noreturn__)) usage(void)
exit(EXIT_SUCCESS);
}
static int real_cap_last_cap(void)
{
/* CAP_LAST_CAP is untrustworthy. */
static int ret = -1;
int matched;
FILE *f;
if (ret != -1)
return ret;
f = fopen(_PATH_PROC_CAPLASTCAP, "r");
if (!f) {
ret = CAP_LAST_CAP; /* guess */
return ret;
}
matched = fscanf(f, "%d", &ret);
fclose(f);
if (matched != 1)
ret = CAP_LAST_CAP; /* guess */
return ret;
}
static int has_cap(enum cap_type which, unsigned int i)
{
switch (which) {
@ -206,7 +175,7 @@ static int has_cap(enum cap_type which, unsigned int i)
/* Returns the number of capabilities printed. */
static int print_caps(FILE *f, enum cap_type which)
{
int i, n = 0, max = real_cap_last_cap();
int i, n = 0, max = cap_last_cap();
for (i = 0; i <= max; i++) {
int ret = has_cap(which, i);
@ -436,7 +405,7 @@ static void dump(int dumplevel)
static void list_known_caps(void)
{
int i, max = real_cap_last_cap();
int i, max = cap_last_cap();
for (i = 0; i <= max; i++) {
const char *name = capng_capability_to_name(i);
@ -565,7 +534,7 @@ static void do_caps(enum cap_type type, const char *caps)
int i;
/* It would be really bad if -all didn't drop all
* caps. It's better to just fail. */
if (real_cap_last_cap() > CAP_LAST_CAP)
if (cap_last_cap() > CAP_LAST_CAP)
errx(SETPRIV_EXIT_PRIVERR,
_("libcap-ng is too old for \"all\" caps"));
for (i = 0; i <= CAP_LAST_CAP; i++)
@ -575,7 +544,7 @@ static void do_caps(enum cap_type type, const char *caps)
if (0 <= cap)
cap_update(action, type, cap);
else if (sscanf(c + 1, "cap_%d", &cap) == 1
&& 0 <= cap && cap <= real_cap_last_cap())
&& 0 <= cap && cap <= cap_last_cap())
cap_update(action, type, cap);
else
errx(EXIT_FAILURE,

4
sys-utils/unshare.1

@ -138,6 +138,10 @@ by bind mount.
Fork the specified \fIprogram\fR as a child process of \fBunshare\fR rather than
running it directly. This is useful when creating a new PID namespace.
.TP
.BR \-\-keep\-caps
When the \fB--user\fP option is given, ensure that capabilities granted
in the user namespace are preserved in the child process.
.TP
.BR \-\-kill\-child [ =\fIsigname ]
When \fBunshare\fR terminates, have \fIsigname\fP be sent to the forked child process.
Combined with \fB--pid\fR this allows for an easy and reliable killing of the entire

51
sys-utils/unshare.c

@ -36,6 +36,7 @@
#include "nls.h"
#include "c.h"
#include "caputils.h"
#include "closestream.h"
#include "namespace.h"
#include "exec_shell.h"
@ -69,7 +70,6 @@ static struct namespace_file {
static int npersists; /* number of persistent namespaces */
enum {
SETGROUPS_NONE = -1,
SETGROUPS_DENY = 0,
@ -278,6 +278,7 @@ static void __attribute__((__noreturn__)) usage(void)
fputs(_(" --propagation slave|shared|private|unchanged\n"
" modify mount propagation in mount namespace\n"), out);
fputs(_(" --setgroups allow|deny control the setgroups syscall in user namespaces\n"), out);
fputs(_(" --keep-caps retain capabilities granted in user namespaces\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(_(" -R, --root=<dir> run the command with root directory set to <dir>\n"), out);
fputs(_(" -w, --wd=<dir> change working directory to <dir>\n"), out);
@ -298,6 +299,7 @@ int main(int argc, char *argv[])
OPT_PROPAGATION,
OPT_SETGROUPS,
OPT_KILLCHILD,
OPT_KEEPCAPS,
};
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
@ -318,6 +320,7 @@ int main(int argc, char *argv[])
{ "map-current-user", no_argument, NULL, 'c' },
{ "propagation", required_argument, NULL, OPT_PROPAGATION },
{ "setgroups", required_argument, NULL, OPT_SETGROUPS },
{ "keep-caps", no_argument, NULL, OPT_KEEPCAPS },
{ "setuid", required_argument, NULL, 'S' },
{ "setgid", required_argument, NULL, 'G' },
{ "root", required_argument, NULL, 'R' },
@ -339,6 +342,7 @@ int main(int argc, char *argv[])
int force_uid = 0, force_gid = 0;
uid_t uid = 0, real_euid = geteuid();
gid_t gid = 0, real_egid = getegid();
int keepcaps = 0;
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
@ -421,6 +425,10 @@ int main(int argc, char *argv[])
kill_child_signo = SIGKILL;
}
break;
case OPT_KEEPCAPS:
keepcaps = 1;
cap_last_cap(); /* Force last cap to be cached before we fork. */
break;
case 'S':
uid = strtoul_or_err(optarg, _("failed to parse uid"));
force_uid = 1;
@ -556,6 +564,47 @@ int main(int argc, char *argv[])
if (force_uid && setuid(uid) < 0) /* change UID */
err(EXIT_FAILURE, _("setuid failed"));
/* We use capabilities system calls to propagate the permitted
* capabilities into the ambient set because we have already
* forked so are in async-signal-safe context. */
if (keepcaps && (unshare_flags & CLONE_NEWUSER)) {
struct __user_cap_header_struct header = {
.version = _LINUX_CAPABILITY_VERSION_3,
.pid = 0,
};
struct __user_cap_data_struct payload[_LINUX_CAPABILITY_U32S_3] = { 0 };
if (capget(&header, payload) < 0) {
err(EXIT_FAILURE, _("capget failed"));
}
/* In order the make capabilities ambient, we first need to ensure
* that they are all inheritable. */
payload[0].inheritable = payload[0].permitted;
payload[1].inheritable = payload[1].permitted;
if (capset(&header, payload) < 0) {
err(EXIT_FAILURE, _("capset failed"));
}
uint64_t effective = ((uint64_t)payload[1].effective << 32) | (uint64_t)payload[0].effective;
for (int cap = 0; cap < 64; cap++) {
/* This is the same check as cap_valid(), but using
* the runtime value for the last valid cap. */
if (cap < 0 || cap > cap_last_cap()) {
continue;
}
if (effective & (1 << cap)) {
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) < 0) {
err(EXIT_FAILURE, _("prctl(PR_CAP_AMBIENT) failed"));
}
}
}
}
if (optind < argc) {
execvp(argv[optind], argv + optind);
errexec(argv[optind]);

Loading…
Cancel
Save