Skip to content

Commit c9903fd

Browse files
committed
Attempt to continue working without namespacing if apparmor denies user namespaces.
If apparmor is configured to deny unprivileged user namespaces, the unshare(CLONE_NEWUSER) call will still work, it just won't have any privileges (like CAP_SYS_ADMIN) that we need. This will manifest itself in the deny_setgroups() function when we try to open /proc/pid/setgroups for write. When the unshare() call itself fails, we try to continue on without namespacing enabled. This allows at least for diminished functionality where .tup/mnt paths are visibile to subprocesses. However, when the deny_setgroups() function fails, tup was just quitting outright. It would be better if we could continue in the diminished capacity as if the unshare() call itself failed. Unfortunately, since unshare() isn't failing, we're already in a new namespace by the time we know that it doesn't have the capabilities we need. Therefore we need to close out the child master_fork process and create a new one in the original namespace. This is the reason for the funkiness of writing out a special restart token ("2"). To prevent these error messages from showing up in the first place, one could set TUP_NO_NAMESPACING=1 in the environment to force tup to run without trying namespacing in the first place. Another option is to update the apparmor configuration for tup. For example, Ubuntu comes with a default tup configuration in /etc/apparmor.d/tup that contains: --- abi <abi/4.0>, include <tunables/global> profile tup /usr/bin/tup flags=(unconfined) { userns, # Site-specific additions and overrides. See local/README for details. include if exists <local/tup> } --- The path (here, /usr/bin/tup) needs to match the path of the actual tup binary. So if building tup from source, these paths won't match. You could add another profile in the same file or create a new apparmor profile like so: --- profile localtup /path/to/local/tup/tup flags=(unconfined) { userns, # Site-specific additions and overrides. See local/README for details. include if exists <local/tup> } --- Fixes #502.
1 parent 1091529 commit c9903fd

File tree

2 files changed

+65
-9
lines changed

2 files changed

+65
-9
lines changed

src/tup/server/master_fork.c

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,12 @@ static int deny_setgroups(pid_t pid)
101101
*/
102102
return 0;
103103
}
104-
perror(filename);
105-
fprintf(stderr, "tup error: Unable to deny setgroups when setting up user namespace.\n");
104+
fprintf(stderr, "tup warning: Unable to deny setgroups when setting up the user namespace.\n");
106105
return -1;
107106
}
108107
if(write(fd, "deny", 4) < 0) {
109108
perror(filename);
110-
fprintf(stderr, "tup error: Unable to write \"deny\" to setgroups.\n");
109+
fprintf(stderr, "tup warning: Unable to write \"deny\" to setgroups.\n");
111110
return -1;
112111
}
113112
close(fd);
@@ -139,8 +138,15 @@ static int update_map(pid_t pid, const char *mapfile, uid_t id)
139138
close(fd);
140139
return 0;
141140
}
141+
142142
#endif
143143

144+
static void disable_namespacing_hint(void)
145+
{
146+
fprintf(stderr, "tup warning: Trying without namespacing enabled. Subprocesses will have '.tup/mnt' paths for the current working directory and some dependencies may be missed.\n");
147+
fprintf(stderr, "You may need to update the apparmor profile in order to enable namespacing properly in tup (make sure the path in apparmor matches the path of the tup executable), or you can run tup with TUP_NO_NAMESPACING=1 to disable this warning.\n");
148+
}
149+
144150
int server_pre_init(void)
145151
{
146152
char c = 0;
@@ -176,11 +182,27 @@ int server_pre_init(void)
176182
uid_t origgid = getgid();
177183
pid_t pid = getpid();
178184
if(unshare(CLONE_NEWUSER) < 0) {
179-
fprintf(stderr, "tup warning: unshare(CLONE_NEWUSER) failed, and tup is not privileged. Subprocesses will have '.tup/mnt' paths for the current working directory and some dependencies may be missed.\n");
185+
fprintf(stderr, "tup warning: unshare(CLONE_NEWUSER) failed, and tup is not privileged.\n");
186+
disable_namespacing_hint();
180187
use_namespacing = 0;
181188
} else {
182-
if(deny_setgroups(pid) < 0)
183-
return -1;
189+
if(deny_setgroups(pid) < 0) {
190+
/* If apparmor is preventing
191+
* unshare(CLONE_NEWUSER) from getting
192+
* CAP_SYS_ADMIN, we will fail here.
193+
*
194+
* See https://github.com/gittup/tup/issues/502
195+
*/
196+
/* Write a special "2" token to the socket so the parent will
197+
* know to retry without namespacing.
198+
*/
199+
close(msd[1]);
200+
if(write(msd[0], "2", 1) < 0) {
201+
perror("write");
202+
}
203+
/* Exit out of this child process, the parent process will try to re-create another. */
204+
exit(1);
205+
}
184206
if(update_map(pid, "uid_map", origuid) < 0)
185207
return -1;
186208
if(update_map(pid, "gid_map", origgid) < 0)
@@ -202,6 +224,7 @@ int server_pre_init(void)
202224
}
203225
exit(master_fork_loop());
204226
}
227+
205228
if(close(msd[0]) < 0) {
206229
perror("close(msd[0])");
207230
return -1;
@@ -213,6 +236,18 @@ int server_pre_init(void)
213236
perror("read");
214237
return -1;
215238
}
239+
if(c == '2' && use_namespacing) {
240+
int status;
241+
close(msd[1]);
242+
if(waitpid(master_fork_pid, &status, 0) < 0) {
243+
perror("waitpid");
244+
return -1;
245+
}
246+
247+
disable_namespacing_hint();
248+
use_namespacing = 0;
249+
return server_pre_init();
250+
}
216251
if(rc == 0 || c != '1') {
217252
fprintf(stderr, "tup error: master_fork server did not start up correctly.\n");
218253
return -1;

src/tup/tup/main.c

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,33 @@ int main(int argc, char **argv)
202202
tup_valgrind_cleanup();
203203
return 0;
204204
} else if(strcmp(cmd, "privileged") == 0) {
205-
#ifdef __linux__
206-
if(unshare(CLONE_NEWUSER) == 0) {
205+
if(tup_privileged())
207206
return 1;
207+
#ifdef __linux__
208+
/* If we're not privileged, we can still pretend that we are
209+
* if we can create a new user namespace.
210+
*/
211+
if(unshare(CLONE_NEWUSER) < 0) {
212+
return 0;
208213
}
214+
int fd;
215+
char filename[PATH_MAX];
216+
217+
/* Even if we can create a new user namespace, apparmor may
218+
* prevent it from getting CAP_SYS_ADMIN. The easiest way to
219+
* check (and the actual failure path in master_fork.c) is to
220+
* see if we can open the setgroups file for write.
221+
*/
222+
snprintf(filename, sizeof(filename), "/proc/%i/setgroups", getpid());
223+
filename[sizeof(filename)-1] = 0;
224+
fd = open(filename, O_WRONLY);
225+
if(fd < 0) {
226+
return 0;
227+
}
228+
return 1;
229+
#else
230+
return 0;
209231
#endif
210-
return tup_privileged();
211232
} else if(strcmp(cmd, "server") == 0) {
212233
printf("%s\n", TUP_SERVER);
213234
return 0;

0 commit comments

Comments
 (0)