From cef4decf0436953031b4a8ea91a8aec89459a8fd Mon Sep 17 00:00:00 2001 From: James Peach Date: Thu, 17 Jan 2019 14:16:54 -0800 Subject: [PATCH] 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. --- include/Makemodule.am | 1 + include/caputils.h | 34 +++++++++++++++++++++++++++++ lib/Makemodule.am | 1 + lib/caputils.c | 45 ++++++++++++++++++++++++++++++++++++++ sys-utils/setpriv.c | 41 +++++----------------------------- sys-utils/unshare.1 | 4 ++++ sys-utils/unshare.c | 51 ++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 140 insertions(+), 37 deletions(-) create mode 100644 include/caputils.h create mode 100644 lib/caputils.c diff --git a/include/Makemodule.am b/include/Makemodule.am index 3e7f7a01ef..08f0e997ee 100644 --- a/include/Makemodule.am +++ b/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 \ diff --git a/include/caputils.h b/include/caputils.h new file mode 100644 index 0000000000..852903a6ee --- /dev/null +++ b/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 + +#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 */ diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 862a06c17c..3d989abb14 100644 --- a/lib/Makemodule.am +++ b/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 diff --git a/lib/caputils.c b/lib/caputils.c new file mode 100644 index 0000000000..17e9c01bb0 --- /dev/null +++ b/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 + +#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; +} diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c index e612f7269d..f8a03640be 100644 --- a/sys-utils/setpriv.c +++ b/sys-utils/setpriv.c @@ -32,6 +32,7 @@ #include #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, diff --git a/sys-utils/unshare.1 b/sys-utils/unshare.1 index db8915fe9e..4f1239d7ba 100644 --- a/sys-utils/unshare.1 +++ b/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 diff --git a/sys-utils/unshare.c b/sys-utils/unshare.c index f32e4294c5..009ad7f184 100644 --- a/sys-utils/unshare.c +++ b/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= run the command with root directory set to \n"), out); fputs(_(" -w, --wd= change working directory to \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]); -- 2.47.3