diff options
| author | Jorge Lucangeli Obes <jorgelo@google.com> | 2016-07-13 15:23:45 -0400 |
|---|---|---|
| committer | Jorge Lucangeli Obes <jorgelo@google.com> | 2016-07-13 15:23:45 -0400 |
| commit | dba909bd9e98cc6ecc9c786eeafe8b8613d38bae (patch) | |
| tree | 106bcec696c7215707b17653af581db6460af5a1 /sdcard | |
| parent | 79233b4169ea54f63ff44951f5298b103800831b (diff) | |
| parent | 1b9b27351ab5f9d30d9e080a729fbf5cda92d03b (diff) | |
| download | system_core-dba909bd9e98cc6ecc9c786eeafe8b8613d38bae.tar.gz system_core-dba909bd9e98cc6ecc9c786eeafe8b8613d38bae.tar.bz2 system_core-dba909bd9e98cc6ecc9c786eeafe8b8613d38bae.zip | |
resolve merge conflicts of 1b9b273 to stage-aosp-master
Change-Id: I12a541cb698d1df866b8be4dc1e35cb99e6f1e64
Diffstat (limited to 'sdcard')
| -rw-r--r-- | sdcard/Android.mk | 2 | ||||
| -rw-r--r-- | sdcard/fuse.c (renamed from sdcard/sdcard.c) | 693 | ||||
| -rw-r--r-- | sdcard/fuse.h | 203 | ||||
| -rw-r--r-- | sdcard/sdcard.cpp | 418 |
4 files changed, 625 insertions, 691 deletions
diff --git a/sdcard/Android.mk b/sdcard/Android.mk index ac5faa795..b12f3ee9f 100644 --- a/sdcard/Android.mk +++ b/sdcard/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := sdcard.c +LOCAL_SRC_FILES := sdcard.cpp fuse.c LOCAL_MODULE := sdcard LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror LOCAL_SHARED_LIBRARIES := liblog libcutils libpackagelistparser diff --git a/sdcard/sdcard.c b/sdcard/fuse.c index 27e91c11f..3523f948d 100644 --- a/sdcard/sdcard.c +++ b/sdcard/fuse.c @@ -16,245 +16,20 @@ #define LOG_TAG "sdcard" -#include <ctype.h> -#include <dirent.h> -#include <errno.h> -#include <fcntl.h> -#include <inttypes.h> -#include <limits.h> -#include <linux/fuse.h> -#include <pthread.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/inotify.h> -#include <sys/mount.h> -#include <sys/param.h> -#include <sys/resource.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/time.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <unistd.h> - -#include <cutils/fs.h> -#include <cutils/hashmap.h> -#include <cutils/log.h> -#include <cutils/multiuser.h> -#include <cutils/properties.h> -#include <packagelistparser/packagelistparser.h> - -#include <private/android_filesystem_config.h> +#include "fuse.h" /* FUSE_CANONICAL_PATH is not currently upstreamed */ #define FUSE_CANONICAL_PATH 2016 -/* README - * - * What is this? - * - * sdcard is a program that uses FUSE to emulate FAT-on-sdcard style - * directory permissions (all files are given fixed owner, group, and - * permissions at creation, owner, group, and permissions are not - * changeable, symlinks and hardlinks are not createable, etc. - * - * See usage() for command line options. - * - * It must be run as root, but will drop to requested UID/GID as soon as it - * mounts a filesystem. It will refuse to run if requested UID/GID are zero. - * - * Things I believe to be true: - * - * - ops that return a fuse_entry (LOOKUP, MKNOD, MKDIR, LINK, SYMLINK, - * CREAT) must bump that node's refcount - * - don't forget that FORGET can forget multiple references (req->nlookup) - * - if an op that returns a fuse_entry fails writing the reply to the - * kernel, you must rollback the refcount to reflect the reference the - * kernel did not actually acquire - * - * This daemon can also derive custom filesystem permissions based on directory - * structure when requested. These custom permissions support several features: - * - * - Apps can access their own files in /Android/data/com.example/ without - * requiring any additional GIDs. - * - Separate permissions for protecting directories like Pictures and Music. - * - Multi-user separation on the same physical device. - */ - -#define FUSE_TRACE 0 - -#if FUSE_TRACE -#define TRACE(x...) ALOGD(x) -#else -#define TRACE(x...) do {} while (0) -#endif - -#define ERROR(x...) ALOGE(x) - #define PROP_SDCARDFS_DEVICE "ro.sys.sdcardfs" #define PROP_SDCARDFS_USER "persist.sys.sdcardfs" #define FUSE_UNKNOWN_INO 0xffffffff -/* Maximum number of bytes to write in one request. */ -#define MAX_WRITE (256 * 1024) - -/* Maximum number of bytes to read in one request. */ -#define MAX_READ (128 * 1024) - -/* Largest possible request. - * The request size is bounded by the maximum size of a FUSE_WRITE request because it has - * the largest possible data payload. */ -#define MAX_REQUEST_SIZE (sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE) - /* Pseudo-error constant used to indicate that no fuse status is needed * or that a reply has already been written. */ #define NO_STATUS 1 -/* Supplementary groups to execute with */ -static const gid_t kGroups[1] = { AID_PACKAGE_INFO }; - -/* Permission mode for a specific node. Controls how file permissions - * are derived for children nodes. */ -typedef enum { - /* Nothing special; this node should just inherit from its parent. */ - PERM_INHERIT, - /* This node is one level above a normal root; used for legacy layouts - * which use the first level to represent user_id. */ - PERM_PRE_ROOT, - /* This node is "/" */ - PERM_ROOT, - /* This node is "/Android" */ - PERM_ANDROID, - /* This node is "/Android/data" */ - PERM_ANDROID_DATA, - /* This node is "/Android/obb" */ - PERM_ANDROID_OBB, - /* This node is "/Android/media" */ - PERM_ANDROID_MEDIA, -} perm_t; - -struct handle { - int fd; -}; - -struct dirhandle { - DIR *d; -}; - -struct node { - __u32 refcount; - __u64 nid; - __u64 gen; - /* - * The inode number for this FUSE node. Note that this isn't stable across - * multiple invocations of the FUSE daemon. - */ - __u32 ino; - - /* State derived based on current position in hierarchy. */ - perm_t perm; - userid_t userid; - uid_t uid; - bool under_android; - - struct node *next; /* per-dir sibling list */ - struct node *child; /* first contained file by this dir */ - struct node *parent; /* containing directory */ - - size_t namelen; - char *name; - /* If non-null, this is the real name of the file in the underlying storage. - * This may differ from the field "name" only by case. - * strlen(actual_name) will always equal strlen(name), so it is safe to use - * namelen for both fields. - */ - char *actual_name; - - /* If non-null, an exact underlying path that should be grafted into this - * position. Used to support things like OBB. */ - char* graft_path; - size_t graft_pathlen; - - bool deleted; -}; - -static int str_hash(void *key) { - return hashmapHash(key, strlen(key)); -} - -/** Test if two string keys are equal ignoring case */ -static bool str_icase_equals(void *keyA, void *keyB) { - return strcasecmp(keyA, keyB) == 0; -} - -/* Global data for all FUSE mounts */ -struct fuse_global { - pthread_mutex_t lock; - - uid_t uid; - gid_t gid; - bool multi_user; - - char source_path[PATH_MAX]; - char obb_path[PATH_MAX]; - - Hashmap* package_to_appid; - - __u64 next_generation; - struct node root; - - /* Used to allocate unique inode numbers for fuse nodes. We use - * a simple counter based scheme where inode numbers from deleted - * nodes aren't reused. Note that inode allocations are not stable - * across multiple invocation of the sdcard daemon, but that shouldn't - * be a huge problem in practice. - * - * Note that we restrict inodes to 32 bit unsigned integers to prevent - * truncation on 32 bit processes when unsigned long long stat.st_ino is - * assigned to an unsigned long ino_t type in an LP32 process. - * - * Also note that fuse_attr and fuse_dirent inode values are 64 bits wide - * on both LP32 and LP64, but the fuse kernel code doesn't squash 64 bit - * inode numbers into 32 bit values on 64 bit kernels (see fuse_squash_ino - * in fs/fuse/inode.c). - * - * Accesses must be guarded by |lock|. - */ - __u32 inode_ctr; - - struct fuse* fuse_default; - struct fuse* fuse_read; - struct fuse* fuse_write; -}; - -/* Single FUSE mount */ -struct fuse { - struct fuse_global* global; - - char dest_path[PATH_MAX]; - - int fd; - - gid_t gid; - mode_t mask; -}; - -/* Private data used by a single FUSE handler */ -struct fuse_handler { - struct fuse* fuse; - int token; - - /* To save memory, we never use the contents of the request buffer and the read - * buffer at the same time. This allows us to share the underlying storage. */ - union { - __u8 request_buffer[MAX_REQUEST_SIZE]; - __u8 read_buffer[MAX_READ + PAGE_SIZE]; - }; -}; - static inline void *id_to_ptr(__u64 nid) { return (void *) (uintptr_t) nid; @@ -514,7 +289,7 @@ static void derive_permissions_locked(struct fuse* fuse, struct node *parent, } } -static void derive_permissions_recursive_locked(struct fuse* fuse, struct node *parent) { +void derive_permissions_recursive_locked(struct fuse* fuse, struct node *parent) { struct node *node; for (node = parent->child; node; node = node->next) { derive_permissions_locked(fuse, parent, node); @@ -1631,7 +1406,7 @@ static int handle_fuse_request(struct fuse *fuse, struct fuse_handler* handler, } } -static void handle_fuse_requests(struct fuse_handler* handler) +void handle_fuse_requests(struct fuse_handler* handler) { struct fuse* fuse = handler->fuse; for (;;) { @@ -1674,465 +1449,3 @@ static void handle_fuse_requests(struct fuse_handler* handler) } } } - -static void* start_handler(void* data) -{ - struct fuse_handler* handler = data; - handle_fuse_requests(handler); - return NULL; -} - -static bool remove_str_to_int(void *key, void *value, void *context) { - Hashmap* map = context; - hashmapRemove(map, key); - free(key); - return true; -} - -static bool package_parse_callback(pkg_info *info, void *userdata) { - struct fuse_global *global = (struct fuse_global *)userdata; - - char* name = strdup(info->name); - hashmapPut(global->package_to_appid, name, (void*) (uintptr_t) info->uid); - packagelist_free(info); - return true; -} - -static bool read_package_list(struct fuse_global* global) { - pthread_mutex_lock(&global->lock); - - hashmapForEach(global->package_to_appid, remove_str_to_int, global->package_to_appid); - - bool rc = packagelist_parse(package_parse_callback, global); - TRACE("read_package_list: found %zu packages\n", - hashmapSize(global->package_to_appid)); - - /* Regenerate ownership details using newly loaded mapping */ - derive_permissions_recursive_locked(global->fuse_default, &global->root); - - pthread_mutex_unlock(&global->lock); - - return rc; -} - -static void watch_package_list(struct fuse_global* global) { - struct inotify_event *event; - char event_buf[512]; - - int nfd = inotify_init(); - if (nfd < 0) { - ERROR("inotify_init failed: %s\n", strerror(errno)); - return; - } - - bool active = false; - while (1) { - if (!active) { - int res = inotify_add_watch(nfd, PACKAGES_LIST_FILE, IN_DELETE_SELF); - if (res == -1) { - if (errno == ENOENT || errno == EACCES) { - /* Framework may not have created yet, sleep and retry */ - ERROR("missing \"%s\"; retrying\n", PACKAGES_LIST_FILE); - sleep(3); - continue; - } else { - ERROR("inotify_add_watch failed: %s\n", strerror(errno)); - return; - } - } - - /* Watch above will tell us about any future changes, so - * read the current state. */ - if (read_package_list(global) == false) { - ERROR("read_package_list failed\n"); - return; - } - active = true; - } - - int event_pos = 0; - int res = read(nfd, event_buf, sizeof(event_buf)); - if (res < (int) sizeof(*event)) { - if (errno == EINTR) - continue; - ERROR("failed to read inotify event: %s\n", strerror(errno)); - return; - } - - while (res >= (int) sizeof(*event)) { - int event_size; - event = (struct inotify_event *) (event_buf + event_pos); - - TRACE("inotify event: %08x\n", event->mask); - if ((event->mask & IN_IGNORED) == IN_IGNORED) { - /* Previously watched file was deleted, probably due to move - * that swapped in new data; re-arm the watch and read. */ - active = false; - } - - event_size = sizeof(*event) + event->len; - res -= event_size; - event_pos += event_size; - } - } -} - -static int usage() { - ERROR("usage: sdcard [OPTIONS] <source_path> <label>\n" - " -u: specify UID to run as\n" - " -g: specify GID to run as\n" - " -U: specify user ID that owns device\n" - " -m: source_path is multi-user\n" - " -w: runtime write mount has full write access\n" - "\n"); - return 1; -} - -static int fuse_setup(struct fuse* fuse, gid_t gid, mode_t mask) { - char opts[256]; - - fuse->fd = open("/dev/fuse", O_RDWR); - if (fuse->fd == -1) { - ERROR("failed to open fuse device: %s\n", strerror(errno)); - return -1; - } - - umount2(fuse->dest_path, MNT_DETACH); - - snprintf(opts, sizeof(opts), - "fd=%i,rootmode=40000,default_permissions,allow_other,user_id=%d,group_id=%d", - fuse->fd, fuse->global->uid, fuse->global->gid); - if (mount("/dev/fuse", fuse->dest_path, "fuse", MS_NOSUID | MS_NODEV | MS_NOEXEC | - MS_NOATIME, opts) != 0) { - ERROR("failed to mount fuse filesystem: %s\n", strerror(errno)); - return -1; - } - - fuse->gid = gid; - fuse->mask = mask; - - return 0; -} - -static void run(const char* source_path, const char* label, uid_t uid, - gid_t gid, userid_t userid, bool multi_user, bool full_write) { - struct fuse_global global; - struct fuse fuse_default; - struct fuse fuse_read; - struct fuse fuse_write; - struct fuse_handler handler_default; - struct fuse_handler handler_read; - struct fuse_handler handler_write; - pthread_t thread_default; - pthread_t thread_read; - pthread_t thread_write; - - memset(&global, 0, sizeof(global)); - memset(&fuse_default, 0, sizeof(fuse_default)); - memset(&fuse_read, 0, sizeof(fuse_read)); - memset(&fuse_write, 0, sizeof(fuse_write)); - memset(&handler_default, 0, sizeof(handler_default)); - memset(&handler_read, 0, sizeof(handler_read)); - memset(&handler_write, 0, sizeof(handler_write)); - - pthread_mutex_init(&global.lock, NULL); - global.package_to_appid = hashmapCreate(256, str_hash, str_icase_equals); - global.uid = uid; - global.gid = gid; - global.multi_user = multi_user; - global.next_generation = 0; - global.inode_ctr = 1; - - memset(&global.root, 0, sizeof(global.root)); - global.root.nid = FUSE_ROOT_ID; /* 1 */ - global.root.refcount = 2; - global.root.namelen = strlen(source_path); - global.root.name = strdup(source_path); - global.root.userid = userid; - global.root.uid = AID_ROOT; - global.root.under_android = false; - - strcpy(global.source_path, source_path); - - if (multi_user) { - global.root.perm = PERM_PRE_ROOT; - snprintf(global.obb_path, sizeof(global.obb_path), "%s/obb", source_path); - } else { - global.root.perm = PERM_ROOT; - snprintf(global.obb_path, sizeof(global.obb_path), "%s/Android/obb", source_path); - } - - fuse_default.global = &global; - fuse_read.global = &global; - fuse_write.global = &global; - - global.fuse_default = &fuse_default; - global.fuse_read = &fuse_read; - global.fuse_write = &fuse_write; - - snprintf(fuse_default.dest_path, PATH_MAX, "/mnt/runtime/default/%s", label); - snprintf(fuse_read.dest_path, PATH_MAX, "/mnt/runtime/read/%s", label); - snprintf(fuse_write.dest_path, PATH_MAX, "/mnt/runtime/write/%s", label); - - handler_default.fuse = &fuse_default; - handler_read.fuse = &fuse_read; - handler_write.fuse = &fuse_write; - - handler_default.token = 0; - handler_read.token = 1; - handler_write.token = 2; - - umask(0); - - if (multi_user) { - /* Multi-user storage is fully isolated per user, so "other" - * permissions are completely masked off. */ - if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006) - || fuse_setup(&fuse_read, AID_EVERYBODY, 0027) - || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0027)) { - ERROR("failed to fuse_setup\n"); - exit(1); - } - } else { - /* Physical storage is readable by all users on device, but - * the Android directories are masked off to a single user - * deep inside attr_from_stat(). */ - if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006) - || fuse_setup(&fuse_read, AID_EVERYBODY, full_write ? 0027 : 0022) - || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0022)) { - ERROR("failed to fuse_setup\n"); - exit(1); - } - } - - /* Drop privs */ - if (setgroups(sizeof(kGroups) / sizeof(kGroups[0]), kGroups) < 0) { - ERROR("cannot setgroups: %s\n", strerror(errno)); - exit(1); - } - if (setgid(gid) < 0) { - ERROR("cannot setgid: %s\n", strerror(errno)); - exit(1); - } - if (setuid(uid) < 0) { - ERROR("cannot setuid: %s\n", strerror(errno)); - exit(1); - } - - if (multi_user) { - fs_prepare_dir(global.obb_path, 0775, uid, gid); - } - - if (pthread_create(&thread_default, NULL, start_handler, &handler_default) - || pthread_create(&thread_read, NULL, start_handler, &handler_read) - || pthread_create(&thread_write, NULL, start_handler, &handler_write)) { - ERROR("failed to pthread_create\n"); - exit(1); - } - - watch_package_list(&global); - ERROR("terminated prematurely\n"); - exit(1); -} - -static int sdcardfs_setup(const char *source_path, const char *dest_path, uid_t fsuid, - gid_t fsgid, bool multi_user, userid_t userid, gid_t gid, mode_t mask) { - char opts[256]; - - snprintf(opts, sizeof(opts), - "fsuid=%d,fsgid=%d,%smask=%d,userid=%d,gid=%d", - fsuid, fsgid, multi_user?"multiuser,":"", mask, userid, gid); - - if (mount(source_path, dest_path, "sdcardfs", - MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts) != 0) { - ERROR("failed to mount sdcardfs filesystem: %s\n", strerror(errno)); - return -1; - } - - return 0; -} - -static void run_sdcardfs(const char* source_path, const char* label, uid_t uid, - gid_t gid, userid_t userid, bool multi_user, bool full_write) { - char dest_path_default[PATH_MAX]; - char dest_path_read[PATH_MAX]; - char dest_path_write[PATH_MAX]; - char obb_path[PATH_MAX]; - snprintf(dest_path_default, PATH_MAX, "/mnt/runtime/default/%s", label); - snprintf(dest_path_read, PATH_MAX, "/mnt/runtime/read/%s", label); - snprintf(dest_path_write, PATH_MAX, "/mnt/runtime/write/%s", label); - - umask(0); - if (multi_user) { - /* Multi-user storage is fully isolated per user, so "other" - * permissions are completely masked off. */ - if (sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid, - AID_SDCARD_RW, 0006) - || sdcardfs_setup(source_path, dest_path_read, uid, gid, multi_user, userid, - AID_EVERYBODY, 0027) - || sdcardfs_setup(source_path, dest_path_write, uid, gid, multi_user, userid, - AID_EVERYBODY, full_write ? 0007 : 0027)) { - ERROR("failed to fuse_setup\n"); - exit(1); - } - } else { - /* Physical storage is readable by all users on device, but - * the Android directories are masked off to a single user - * deep inside attr_from_stat(). */ - if (sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid, - AID_SDCARD_RW, 0006) - || sdcardfs_setup(source_path, dest_path_read, uid, gid, multi_user, userid, - AID_EVERYBODY, full_write ? 0027 : 0022) - || sdcardfs_setup(source_path, dest_path_write, uid, gid, multi_user, userid, - AID_EVERYBODY, full_write ? 0007 : 0022)) { - ERROR("failed to fuse_setup\n"); - exit(1); - } - } - - /* Drop privs */ - if (setgroups(sizeof(kGroups) / sizeof(kGroups[0]), kGroups) < 0) { - ERROR("cannot setgroups: %s\n", strerror(errno)); - exit(1); - } - if (setgid(gid) < 0) { - ERROR("cannot setgid: %s\n", strerror(errno)); - exit(1); - } - if (setuid(uid) < 0) { - ERROR("cannot setuid: %s\n", strerror(errno)); - exit(1); - } - - if (multi_user) { - snprintf(obb_path, sizeof(obb_path), "%s/obb", source_path); - fs_prepare_dir(&obb_path[0], 0775, uid, gid); - } - - exit(0); -} - -static bool supports_sdcardfs(void) { - FILE *fp; - char *buf = NULL; - size_t buflen = 0; - - fp = fopen("/proc/filesystems", "r"); - if (!fp) { - ERROR("Could not read /proc/filesystems, error: %s\n", strerror(errno)); - return false; - } - while ((getline(&buf, &buflen, fp)) > 0) { - if (strstr(buf, "sdcardfs\n")) { - free(buf); - fclose(fp); - return true; - } - } - free(buf); - fclose(fp); - return false; -} - -static bool should_use_sdcardfs(void) { - char property[PROPERTY_VALUE_MAX]; - - // Allow user to have a strong opinion about state - property_get(PROP_SDCARDFS_USER, property, ""); - if (!strcmp(property, "force_on")) { - ALOGW("User explicitly enabled sdcardfs"); - return supports_sdcardfs(); - } else if (!strcmp(property, "force_off")) { - ALOGW("User explicitly disabled sdcardfs"); - return false; - } - - // Fall back to device opinion about state - if (property_get_bool(PROP_SDCARDFS_DEVICE, false)) { - ALOGW("Device explicitly enabled sdcardfs"); - return supports_sdcardfs(); - } else { - ALOGW("Device explicitly disabled sdcardfs"); - return false; - } -} - -int main(int argc, char **argv) { - const char *source_path = NULL; - const char *label = NULL; - uid_t uid = 0; - gid_t gid = 0; - userid_t userid = 0; - bool multi_user = false; - bool full_write = false; - int i; - struct rlimit rlim; - int fs_version; - - int opt; - while ((opt = getopt(argc, argv, "u:g:U:mw")) != -1) { - switch (opt) { - case 'u': - uid = strtoul(optarg, NULL, 10); - break; - case 'g': - gid = strtoul(optarg, NULL, 10); - break; - case 'U': - userid = strtoul(optarg, NULL, 10); - break; - case 'm': - multi_user = true; - break; - case 'w': - full_write = true; - break; - case '?': - default: - return usage(); - } - } - - for (i = optind; i < argc; i++) { - char* arg = argv[i]; - if (!source_path) { - source_path = arg; - } else if (!label) { - label = arg; - } else { - ERROR("too many arguments\n"); - return usage(); - } - } - - if (!source_path) { - ERROR("no source path specified\n"); - return usage(); - } - if (!label) { - ERROR("no label specified\n"); - return usage(); - } - if (!uid || !gid) { - ERROR("uid and gid must be nonzero\n"); - return usage(); - } - - rlim.rlim_cur = 8192; - rlim.rlim_max = 8192; - if (setrlimit(RLIMIT_NOFILE, &rlim)) { - ERROR("Error setting RLIMIT_NOFILE, errno = %d\n", errno); - } - - while ((fs_read_atomic_int("/data/.layout_version", &fs_version) == -1) || (fs_version < 3)) { - ERROR("installd fs upgrade not yet complete. Waiting...\n"); - sleep(1); - } - - if (should_use_sdcardfs()) { - run_sdcardfs(source_path, label, uid, gid, userid, multi_user, full_write); - } else { - run(source_path, label, uid, gid, userid, multi_user, full_write); - } - return 1; -} diff --git a/sdcard/fuse.h b/sdcard/fuse.h new file mode 100644 index 000000000..e1347f9ca --- /dev/null +++ b/sdcard/fuse.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FUSE_H_ +#define FUSE_H_ + +#include <dirent.h> +#include <fcntl.h> +#include <linux/fuse.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> + +#include <cutils/fs.h> +#include <cutils/hashmap.h> +#include <cutils/log.h> +#include <cutils/multiuser.h> +#include <packagelistparser/packagelistparser.h> + +#include <private/android_filesystem_config.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define FUSE_TRACE 0 + +#if FUSE_TRACE +#define TRACE(x...) ALOGD(x) +#else +#define TRACE(x...) do {} while (0) +#endif + +#define ERROR(x...) ALOGE(x) + +/* Maximum number of bytes to write in one request. */ +#define MAX_WRITE (256 * 1024) + +/* Maximum number of bytes to read in one request. */ +#define MAX_READ (128 * 1024) + +/* Largest possible request. + * The request size is bounded by the maximum size of a FUSE_WRITE request because it has + * the largest possible data payload. */ +#define MAX_REQUEST_SIZE (sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE) + +/* Permission mode for a specific node. Controls how file permissions + * are derived for children nodes. */ +typedef enum { + /* Nothing special; this node should just inherit from its parent. */ + PERM_INHERIT, + /* This node is one level above a normal root; used for legacy layouts + * which use the first level to represent user_id. */ + PERM_PRE_ROOT, + /* This node is "/" */ + PERM_ROOT, + /* This node is "/Android" */ + PERM_ANDROID, + /* This node is "/Android/data" */ + PERM_ANDROID_DATA, + /* This node is "/Android/obb" */ + PERM_ANDROID_OBB, + /* This node is "/Android/media" */ + PERM_ANDROID_MEDIA, +} perm_t; + +struct handle { + int fd; +}; + +struct dirhandle { + DIR *d; +}; + +struct node { + __u32 refcount; + __u64 nid; + __u64 gen; + /* + * The inode number for this FUSE node. Note that this isn't stable across + * multiple invocations of the FUSE daemon. + */ + __u32 ino; + + /* State derived based on current position in hierarchy. */ + perm_t perm; + userid_t userid; + uid_t uid; + bool under_android; + + struct node *next; /* per-dir sibling list */ + struct node *child; /* first contained file by this dir */ + struct node *parent; /* containing directory */ + + size_t namelen; + char *name; + /* If non-null, this is the real name of the file in the underlying storage. + * This may differ from the field "name" only by case. + * strlen(actual_name) will always equal strlen(name), so it is safe to use + * namelen for both fields. + */ + char *actual_name; + + /* If non-null, an exact underlying path that should be grafted into this + * position. Used to support things like OBB. */ + char* graft_path; + size_t graft_pathlen; + + bool deleted; +}; + +/* Global data for all FUSE mounts */ +struct fuse_global { + pthread_mutex_t lock; + + uid_t uid; + gid_t gid; + bool multi_user; + + char source_path[PATH_MAX]; + char obb_path[PATH_MAX]; + + Hashmap* package_to_appid; + + __u64 next_generation; + struct node root; + + /* Used to allocate unique inode numbers for fuse nodes. We use + * a simple counter based scheme where inode numbers from deleted + * nodes aren't reused. Note that inode allocations are not stable + * across multiple invocation of the sdcard daemon, but that shouldn't + * be a huge problem in practice. + * + * Note that we restrict inodes to 32 bit unsigned integers to prevent + * truncation on 32 bit processes when unsigned long long stat.st_ino is + * assigned to an unsigned long ino_t type in an LP32 process. + * + * Also note that fuse_attr and fuse_dirent inode values are 64 bits wide + * on both LP32 and LP64, but the fuse kernel code doesn't squash 64 bit + * inode numbers into 32 bit values on 64 bit kernels (see fuse_squash_ino + * in fs/fuse/inode.c). + * + * Accesses must be guarded by |lock|. + */ + __u32 inode_ctr; + + struct fuse* fuse_default; + struct fuse* fuse_read; + struct fuse* fuse_write; +}; + +/* Single FUSE mount */ +struct fuse { + struct fuse_global* global; + + char dest_path[PATH_MAX]; + + int fd; + + gid_t gid; + mode_t mask; +}; + +/* Private data used by a single FUSE handler */ +struct fuse_handler { + struct fuse* fuse; + int token; + + /* To save memory, we never use the contents of the request buffer and the read + * buffer at the same time. This allows us to share the underlying storage. */ + union { + __u8 request_buffer[MAX_REQUEST_SIZE]; + __u8 read_buffer[MAX_READ + PAGE_SIZE]; + }; +}; + +void handle_fuse_requests(struct fuse_handler* handler); +void derive_permissions_recursive_locked(struct fuse* fuse, struct node *parent); + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#endif /* FUSE_H_ */ diff --git a/sdcard/sdcard.cpp b/sdcard/sdcard.cpp new file mode 100644 index 000000000..00dcaf9e9 --- /dev/null +++ b/sdcard/sdcard.cpp @@ -0,0 +1,418 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define LOG_TAG "sdcard" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/fuse.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <sys/inotify.h> +#include <sys/mount.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <cutils/fs.h> +#include <cutils/hashmap.h> +#include <cutils/log.h> +#include <cutils/multiuser.h> +#include <packagelistparser/packagelistparser.h> + +#include <private/android_filesystem_config.h> + +/* README + * + * What is this? + * + * sdcard is a program that uses FUSE to emulate FAT-on-sdcard style + * directory permissions (all files are given fixed owner, group, and + * permissions at creation, owner, group, and permissions are not + * changeable, symlinks and hardlinks are not createable, etc. + * + * See usage() for command line options. + * + * It must be run as root, but will drop to requested UID/GID as soon as it + * mounts a filesystem. It will refuse to run if requested UID/GID are zero. + * + * Things I believe to be true: + * + * - ops that return a fuse_entry (LOOKUP, MKNOD, MKDIR, LINK, SYMLINK, + * CREAT) must bump that node's refcount + * - don't forget that FORGET can forget multiple references (req->nlookup) + * - if an op that returns a fuse_entry fails writing the reply to the + * kernel, you must rollback the refcount to reflect the reference the + * kernel did not actually acquire + * + * This daemon can also derive custom filesystem permissions based on directory + * structure when requested. These custom permissions support several features: + * + * - Apps can access their own files in /Android/data/com.example/ without + * requiring any additional GIDs. + * - Separate permissions for protecting directories like Pictures and Music. + * - Multi-user separation on the same physical device. + */ + +#include "fuse.h" + +/* Supplementary groups to execute with. */ +static const gid_t kGroups[1] = { AID_PACKAGE_INFO }; + +static int str_hash(void *key) { + return hashmapHash(key, strlen(static_cast<const char*>(key))); +} + +/* Tests if two string keys are equal ignoring case. */ +static bool str_icase_equals(void *keyA, void *keyB) { + return strcasecmp(static_cast<const char*>(keyA), static_cast<const char*>(keyB)) == 0; +} + +static bool remove_str_to_int(void *key, void *value, void *context) { + Hashmap* map = static_cast<Hashmap*>(context); + hashmapRemove(map, key); + free(key); + return true; +} + +static bool package_parse_callback(pkg_info *info, void *userdata) { + struct fuse_global *global = (struct fuse_global *)userdata; + + char* name = strdup(info->name); + hashmapPut(global->package_to_appid, name, (void*) (uintptr_t) info->uid); + packagelist_free(info); + return true; +} + +static bool read_package_list(struct fuse_global* global) { + pthread_mutex_lock(&global->lock); + + hashmapForEach(global->package_to_appid, remove_str_to_int, global->package_to_appid); + + bool rc = packagelist_parse(package_parse_callback, global); + TRACE("read_package_list: found %zu packages\n", + hashmapSize(global->package_to_appid)); + + /* Regenerate ownership details using newly loaded mapping */ + derive_permissions_recursive_locked(global->fuse_default, &global->root); + + pthread_mutex_unlock(&global->lock); + + return rc; +} + +static void watch_package_list(struct fuse_global* global) { + struct inotify_event *event; + char event_buf[512]; + + int nfd = inotify_init(); + if (nfd < 0) { + ERROR("inotify_init failed: %s\n", strerror(errno)); + return; + } + + bool active = false; + while (1) { + if (!active) { + int res = inotify_add_watch(nfd, PACKAGES_LIST_FILE, IN_DELETE_SELF); + if (res == -1) { + if (errno == ENOENT || errno == EACCES) { + /* Framework may not have created yet, sleep and retry */ + ERROR("missing \"%s\"; retrying\n", PACKAGES_LIST_FILE); + sleep(3); + continue; + } else { + ERROR("inotify_add_watch failed: %s\n", strerror(errno)); + return; + } + } + + /* Watch above will tell us about any future changes, so + * read the current state. */ + if (read_package_list(global) == false) { + ERROR("read_package_list failed\n"); + return; + } + active = true; + } + + int event_pos = 0; + int res = read(nfd, event_buf, sizeof(event_buf)); + if (res < (int) sizeof(*event)) { + if (errno == EINTR) + continue; + ERROR("failed to read inotify event: %s\n", strerror(errno)); + return; + } + + while (res >= (int) sizeof(*event)) { + int event_size; + event = (struct inotify_event *) (event_buf + event_pos); + + TRACE("inotify event: %08x\n", event->mask); + if ((event->mask & IN_IGNORED) == IN_IGNORED) { + /* Previously watched file was deleted, probably due to move + * that swapped in new data; re-arm the watch and read. */ + active = false; + } + + event_size = sizeof(*event) + event->len; + res -= event_size; + event_pos += event_size; + } + } +} + +static int fuse_setup(struct fuse* fuse, gid_t gid, mode_t mask) { + char opts[256]; + + fuse->fd = open("/dev/fuse", O_RDWR); + if (fuse->fd == -1) { + ERROR("failed to open fuse device: %s\n", strerror(errno)); + return -1; + } + + umount2(fuse->dest_path, MNT_DETACH); + + snprintf(opts, sizeof(opts), + "fd=%i,rootmode=40000,default_permissions,allow_other,user_id=%d,group_id=%d", + fuse->fd, fuse->global->uid, fuse->global->gid); + if (mount("/dev/fuse", fuse->dest_path, "fuse", MS_NOSUID | MS_NODEV | MS_NOEXEC | + MS_NOATIME, opts) != 0) { + ERROR("failed to mount fuse filesystem: %s\n", strerror(errno)); + return -1; + } + + fuse->gid = gid; + fuse->mask = mask; + + return 0; +} + +static void* start_handler(void* data) { + struct fuse_handler* handler = static_cast<fuse_handler*>(data); + handle_fuse_requests(handler); + return NULL; +} + +static void run(const char* source_path, const char* label, uid_t uid, + gid_t gid, userid_t userid, bool multi_user, bool full_write) { + struct fuse_global global; + struct fuse fuse_default; + struct fuse fuse_read; + struct fuse fuse_write; + struct fuse_handler handler_default; + struct fuse_handler handler_read; + struct fuse_handler handler_write; + pthread_t thread_default; + pthread_t thread_read; + pthread_t thread_write; + + memset(&global, 0, sizeof(global)); + memset(&fuse_default, 0, sizeof(fuse_default)); + memset(&fuse_read, 0, sizeof(fuse_read)); + memset(&fuse_write, 0, sizeof(fuse_write)); + memset(&handler_default, 0, sizeof(handler_default)); + memset(&handler_read, 0, sizeof(handler_read)); + memset(&handler_write, 0, sizeof(handler_write)); + + pthread_mutex_init(&global.lock, NULL); + global.package_to_appid = hashmapCreate(256, str_hash, str_icase_equals); + global.uid = uid; + global.gid = gid; + global.multi_user = multi_user; + global.next_generation = 0; + global.inode_ctr = 1; + + memset(&global.root, 0, sizeof(global.root)); + global.root.nid = FUSE_ROOT_ID; /* 1 */ + global.root.refcount = 2; + global.root.namelen = strlen(source_path); + global.root.name = strdup(source_path); + global.root.userid = userid; + global.root.uid = AID_ROOT; + global.root.under_android = false; + + strcpy(global.source_path, source_path); + + if (multi_user) { + global.root.perm = PERM_PRE_ROOT; + snprintf(global.obb_path, sizeof(global.obb_path), "%s/obb", source_path); + } else { + global.root.perm = PERM_ROOT; + snprintf(global.obb_path, sizeof(global.obb_path), "%s/Android/obb", source_path); + } + + fuse_default.global = &global; + fuse_read.global = &global; + fuse_write.global = &global; + + global.fuse_default = &fuse_default; + global.fuse_read = &fuse_read; + global.fuse_write = &fuse_write; + + snprintf(fuse_default.dest_path, PATH_MAX, "/mnt/runtime/default/%s", label); + snprintf(fuse_read.dest_path, PATH_MAX, "/mnt/runtime/read/%s", label); + snprintf(fuse_write.dest_path, PATH_MAX, "/mnt/runtime/write/%s", label); + + handler_default.fuse = &fuse_default; + handler_read.fuse = &fuse_read; + handler_write.fuse = &fuse_write; + + handler_default.token = 0; + handler_read.token = 1; + handler_write.token = 2; + + umask(0); + + if (multi_user) { + /* Multi-user storage is fully isolated per user, so "other" + * permissions are completely masked off. */ + if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006) + || fuse_setup(&fuse_read, AID_EVERYBODY, 0027) + || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0027)) { + ERROR("failed to fuse_setup\n"); + exit(1); + } + } else { + /* Physical storage is readable by all users on device, but + * the Android directories are masked off to a single user + * deep inside attr_from_stat(). */ + if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006) + || fuse_setup(&fuse_read, AID_EVERYBODY, full_write ? 0027 : 0022) + || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0022)) { + ERROR("failed to fuse_setup\n"); + exit(1); + } + } + + /* Drop privs */ + if (setgroups(sizeof(kGroups) / sizeof(kGroups[0]), kGroups) < 0) { + ERROR("cannot setgroups: %s\n", strerror(errno)); + exit(1); + } + if (setgid(gid) < 0) { + ERROR("cannot setgid: %s\n", strerror(errno)); + exit(1); + } + if (setuid(uid) < 0) { + ERROR("cannot setuid: %s\n", strerror(errno)); + exit(1); + } + + if (multi_user) { + fs_prepare_dir(global.obb_path, 0775, uid, gid); + } + + if (pthread_create(&thread_default, NULL, start_handler, &handler_default) + || pthread_create(&thread_read, NULL, start_handler, &handler_read) + || pthread_create(&thread_write, NULL, start_handler, &handler_write)) { + ERROR("failed to pthread_create\n"); + exit(1); + } + + watch_package_list(&global); + ERROR("terminated prematurely\n"); + exit(1); +} + +static int usage() { + ERROR("usage: sdcard [OPTIONS] <source_path> <label>\n" + " -u: specify UID to run as\n" + " -g: specify GID to run as\n" + " -U: specify user ID that owns device\n" + " -m: source_path is multi-user\n" + " -w: runtime write mount has full write access\n" + "\n"); + return 1; +} + +int main(int argc, char **argv) { + const char *source_path = NULL; + const char *label = NULL; + uid_t uid = 0; + gid_t gid = 0; + userid_t userid = 0; + bool multi_user = false; + bool full_write = false; + int i; + struct rlimit rlim; + int fs_version; + + int opt; + while ((opt = getopt(argc, argv, "u:g:U:mw")) != -1) { + switch (opt) { + case 'u': + uid = strtoul(optarg, NULL, 10); + break; + case 'g': + gid = strtoul(optarg, NULL, 10); + break; + case 'U': + userid = strtoul(optarg, NULL, 10); + break; + case 'm': + multi_user = true; + break; + case 'w': + full_write = true; + break; + case '?': + default: + return usage(); + } + } + + for (i = optind; i < argc; i++) { + char* arg = argv[i]; + if (!source_path) { + source_path = arg; + } else if (!label) { + label = arg; + } else { + ERROR("too many arguments\n"); + return usage(); + } + } + + if (!source_path) { + ERROR("no source path specified\n"); + return usage(); + } + if (!label) { + ERROR("no label specified\n"); + return usage(); + } + if (!uid || !gid) { + ERROR("uid and gid must be nonzero\n"); + return usage(); + } + + rlim.rlim_cur = 8192; + rlim.rlim_max = 8192; + if (setrlimit(RLIMIT_NOFILE, &rlim)) { + ERROR("Error setting RLIMIT_NOFILE, errno = %d\n", errno); + } + + while ((fs_read_atomic_int("/data/.layout_version", &fs_version) == -1) || (fs_version < 3)) { + ERROR("installd fs upgrade not yet complete. Waiting...\n"); + sleep(1); + } + + run(source_path, label, uid, gid, userid, multi_user, full_write); + return 1; +} |
