blob: 00dcaf9e95e53320364930cfe3005de3b1f7b7bc [file] [log] [blame]
Jorge Lucangeli Obesc255f252016-07-12 15:13:05 -04001// Copyright (C) 2016 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#define LOG_TAG "sdcard"
16
17#include <dirent.h>
18#include <errno.h>
19#include <fcntl.h>
20#include <linux/fuse.h>
21#include <pthread.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sys/inotify.h>
25#include <sys/mount.h>
26#include <sys/resource.h>
27#include <sys/stat.h>
28#include <sys/types.h>
29#include <unistd.h>
30
31#include <cutils/fs.h>
32#include <cutils/hashmap.h>
33#include <cutils/log.h>
34#include <cutils/multiuser.h>
35#include <packagelistparser/packagelistparser.h>
36
37#include <private/android_filesystem_config.h>
38
39/* README
40 *
41 * What is this?
42 *
43 * sdcard is a program that uses FUSE to emulate FAT-on-sdcard style
44 * directory permissions (all files are given fixed owner, group, and
45 * permissions at creation, owner, group, and permissions are not
46 * changeable, symlinks and hardlinks are not createable, etc.
47 *
48 * See usage() for command line options.
49 *
50 * It must be run as root, but will drop to requested UID/GID as soon as it
51 * mounts a filesystem. It will refuse to run if requested UID/GID are zero.
52 *
53 * Things I believe to be true:
54 *
55 * - ops that return a fuse_entry (LOOKUP, MKNOD, MKDIR, LINK, SYMLINK,
56 * CREAT) must bump that node's refcount
57 * - don't forget that FORGET can forget multiple references (req->nlookup)
58 * - if an op that returns a fuse_entry fails writing the reply to the
59 * kernel, you must rollback the refcount to reflect the reference the
60 * kernel did not actually acquire
61 *
62 * This daemon can also derive custom filesystem permissions based on directory
63 * structure when requested. These custom permissions support several features:
64 *
65 * - Apps can access their own files in /Android/data/com.example/ without
66 * requiring any additional GIDs.
67 * - Separate permissions for protecting directories like Pictures and Music.
68 * - Multi-user separation on the same physical device.
69 */
70
71#include "fuse.h"
72
73/* Supplementary groups to execute with. */
74static const gid_t kGroups[1] = { AID_PACKAGE_INFO };
75
76static int str_hash(void *key) {
77 return hashmapHash(key, strlen(static_cast<const char*>(key)));
78}
79
80/* Tests if two string keys are equal ignoring case. */
81static bool str_icase_equals(void *keyA, void *keyB) {
82 return strcasecmp(static_cast<const char*>(keyA), static_cast<const char*>(keyB)) == 0;
83}
84
85static bool remove_str_to_int(void *key, void *value, void *context) {
86 Hashmap* map = static_cast<Hashmap*>(context);
87 hashmapRemove(map, key);
88 free(key);
89 return true;
90}
91
92static bool package_parse_callback(pkg_info *info, void *userdata) {
93 struct fuse_global *global = (struct fuse_global *)userdata;
94
95 char* name = strdup(info->name);
96 hashmapPut(global->package_to_appid, name, (void*) (uintptr_t) info->uid);
97 packagelist_free(info);
98 return true;
99}
100
101static bool read_package_list(struct fuse_global* global) {
102 pthread_mutex_lock(&global->lock);
103
104 hashmapForEach(global->package_to_appid, remove_str_to_int, global->package_to_appid);
105
106 bool rc = packagelist_parse(package_parse_callback, global);
107 TRACE("read_package_list: found %zu packages\n",
108 hashmapSize(global->package_to_appid));
109
110 /* Regenerate ownership details using newly loaded mapping */
111 derive_permissions_recursive_locked(global->fuse_default, &global->root);
112
113 pthread_mutex_unlock(&global->lock);
114
115 return rc;
116}
117
118static void watch_package_list(struct fuse_global* global) {
119 struct inotify_event *event;
120 char event_buf[512];
121
122 int nfd = inotify_init();
123 if (nfd < 0) {
124 ERROR("inotify_init failed: %s\n", strerror(errno));
125 return;
126 }
127
128 bool active = false;
129 while (1) {
130 if (!active) {
131 int res = inotify_add_watch(nfd, PACKAGES_LIST_FILE, IN_DELETE_SELF);
132 if (res == -1) {
133 if (errno == ENOENT || errno == EACCES) {
134 /* Framework may not have created yet, sleep and retry */
135 ERROR("missing \"%s\"; retrying\n", PACKAGES_LIST_FILE);
136 sleep(3);
137 continue;
138 } else {
139 ERROR("inotify_add_watch failed: %s\n", strerror(errno));
140 return;
141 }
142 }
143
144 /* Watch above will tell us about any future changes, so
145 * read the current state. */
146 if (read_package_list(global) == false) {
147 ERROR("read_package_list failed\n");
148 return;
149 }
150 active = true;
151 }
152
153 int event_pos = 0;
154 int res = read(nfd, event_buf, sizeof(event_buf));
155 if (res < (int) sizeof(*event)) {
156 if (errno == EINTR)
157 continue;
158 ERROR("failed to read inotify event: %s\n", strerror(errno));
159 return;
160 }
161
162 while (res >= (int) sizeof(*event)) {
163 int event_size;
164 event = (struct inotify_event *) (event_buf + event_pos);
165
166 TRACE("inotify event: %08x\n", event->mask);
167 if ((event->mask & IN_IGNORED) == IN_IGNORED) {
168 /* Previously watched file was deleted, probably due to move
169 * that swapped in new data; re-arm the watch and read. */
170 active = false;
171 }
172
173 event_size = sizeof(*event) + event->len;
174 res -= event_size;
175 event_pos += event_size;
176 }
177 }
178}
179
180static int fuse_setup(struct fuse* fuse, gid_t gid, mode_t mask) {
181 char opts[256];
182
183 fuse->fd = open("/dev/fuse", O_RDWR);
184 if (fuse->fd == -1) {
185 ERROR("failed to open fuse device: %s\n", strerror(errno));
186 return -1;
187 }
188
189 umount2(fuse->dest_path, MNT_DETACH);
190
191 snprintf(opts, sizeof(opts),
192 "fd=%i,rootmode=40000,default_permissions,allow_other,user_id=%d,group_id=%d",
193 fuse->fd, fuse->global->uid, fuse->global->gid);
194 if (mount("/dev/fuse", fuse->dest_path, "fuse", MS_NOSUID | MS_NODEV | MS_NOEXEC |
195 MS_NOATIME, opts) != 0) {
196 ERROR("failed to mount fuse filesystem: %s\n", strerror(errno));
197 return -1;
198 }
199
200 fuse->gid = gid;
201 fuse->mask = mask;
202
203 return 0;
204}
205
206static void* start_handler(void* data) {
207 struct fuse_handler* handler = static_cast<fuse_handler*>(data);
208 handle_fuse_requests(handler);
209 return NULL;
210}
211
212static void run(const char* source_path, const char* label, uid_t uid,
213 gid_t gid, userid_t userid, bool multi_user, bool full_write) {
214 struct fuse_global global;
215 struct fuse fuse_default;
216 struct fuse fuse_read;
217 struct fuse fuse_write;
218 struct fuse_handler handler_default;
219 struct fuse_handler handler_read;
220 struct fuse_handler handler_write;
221 pthread_t thread_default;
222 pthread_t thread_read;
223 pthread_t thread_write;
224
225 memset(&global, 0, sizeof(global));
226 memset(&fuse_default, 0, sizeof(fuse_default));
227 memset(&fuse_read, 0, sizeof(fuse_read));
228 memset(&fuse_write, 0, sizeof(fuse_write));
229 memset(&handler_default, 0, sizeof(handler_default));
230 memset(&handler_read, 0, sizeof(handler_read));
231 memset(&handler_write, 0, sizeof(handler_write));
232
233 pthread_mutex_init(&global.lock, NULL);
234 global.package_to_appid = hashmapCreate(256, str_hash, str_icase_equals);
235 global.uid = uid;
236 global.gid = gid;
237 global.multi_user = multi_user;
238 global.next_generation = 0;
239 global.inode_ctr = 1;
240
241 memset(&global.root, 0, sizeof(global.root));
242 global.root.nid = FUSE_ROOT_ID; /* 1 */
243 global.root.refcount = 2;
244 global.root.namelen = strlen(source_path);
245 global.root.name = strdup(source_path);
246 global.root.userid = userid;
247 global.root.uid = AID_ROOT;
248 global.root.under_android = false;
249
250 strcpy(global.source_path, source_path);
251
252 if (multi_user) {
253 global.root.perm = PERM_PRE_ROOT;
254 snprintf(global.obb_path, sizeof(global.obb_path), "%s/obb", source_path);
255 } else {
256 global.root.perm = PERM_ROOT;
257 snprintf(global.obb_path, sizeof(global.obb_path), "%s/Android/obb", source_path);
258 }
259
260 fuse_default.global = &global;
261 fuse_read.global = &global;
262 fuse_write.global = &global;
263
264 global.fuse_default = &fuse_default;
265 global.fuse_read = &fuse_read;
266 global.fuse_write = &fuse_write;
267
268 snprintf(fuse_default.dest_path, PATH_MAX, "/mnt/runtime/default/%s", label);
269 snprintf(fuse_read.dest_path, PATH_MAX, "/mnt/runtime/read/%s", label);
270 snprintf(fuse_write.dest_path, PATH_MAX, "/mnt/runtime/write/%s", label);
271
272 handler_default.fuse = &fuse_default;
273 handler_read.fuse = &fuse_read;
274 handler_write.fuse = &fuse_write;
275
276 handler_default.token = 0;
277 handler_read.token = 1;
278 handler_write.token = 2;
279
280 umask(0);
281
282 if (multi_user) {
283 /* Multi-user storage is fully isolated per user, so "other"
284 * permissions are completely masked off. */
285 if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006)
286 || fuse_setup(&fuse_read, AID_EVERYBODY, 0027)
287 || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0027)) {
288 ERROR("failed to fuse_setup\n");
289 exit(1);
290 }
291 } else {
292 /* Physical storage is readable by all users on device, but
293 * the Android directories are masked off to a single user
294 * deep inside attr_from_stat(). */
295 if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006)
296 || fuse_setup(&fuse_read, AID_EVERYBODY, full_write ? 0027 : 0022)
297 || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0022)) {
298 ERROR("failed to fuse_setup\n");
299 exit(1);
300 }
301 }
302
303 /* Drop privs */
304 if (setgroups(sizeof(kGroups) / sizeof(kGroups[0]), kGroups) < 0) {
305 ERROR("cannot setgroups: %s\n", strerror(errno));
306 exit(1);
307 }
308 if (setgid(gid) < 0) {
309 ERROR("cannot setgid: %s\n", strerror(errno));
310 exit(1);
311 }
312 if (setuid(uid) < 0) {
313 ERROR("cannot setuid: %s\n", strerror(errno));
314 exit(1);
315 }
316
317 if (multi_user) {
318 fs_prepare_dir(global.obb_path, 0775, uid, gid);
319 }
320
321 if (pthread_create(&thread_default, NULL, start_handler, &handler_default)
322 || pthread_create(&thread_read, NULL, start_handler, &handler_read)
323 || pthread_create(&thread_write, NULL, start_handler, &handler_write)) {
324 ERROR("failed to pthread_create\n");
325 exit(1);
326 }
327
328 watch_package_list(&global);
329 ERROR("terminated prematurely\n");
330 exit(1);
331}
332
333static int usage() {
334 ERROR("usage: sdcard [OPTIONS] <source_path> <label>\n"
335 " -u: specify UID to run as\n"
336 " -g: specify GID to run as\n"
337 " -U: specify user ID that owns device\n"
338 " -m: source_path is multi-user\n"
339 " -w: runtime write mount has full write access\n"
340 "\n");
341 return 1;
342}
343
344int main(int argc, char **argv) {
345 const char *source_path = NULL;
346 const char *label = NULL;
347 uid_t uid = 0;
348 gid_t gid = 0;
349 userid_t userid = 0;
350 bool multi_user = false;
351 bool full_write = false;
352 int i;
353 struct rlimit rlim;
354 int fs_version;
355
356 int opt;
357 while ((opt = getopt(argc, argv, "u:g:U:mw")) != -1) {
358 switch (opt) {
359 case 'u':
360 uid = strtoul(optarg, NULL, 10);
361 break;
362 case 'g':
363 gid = strtoul(optarg, NULL, 10);
364 break;
365 case 'U':
366 userid = strtoul(optarg, NULL, 10);
367 break;
368 case 'm':
369 multi_user = true;
370 break;
371 case 'w':
372 full_write = true;
373 break;
374 case '?':
375 default:
376 return usage();
377 }
378 }
379
380 for (i = optind; i < argc; i++) {
381 char* arg = argv[i];
382 if (!source_path) {
383 source_path = arg;
384 } else if (!label) {
385 label = arg;
386 } else {
387 ERROR("too many arguments\n");
388 return usage();
389 }
390 }
391
392 if (!source_path) {
393 ERROR("no source path specified\n");
394 return usage();
395 }
396 if (!label) {
397 ERROR("no label specified\n");
398 return usage();
399 }
400 if (!uid || !gid) {
401 ERROR("uid and gid must be nonzero\n");
402 return usage();
403 }
404
405 rlim.rlim_cur = 8192;
406 rlim.rlim_max = 8192;
407 if (setrlimit(RLIMIT_NOFILE, &rlim)) {
408 ERROR("Error setting RLIMIT_NOFILE, errno = %d\n", errno);
409 }
410
411 while ((fs_read_atomic_int("/data/.layout_version", &fs_version) == -1) || (fs_version < 3)) {
412 ERROR("installd fs upgrade not yet complete. Waiting...\n");
413 sleep(1);
414 }
415
416 run(source_path, label, uid, gid, userid, multi_user, full_write);
417 return 1;
418}