libfuse: util/mount.fuse.c Source File

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 }
fuse_open_channel
int fuse_open_channel(const char *mountpoint, const char *options)
Definition: helper.c:424
fuse.h