1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
From fbceefded6645de693d576cd988a703a6f60d207 Mon Sep 17 00:00:00 2001
From: Karel Zak <kzak@redhat.com>
Date: Thu, 8 Jan 2015 11:51:58 +0100
Subject: unshare: add --setgroups=deny|allow
Since Linux 3.19 the file /proc/self/setgroups controls setgroups(2)
syscall usage in user namespaces. This patch provides command line knob
for this feature.
The new --setgroups does not automatically implies --user to avoid
complexity, it's user's responsibility to use it in right context. The
exception is --map-root-user which is mutually exclusive to
--setgroups=allow.
CC: "Eric W. Biederman" <ebiederm@xmission.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
diff --git a/sys-utils/unshare.1 b/sys-utils/unshare.1
index 1aa9bcb..c9e159d 100644
--- a/sys-utils/unshare.1
+++ b/sys-utils/unshare.1
@@ -85,6 +85,21 @@ conveniently gain capabilities needed to manage various aspects of the newly cre
namespaces (such as configuring interfaces in the network namespace or mounting filesystems in
the mount namespace) even when run unprivileged. As a mere convenience feature, it does not support
more sophisticated use cases, such as mapping multiple ranges of UIDs and GIDs.
+This option implies --setgroups=deny.
+.TP
+.BR \-s , " \-\-setgroups \fIallow|deny\fP"
+Allow or deny
+.BR setgroups (2)
+syscall in user namespaces.
+
+.BR setgroups(2)
+is only callable with CAP_SETGID and CAP_SETGID in a user
+namespace (since Linux 3.19) does not give you permission to call setgroups(2)
+until after GID map has been set. The GID map is writable by root when
+.BR setgroups(2)
+is enabled and GID map becomes writable by unprivileged processes when
+.BR setgroups(2)
+is permamently disabled.
.TP
.BR \-V , " \-\-version"
Display version information and exit.
diff --git a/sys-utils/unshare.c b/sys-utils/unshare.c
index 9fdce93..83c4a00 100644
--- a/sys-utils/unshare.c
+++ b/sys-utils/unshare.c
@@ -39,12 +39,39 @@
#include "pathnames.h"
#include "all-io.h"
-static void disable_setgroups(void)
+enum {
+ SETGROUPS_NONE = -1,
+ SETGROUPS_DENY = 0,
+ SETGROUPS_ALLOW = 1,
+};
+
+static const char *setgroups_strings[] =
+{
+ [SETGROUPS_DENY] = "deny",
+ [SETGROUPS_ALLOW] = "allow"
+};
+
+static int setgroups_str2id(const char *str)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(setgroups_strings); i++)
+ if (strcmp(str, setgroups_strings[i]) == 0)
+ return i;
+
+ errx(EXIT_FAILURE, _("unsupported --setgroups argument '%s'"), str);
+}
+
+static void setgroups_control(int action)
{
const char *file = _PATH_PROC_SETGROUPS;
- const char *deny = "deny";
+ const char *cmd;
int fd;
+ if (action < 0 || (size_t) action >= ARRAY_SIZE(setgroups_strings))
+ return;
+ cmd = setgroups_strings[action];
+
fd = open(file, O_WRONLY);
if (fd < 0) {
if (errno == ENOENT)
@@ -52,7 +79,7 @@ static void disable_setgroups(void)
err(EXIT_FAILURE, _("cannot open %s"), file);
}
- if (write_all(fd, deny, strlen(deny)))
+ if (write_all(fd, cmd, strlen(cmd)))
err(EXIT_FAILURE, _("write failed %s"), file);
close(fd);
}
@@ -94,6 +121,7 @@ static void usage(int status)
fputs(_(" -f, --fork fork before launching <program>\n"), out);
fputs(_(" --mount-proc[=<dir>] mount proc filesystem first (implies --mount)\n"), out);
fputs(_(" -r, --map-root-user map current user to root (implies --user)\n"), out);
+ fputs(_(" -s, --setgroups <allow|deny> control setgroups syscall in user namespaces\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(USAGE_HELP, out);
@@ -106,7 +134,8 @@ static void usage(int status)
int main(int argc, char *argv[])
{
enum {
- OPT_MOUNTPROC = CHAR_MAX + 1
+ OPT_MOUNTPROC = CHAR_MAX + 1,
+ OPT_SETGROUPS
};
static const struct option longopts[] = {
{ "help", no_argument, 0, 'h' },
@@ -120,9 +149,11 @@ int main(int argc, char *argv[])
{ "fork", no_argument, 0, 'f' },
{ "mount-proc", optional_argument, 0, OPT_MOUNTPROC },
{ "map-root-user", no_argument, 0, 'r' },
+ { "setgroups", required_argument, 0, OPT_SETGROUPS },
{ NULL, 0, 0, 0 }
};
+ int setgrpcmd = SETGROUPS_NONE;
int unshare_flags = 0;
int c, forkit = 0, maproot = 0;
const char *procmnt = NULL;
@@ -170,6 +201,9 @@ int main(int argc, char *argv[])
unshare_flags |= CLONE_NEWUSER;
maproot = 1;
break;
+ case OPT_SETGROUPS:
+ setgrpcmd = setgroups_str2id(optarg);
+ break;
default:
usage(EXIT_FAILURE);
}
@@ -199,10 +233,20 @@ int main(int argc, char *argv[])
}
if (maproot) {
- disable_setgroups();
+ if (setgrpcmd == SETGROUPS_ALLOW)
+ errx(EXIT_FAILURE, _("options --setgroups=allow and "
+ "--map-root-user are mutually exclusive."));
+
+ /* since Linux 3.19 unprivileged writing of /proc/self/gid_map
+ * has s been disabled unless /proc/self/setgroups is written
+ * first to permanently disable the ability to call setgroups
+ * in that user namespace. */
+ setgroups_control(SETGROUPS_DENY);
map_id(_PATH_PROC_UIDMAP, 0, real_euid);
map_id(_PATH_PROC_GIDMAP, 0, real_egid);
- }
+
+ } else if (setgrpcmd != SETGROUPS_NONE)
+ setgroups_control(setgrpcmd);
if (procmnt &&
(mount("none", procmnt, NULL, MS_PRIVATE|MS_REC, NULL) != 0 ||
--
cgit v0.10.2
|