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