• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
xstrdup(const char * s)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 
xrealloc(void * oldptr,size_t size)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 
add_arg(char ** cmdp,const char * opt)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 
add_option(const char * opt,char * options)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 
prepare_fuse_fd(const char * mountpoint,const char * subtype,const char * options)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
get_capabilities(void)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 
set_capabilities(uint64_t caps)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 
drop_and_lock_capabilities(void)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 
main(int argc,char * argv[])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 }
455