libfuse

mount.fuse.c
1 /*
2 FUSE: Filesystem in Userspace
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
4
5 This program can be distributed under the terms of the GNU GPLv2.
6 See the file COPYING.
7 */
8
9 #include "config.h"
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include <stdint.h>
17 #include <fcntl.h>
18 #include <pwd.h>
19 #include <sys/wait.h>
20
21 #ifdef linux
22 #include <sys/prctl.h>
23 #include <sys/syscall.h>
24 #include <linux/capability.h>
25 #include <linux/securebits.h>
26 /* for 2.6 kernels */
27 #if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
28 #define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
29 #endif
30 #if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
31 #define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
32 #endif
33 #if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
34 #define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
35 #endif
36 #if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
37 #define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
38 #endif
39 #if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
40 #define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
41 #endif
42 #if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
43 #define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
44 #endif
45 #endif
46
47 #include "fuse.h"
48
49 static char *progname;
50
51 static char *xstrdup(const char *s)
52 {
53 char *t = strdup(s);
54 if (!t) {
55 fprintf(stderr, "%s: failed to allocate memory\n", progname);
56 exit(1);
57 }
58 return t;
59 }
60
61 static void *xrealloc(void *oldptr, size_t size)
62 {
63 void *ptr = realloc(oldptr, size);
64 if (!ptr) {
65 fprintf(stderr, "%s: failed to allocate memory\n", progname);
66 exit(1);
67 }
68 return ptr;
69 }
70
71 static void add_arg(char **cmdp, const char *opt)
72 {
73 size_t optlen = strlen(opt);
74 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
75 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
76 fprintf(stderr, "%s: argument too long\n", progname);
77 exit(1);
78 }
79 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
80 char *s;
81 s = cmd + cmdlen;
82 if (*cmdp)
83 *s++ = ' ';
84
85 *s++ = '\'';
86 for (; *opt; opt++) {
87 if (*opt == '\'') {
88 *s++ = '\'';
89 *s++ = '\\';
90 *s++ = '\'';
91 *s++ = '\'';
92 } else
93 *s++ = *opt;
94 }
95 *s++ = '\'';
96 *s = '\0';
97 *cmdp = cmd;
98 }
99
100 static char *add_option(const char *opt, char *options)
101 {
102 int oldlen = options ? strlen(options) : 0;
103
104 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
105 if (!oldlen)
106 strcpy(options, opt);
107 else {
108 strcat(options, ",");
109 strcat(options, opt);
110 }
111 return options;
112 }
113
114 static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
115 const char *options)
116 {
117 int fuse_fd = -1;
118 int flags = -1;
119 int subtype_len = strlen(subtype) + 9;
120 char* options_copy = xrealloc(NULL, subtype_len);
121
122 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
123 options_copy = add_option(options, options_copy);
124 fuse_fd = fuse_open_channel(mountpoint, options_copy);
125 if (fuse_fd == -1) {
126 exit(1);
127 }
128
129 flags = fcntl(fuse_fd, F_GETFD);
130 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
131 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
132 progname, strerror(errno));
133 exit(1);
134 }
135
136 return fuse_fd;
137 }
138
139 #ifdef linux
140 static uint64_t get_capabilities(void)
141 {
142 /*
143 * This invokes the capset syscall directly to avoid the libcap
144 * dependency, which isn't really justified just for this.
145 */
146 struct __user_cap_header_struct header = {
147 .version = _LINUX_CAPABILITY_VERSION_3,
148 .pid = 0,
149 };
150 struct __user_cap_data_struct data[2];
151 memset(data, 0, sizeof(data));
152 if (syscall(SYS_capget, &header, data) == -1) {
153 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
154 progname, strerror(errno));
155 exit(1);
156 }
157
158 return data[0].effective | ((uint64_t) data[1].effective << 32);
159 }
160
161 static void set_capabilities(uint64_t caps)
162 {
163 /*
164 * This invokes the capset syscall directly to avoid the libcap
165 * dependency, which isn't really justified just for this.
166 */
167 struct __user_cap_header_struct header = {
168 .version = _LINUX_CAPABILITY_VERSION_3,
169 .pid = 0,
170 };
171 struct __user_cap_data_struct data[2];
172 memset(data, 0, sizeof(data));
173 data[0].effective = data[0].permitted = caps;
174 data[1].effective = data[1].permitted = caps >> 32;
175 if (syscall(SYS_capset, &header, data) == -1) {
176 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
177 progname, strerror(errno));
178 exit(1);
179 }
180 }
181
182 static void drop_and_lock_capabilities(void)
183 {
184 /* Set and lock securebits. */
185 if (prctl(PR_SET_SECUREBITS,
186 SECBIT_KEEP_CAPS_LOCKED |
187 SECBIT_NO_SETUID_FIXUP |
188 SECBIT_NO_SETUID_FIXUP_LOCKED |
189 SECBIT_NOROOT |
190 SECBIT_NOROOT_LOCKED) == -1) {
191 fprintf(stderr, "%s: Failed to set securebits %s\n",
192 progname, strerror(errno));
193 exit(1);
194 }
195
196 /* Clear the capability bounding set. */
197 int cap;
198 for (cap = 0; ; cap++) {
199 int cap_status = prctl(PR_CAPBSET_READ, cap);
200 if (cap_status == 0) {
201 continue;
202 }
203 if (cap_status == -1 && errno == EINVAL) {
204 break;
205 }
206
207 if (cap_status != 1) {
208 fprintf(stderr,
209 "%s: Failed to get capability %u: %s\n",
210 progname, cap, strerror(errno));
211 exit(1);
212 }
213 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
214 fprintf(stderr,
215 "%s: Failed to drop capability %u: %s\n",
216 progname, cap, strerror(errno));
217 }
218 }
219
220 /* Drop capabilities. */
221 set_capabilities(0);
222
223 /* Prevent re-acquisition of privileges. */
224 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
225 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
226 progname, strerror(errno));
227 exit(1);
228 }
229 }
230 #endif
231
232 int main(int argc, char *argv[])
233 {
234 char *type = NULL;
235 char *source;
236 char *dup_source = NULL;
237 const char *mountpoint;
238 char *basename;
239 char *options = NULL;
240 char *command = NULL;
241 char *setuid_name = NULL;
242 int i;
243 int dev = 1;
244 int suid = 1;
245 int pass_fuse_fd = 0;
246 int drop_privileges = 0;
247 char *dev_fd_mountpoint = NULL;
248
249 progname = argv[0];
250 basename = strrchr(argv[0], '/');
251 if (basename)
252 basename++;
253 else
254 basename = argv[0];
255
256 if (strncmp(basename, "mount.fuse.", 11) == 0)
257 type = basename + 11;
258 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
259 type = basename + 14;
260
261 if (type && !type[0])
262 type = NULL;
263
264 if (argc < 3) {
265 fprintf(stderr,
266 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
267 progname, type ? "source" : "type#[source]");
268 exit(1);
269 }
270
271 source = argv[1];
272 if (!source[0])
273 source = NULL;
274
275 mountpoint = argv[2];
276
277 for (i = 3; i < argc; i++) {
278 if (strcmp(argv[i], "-v") == 0) {
279 continue;
280 } else if (strcmp(argv[i], "-t") == 0) {
281 i++;
282
283 if (i == argc) {
284 fprintf(stderr,
285 "%s: missing argument to option '-t'\n",
286 progname);
287 exit(1);
288 }
289 type = argv[i];
290 if (strncmp(type, "fuse.", 5) == 0)
291 type += 5;
292 else if (strncmp(type, "fuseblk.", 8) == 0)
293 type += 8;
294
295 if (!type[0]) {
296 fprintf(stderr,
297 "%s: empty type given as argument to option '-t'\n",
298 progname);
299 exit(1);
300 }
301 } else if (strcmp(argv[i], "-o") == 0) {
302 char *opts;
303 char *opt;
304 i++;
305 if (i == argc)
306 break;
307
308 opts = xstrdup(argv[i]);
309 opt = strtok(opts, ",");
310 while (opt) {
311 int j;
312 int ignore = 0;
313 const char *ignore_opts[] = { "",
314 "user",
315 "nofail",
316 "nouser",
317 "users",
318 "auto",
319 "noauto",
320 "_netdev",
321 NULL};
322 if (strncmp(opt, "setuid=", 7) == 0) {
323 setuid_name = xstrdup(opt + 7);
324 ignore = 1;
325 } else if (strcmp(opt,
326 "drop_privileges") == 0) {
327 pass_fuse_fd = 1;
328 drop_privileges = 1;
329 ignore = 1;
330 }
331 for (j = 0; ignore_opts[j]; j++)
332 if (strcmp(opt, ignore_opts[j]) == 0)
333 ignore = 1;
334
335 if (!ignore) {
336 if (strcmp(opt, "nodev") == 0)
337 dev = 0;
338 else if (strcmp(opt, "nosuid") == 0)
339 suid = 0;
340
341 options = add_option(opt, options);
342 }
343 opt = strtok(NULL, ",");
344 }
345 free(opts);
346 }
347 }
348
349 if (drop_privileges) {
350 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
351 CAP_TO_MASK(CAP_SYS_ADMIN);
352 if ((get_capabilities() & required_caps) != required_caps) {
353 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
354 progname, progname);
355 exit(1);
356 }
357 }
358
359 if (dev)
360 options = add_option("dev", options);
361 if (suid)
362 options = add_option("suid", options);
363
364 if (!type) {
365 if (source) {
366 dup_source = xstrdup(source);
367 type = dup_source;
368 source = strchr(type, '#');
369 if (source)
370 *source++ = '\0';
371 if (!type[0]) {
372 fprintf(stderr, "%s: empty filesystem type\n",
373 progname);
374 exit(1);
375 }
376 } else {
377 fprintf(stderr, "%s: empty source\n", progname);
378 exit(1);
379 }
380 }
381
382 if (setuid_name && setuid_name[0]) {
383 #ifdef linux
384 if (drop_privileges) {
385 /*
386 * Make securebits more permissive before calling
387 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
388 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
389 * have the side effect of dropping all capabilities,
390 * and we need to retain CAP_SETPCAP in order to drop
391 * all privileges before exec().
392 */
393 if (prctl(PR_SET_SECUREBITS,
394 SECBIT_KEEP_CAPS |
395 SECBIT_NO_SETUID_FIXUP) == -1) {
396 fprintf(stderr,
397 "%s: Failed to set securebits %s\n",
398 progname, strerror(errno));
399 exit(1);
400 }
401 }
402 #endif
403
404 struct passwd *pwd = getpwnam(setuid_name);
405 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
406 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
407 progname, setuid_name, strerror(errno));
408 exit(1);
409 }
410 } else if (!getenv("HOME")) {
411 /* Hack to make filesystems work in the boot environment */
412 setenv("HOME", "/root", 0);
413 }
414
415 if (pass_fuse_fd) {
416 int fuse_fd = prepare_fuse_fd(mountpoint, type, options);
417 dev_fd_mountpoint = xrealloc(NULL, 20);
418 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
419 mountpoint = dev_fd_mountpoint;
420 }
421
422 #ifdef linux
423 if (drop_privileges) {
424 drop_and_lock_capabilities();
425 }
426 #endif
427 add_arg(&command, type);
428 if (source)
429 add_arg(&command, source);
430 add_arg(&command, mountpoint);
431 if (options) {
432 add_arg(&command, "-o");
433 add_arg(&command, options);
434 }
435
436 free(options);
437 free(dev_fd_mountpoint);
438 free(dup_source);
439 free(setuid_name);
440
441 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
442 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
443 strerror(errno));
444 return 1;
445 }
fuse_open_channel
int fuse_open_channel(const char *mountpoint, const char *options)
Definition: helper.c:424