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