include/carefulputc.h \
include/cctype.h \
include/c.h \
+ include/caputils.h \
include/closestream.h \
include/colors.h \
include/color-names.h \
--- /dev/null
+/*
+ * 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 */
if LINUX
libcommon_la_SOURCES += \
+ lib/caputils.c \
lib/linux_version.c \
lib/loopdev.c
endif
--- /dev/null
+/*
+ * 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;
+}
#include <unistd.h>
#include "c.h"
+#include "caputils.h"
#include "closestream.h"
#include "nls.h"
#include "optutils.h"
# 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. */
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) {
/* 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);
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);
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++)
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,
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
#include "nls.h"
#include "c.h"
+#include "caputils.h"
#include "closestream.h"
#include "namespace.h"
#include "exec_shell.h"
static int npersists; /* number of persistent namespaces */
-
enum {
SETGROUPS_NONE = -1,
SETGROUPS_DENY = 0,
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);
OPT_PROPAGATION,
OPT_SETGROUPS,
OPT_KILLCHILD,
+ OPT_KEEPCAPS,
};
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "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' },
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);
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;
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]);