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 
53 static char *progname;
54 
55 static 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 
65 static 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 
75 static 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 
104 static 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 
118 static 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
144 static 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 
165 static 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 
186 static 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 
236 int 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