Loading sdcard/Android.mk +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading sdcard/sdcard.c→sdcard/fuse.c +3 −690 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 (;;) { Loading Loading @@ -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; } Loading
sdcard/Android.mk +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
sdcard/sdcard.c→sdcard/fuse.c +3 −690 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 (;;) { Loading Loading @@ -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; }