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 fuse_fd = 0;
247  int drop_privileges = 0;
248  char *dev_fd_mountpoint = NULL;
249 
250  progname = argv[0];
251  basename = strrchr(argv[0], '/');
252  if (basename)
253  basename++;
254  else
255  basename = argv[0];
256 
257  if (strncmp(basename, "mount.fuse.", 11) == 0)
258  type = basename + 11;
259  if (strncmp(basename, "mount.fuseblk.", 14) == 0)
260  type = basename + 14;
261 
262  if (type && !type[0])
263  type = NULL;
264 
265  if (argc < 3) {
266  fprintf(stderr,
267  "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
268  progname, type ? "source" : "type#[source]");
269  exit(1);
270  }
271 
272  source = argv[1];
273  if (!source[0])
274  source = NULL;
275 
276  mountpoint = argv[2];
277 
278  for (i = 3; i < argc; i++) {
279  if (strcmp(argv[i], "-v") == 0) {
280  continue;
281  } else if (strcmp(argv[i], "-t") == 0) {
282  i++;
283 
284  if (i == argc) {
285  fprintf(stderr,
286  "%s: missing argument to option '-t'\n",
287  progname);
288  exit(1);
289  }
290  type = argv[i];
291  if (strncmp(type, "fuse.", 5) == 0)
292  type += 5;
293  else if (strncmp(type, "fuseblk.", 8) == 0)
294  type += 8;
295 
296  if (!type[0]) {
297  fprintf(stderr,
298  "%s: empty type given as argument to option '-t'\n",
299  progname);
300  exit(1);
301  }
302  } else if (strcmp(argv[i], "-o") == 0) {
303  char *opts;
304  char *opt;
305  i++;
306  if (i == argc)
307  break;
308 
309  opts = xstrdup(argv[i]);
310  opt = strtok(opts, ",");
311  while (opt) {
312  int j;
313  int ignore = 0;
314  const char *ignore_opts[] = { "",
315  "user",
316  "nofail",
317  "nouser",
318  "users",
319  "auto",
320  "noauto",
321  "_netdev",
322  NULL};
323  if (strncmp(opt, "setuid=", 7) == 0) {
324  setuid_name = xstrdup(opt + 7);
325  ignore = 1;
326  } else if (strcmp(opt,
327  "drop_privileges") == 0) {
328  pass_fuse_fd = 1;
329  drop_privileges = 1;
330  ignore = 1;
331  }
332  for (j = 0; ignore_opts[j]; j++)
333  if (strcmp(opt, ignore_opts[j]) == 0)
334  ignore = 1;
335 
336  if (!ignore) {
337  if (strcmp(opt, "nodev") == 0)
338  dev = 0;
339  else if (strcmp(opt, "nosuid") == 0)
340  suid = 0;
341 
342  options = add_option(opt, options);
343  }
344  opt = strtok(NULL, ",");
345  }
346  free(opts);
347  }
348  }
349 
350  if (drop_privileges) {
351  uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
352  CAP_TO_MASK(CAP_SYS_ADMIN);
353  if ((get_capabilities() & required_caps) != required_caps) {
354  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",
355  progname, progname);
356  exit(1);
357  }
358  }
359 
360  if (dev)
361  options = add_option("dev", options);
362  if (suid)
363  options = add_option("suid", options);
364 
365  if (!type) {
366  if (source) {
367  dup_source = xstrdup(source);
368  type = dup_source;
369  source = strchr(type, '#');
370  if (source)
371  *source++ = '\0';
372  if (!type[0]) {
373  fprintf(stderr, "%s: empty filesystem type\n",
374  progname);
375  exit(1);
376  }
377  } else {
378  fprintf(stderr, "%s: empty source\n", progname);
379  exit(1);
380  }
381  }
382 
383  if (setuid_name && setuid_name[0]) {
384 #ifdef linux
385  if (drop_privileges) {
386  /*
387  * Make securebits more permissive before calling
388  * setuid(). Specifically, if SECBIT_KEEP_CAPS and
389  * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
390  * have the side effect of dropping all capabilities,
391  * and we need to retain CAP_SETPCAP in order to drop
392  * all privileges before exec().
393  */
394  if (prctl(PR_SET_SECUREBITS,
395  SECBIT_KEEP_CAPS |
396  SECBIT_NO_SETUID_FIXUP) == -1) {
397  fprintf(stderr,
398  "%s: Failed to set securebits %s\n",
399  progname, strerror(errno));
400  exit(1);
401  }
402  }
403 #endif
404 
405  struct passwd *pwd = getpwnam(setuid_name);
406  if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
407  fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
408  progname, setuid_name, strerror(errno));
409  exit(1);
410  }
411  } else if (!getenv("HOME")) {
412  /* Hack to make filesystems work in the boot environment */
413  setenv("HOME", "/root", 0);
414  }
415 
416  if (pass_fuse_fd) {
417  fuse_fd = prepare_fuse_fd(mountpoint, type, options);
418  dev_fd_mountpoint = xrealloc(NULL, 20);
419  snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
420  mountpoint = dev_fd_mountpoint;
421  }
422 
423 #ifdef linux
424  if (drop_privileges) {
425  drop_and_lock_capabilities();
426  }
427 #endif
428  add_arg(&command, type);
429  if (source)
430  add_arg(&command, source);
431  add_arg(&command, mountpoint);
432  if (options) {
433  add_arg(&command, "-o");
434  add_arg(&command, options);
435  }
436 
437  free(options);
438  free(dev_fd_mountpoint);
439  free(dup_source);
440  free(setuid_name);
441 
442  execl("/bin/sh", "/bin/sh", "-c", command, NULL);
443  fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
444  strerror(errno));
445 
446  if (pass_fuse_fd)
447  close(fuse_fd);
448  free(command);
449  return 1;
450 }
int fuse_open_channel(const char *mountpoint, const char *options)
Definition: helper.c:424