Merge "Add legacy layout support to FUSE, enforce write." into klp-dev

This commit is contained in:
Jeff Sharkey 2013-08-14 20:08:34 +00:00 committed by Android (Google) Code Review
commit 7e64c1436b
2 changed files with 265 additions and 69 deletions

View file

@ -111,7 +111,7 @@ map_file(const char* filename, size_t* filesize)
goto EXIT;
/* Ensure that the file is owned by the system user */
if ((st.st_uid != AID_SYSTEM) || (st.st_gid != AID_SYSTEM)) {
if ((st.st_uid != AID_SYSTEM) || (st.st_gid != AID_PACKAGE_INFO)) {
goto EXIT;
}

View file

@ -126,14 +126,30 @@ 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_LEGACY_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/user" */
PERM_ANDROID_USER,
} perm_t;
/* Permissions structure to derive */
typedef enum {
DERIVE_NONE,
DERIVE_LEGACY,
DERIVE_UNIFIED,
} derive_t;
struct handle {
int fd;
};
@ -142,10 +158,6 @@ struct dirhandle {
DIR *d;
};
struct package {
appid_t appid;
};
struct node {
__u32 refcount;
__u64 nid;
@ -170,6 +182,11 @@ struct node {
* 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;
};
static int str_hash(void *key) {
@ -180,17 +197,27 @@ static bool str_equals(void *keyA, void *keyB) {
return strcmp(keyA, keyB) == 0;
}
static int int_hash(void *key) {
return (int) key;
}
static bool int_equals(void *keyA, void *keyB) {
return keyA == keyB;
}
/* Global data structure shared by all fuse handlers. */
struct fuse {
pthread_mutex_t lock;
__u64 next_generation;
int fd;
bool derive_perms;
derive_t derive;
bool split_perms;
struct node root;
char rootpath[PATH_MAX];
char obbpath[PATH_MAX];
Hashmap *package_to_appid;
Hashmap* package_to_appid;
Hashmap* appid_with_rw;
};
/* Private data used by a single fuse handler. */
@ -275,15 +302,26 @@ static void remove_node_from_parent_locked(struct node* node)
* Populates 'buf' with the path and returns the length of the path on success,
* or returns -1 if the path is too long for the provided buffer.
*/
static ssize_t get_node_path_locked(struct node* node, char* buf, size_t bufsize)
{
size_t namelen = node->namelen;
static ssize_t get_node_path_locked(struct node* node, char* buf, size_t bufsize) {
const char* name;
size_t namelen;
if (node->graft_path) {
name = node->graft_path;
namelen = node->graft_pathlen;
} else if (node->actual_name) {
name = node->actual_name;
namelen = node->namelen;
} else {
name = node->name;
namelen = node->namelen;
}
if (bufsize < namelen + 1) {
return -1;
}
ssize_t pathlen = 0;
if (node->parent) {
if (node->parent && node->graft_path == NULL) {
pathlen = get_node_path_locked(node->parent, buf, bufsize - namelen - 2);
if (pathlen < 0) {
return -1;
@ -291,7 +329,6 @@ static ssize_t get_node_path_locked(struct node* node, char* buf, size_t bufsize
buf[pathlen++] = '/';
}
const char* name = node->actual_name ? node->actual_name : node->name;
memcpy(buf + pathlen, name, namelen + 1); /* include trailing \0 */
return pathlen + namelen;
}
@ -366,7 +403,7 @@ static void attr_from_stat(struct fuse_attr *attr, const struct stat *s, const s
static void derive_permissions_locked(struct fuse* fuse, struct node *parent,
struct node *node) {
struct package* package;
appid_t appid;
/* By default, each node inherits from its parent */
node->perm = PERM_INHERIT;
@ -375,7 +412,7 @@ static void derive_permissions_locked(struct fuse* fuse, struct node *parent,
node->gid = parent->gid;
node->mode = parent->mode;
if (!fuse->derive_perms) {
if (fuse->derive == DERIVE_NONE) {
return;
}
@ -384,23 +421,30 @@ static void derive_permissions_locked(struct fuse* fuse, struct node *parent,
case PERM_INHERIT:
/* Already inherited above */
break;
case PERM_LEGACY_PRE_ROOT:
/* Legacy internal layout places users at top level */
node->perm = PERM_ROOT;
node->userid = strtoul(node->name, NULL, 10);
break;
case PERM_ROOT:
/* Assume masked off by default. */
node->mode = 0770;
if (!strcmp(node->name, "Android")) {
/* App-specific directories inside; let anyone traverse */
node->perm = PERM_ANDROID;
node->mode = 0771;
} else if (!strcmp(node->name, "DCIM")
|| !strcmp(node->name, "Pictures")) {
node->gid = AID_SDCARD_PICS;
node->mode = 0770;
} else if (!strcmp(node->name, "Alarms")
|| !strcmp(node->name, "Movies")
|| !strcmp(node->name, "Music")
|| !strcmp(node->name, "Notifications")
|| !strcmp(node->name, "Podcasts")
|| !strcmp(node->name, "Ringtones")) {
node->gid = AID_SDCARD_AV;
node->mode = 0770;
} else if (fuse->split_perms) {
if (!strcmp(node->name, "DCIM")
|| !strcmp(node->name, "Pictures")) {
node->gid = AID_SDCARD_PICS;
} else if (!strcmp(node->name, "Alarms")
|| !strcmp(node->name, "Movies")
|| !strcmp(node->name, "Music")
|| !strcmp(node->name, "Notifications")
|| !strcmp(node->name, "Podcasts")
|| !strcmp(node->name, "Ringtones")) {
node->gid = AID_SDCARD_AV;
}
}
break;
case PERM_ANDROID:
@ -412,6 +456,9 @@ static void derive_permissions_locked(struct fuse* fuse, struct node *parent,
/* App-specific directories inside; let anyone traverse */
node->perm = PERM_ANDROID_OBB;
node->mode = 0771;
/* Single OBB directory is always shared */
node->graft_path = fuse->obbpath;
node->graft_pathlen = strlen(fuse->obbpath);
} else if (!strcmp(node->name, "user")) {
/* User directories must only be accessible to system, protected
* by sdcard_all. Zygote will bind mount the appropriate user-
@ -423,22 +470,72 @@ static void derive_permissions_locked(struct fuse* fuse, struct node *parent,
break;
case PERM_ANDROID_DATA:
case PERM_ANDROID_OBB:
package = hashmapGet(fuse->package_to_appid, node->name);
if (package != NULL) {
node->uid = multiuser_get_uid(parent->userid, package->appid);
appid = (appid_t) hashmapGet(fuse->package_to_appid, node->name);
if (appid != 0) {
node->uid = multiuser_get_uid(parent->userid, appid);
}
node->mode = 0770;
break;
case PERM_ANDROID_USER:
/* Root of a secondary user */
node->perm = PERM_ROOT;
node->userid = atoi(node->name);
node->gid = AID_SDCARD_RW;
node->userid = strtoul(node->name, NULL, 10);
node->gid = AID_SDCARD_R;
node->mode = 0771;
break;
}
}
/* Kernel has already enforced everything we returned through
* derive_permissions_locked(), so this is used to lock down access
* even further, such as enforcing that apps hold sdcard_rw. */
static bool check_caller_access_to_name(struct fuse* fuse,
const struct fuse_in_header *hdr, const struct node* parent_node,
const char* name, int mode) {
/* Always block security-sensitive files at root */
if (parent_node && parent_node->perm == PERM_ROOT) {
if (!strcmp(name, "autorun.inf")
|| !strcmp(name, ".android_secure")
|| !strcmp(name, "android_secure")) {
return false;
}
}
/* No additional permissions enforcement */
if (fuse->derive == DERIVE_NONE) {
return true;
}
/* Root or shell always have access */
if (hdr->uid == 0 || hdr->uid == AID_SHELL) {
return true;
}
/* If asking to write, verify that caller either owns the
* parent or holds sdcard_rw. */
if (mode & W_OK) {
if (parent_node && hdr->uid == parent_node->uid) {
return true;
}
appid_t appid = multiuser_get_app_id(hdr->uid);
pthread_mutex_lock(&fuse->lock);
bool hasRw = hashmapContainsKey(fuse->appid_with_rw, (void*) appid);
pthread_mutex_unlock(&fuse->lock);
return hasRw;
}
/* No extra permissions to enforce */
return true;
}
static bool check_caller_access_to_node(struct fuse* fuse,
const struct fuse_in_header *hdr, const struct node* node, int mode) {
return check_caller_access_to_name(fuse, hdr, node->parent, node->name, mode);
}
struct node *create_node_locked(struct fuse* fuse,
struct node *parent, const char *name, const char* actual_name)
{
@ -561,32 +658,53 @@ static struct node* acquire_or_create_child_locked(
return child;
}
static void fuse_init(struct fuse *fuse, int fd, const char *source_path, bool derive_perms)
{
static void fuse_init(struct fuse *fuse, int fd, const char *source_path,
gid_t fs_gid, derive_t derive, bool split_perms) {
pthread_mutex_init(&fuse->lock, NULL);
fuse->fd = fd;
fuse->next_generation = 0;
fuse->derive_perms = derive_perms;
fuse->derive = derive;
fuse->split_perms = split_perms;
memset(&fuse->root, 0, sizeof(fuse->root));
fuse->root.nid = FUSE_ROOT_ID; /* 1 */
fuse->root.refcount = 2;
fuse->root.namelen = strlen(source_path);
fuse->root.name = strdup(source_path);
fuse->root.perm = PERM_ROOT;
fuse->root.userid = 0;
fuse->root.uid = AID_ROOT;
fuse->root.gid = AID_SDCARD_RW;
if (derive_perms) {
fuse->root.mode = 0771;
} else {
fuse->root.mode = 0775;
}
if (derive_perms) {
/* Set up root node for various modes of operation */
switch (derive) {
case DERIVE_NONE:
/* Traditional behavior that treats entire device as being accessible
* to sdcard_rw, and no permissions are derived. */
fuse->root.perm = PERM_ROOT;
fuse->root.mode = 0775;
fuse->root.gid = AID_SDCARD_RW;
break;
case DERIVE_LEGACY:
/* Legacy behavior used to support internal multiuser layout which
* places user_id at the top directory level, with the actual roots
* just below that. Shared OBB path is also at top level. */
fuse->root.perm = PERM_LEGACY_PRE_ROOT;
fuse->root.mode = 0771;
fuse->root.gid = fs_gid;
fuse->package_to_appid = hashmapCreate(256, str_hash, str_equals);
fuse->appid_with_rw = hashmapCreate(128, int_hash, int_equals);
snprintf(fuse->obbpath, sizeof(fuse->obbpath), "%s/obb", source_path);
break;
case DERIVE_UNIFIED:
/* Unified multiuser layout which places secondary user_id under
* /Android/user and shared OBB path under /Android/obb. */
fuse->root.perm = PERM_ROOT;
fuse->root.mode = 0771;
fuse->root.gid = fs_gid;
fuse->package_to_appid = hashmapCreate(256, str_hash, str_equals);
fuse->appid_with_rw = hashmapCreate(128, int_hash, int_equals);
snprintf(fuse->obbpath, sizeof(fuse->obbpath), "%s/Android/obb", source_path);
break;
}
}
@ -637,7 +755,7 @@ static int fuse_reply_entry(struct fuse* fuse, __u64 unique,
TRACE("automatically creating %s\n", path);
pthread_mutex_lock(&fuse->lock);
bool validPackage = hashmapContainsKey(fuse->package_to_appid, name);
bool validPackage = hashmapContainsKey(fuse->package_to_appid, (char*) name);
pthread_mutex_unlock(&fuse->lock);
if (!validPackage) {
@ -713,6 +831,10 @@ static int handle_lookup(struct fuse* fuse, struct fuse_handler* handler,
child_path, sizeof(child_path), 1))) {
return -ENOENT;
}
if (!check_caller_access_to_name(fuse, hdr, parent_node, name, R_OK)) {
return -EACCES;
}
return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
}
@ -750,6 +872,10 @@ static int handle_getattr(struct fuse* fuse, struct fuse_handler* handler,
if (!node) {
return -ENOENT;
}
if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) {
return -EACCES;
}
return fuse_reply_attr(fuse, hdr->unique, node, path);
}
@ -769,6 +895,9 @@ static int handle_setattr(struct fuse* fuse, struct fuse_handler* handler,
if (!node) {
return -ENOENT;
}
if (!check_caller_access_to_node(fuse, hdr, node, W_OK)) {
return -EACCES;
}
/* XXX: incomplete implementation on purpose.
* chmod/chown should NEVER be implemented.*/
@ -830,6 +959,9 @@ static int handle_mknod(struct fuse* fuse, struct fuse_handler* handler,
child_path, sizeof(child_path), 1))) {
return -ENOENT;
}
if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
return -EACCES;
}
__u32 mode = (req->mode & (~0777)) | 0664;
if (mknod(child_path, mode, req->rdev) < 0) {
return -errno;
@ -856,6 +988,9 @@ static int handle_mkdir(struct fuse* fuse, struct fuse_handler* handler,
child_path, sizeof(child_path), 1))) {
return -ENOENT;
}
if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
return -EACCES;
}
__u32 mode = (req->mode & (~0777)) | 0775;
if (mkdir(child_path, mode) < 0) {
return -errno;
@ -881,6 +1016,9 @@ static int handle_unlink(struct fuse* fuse, struct fuse_handler* handler,
child_path, sizeof(child_path), 1)) {
return -ENOENT;
}
if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
return -EACCES;
}
if (unlink(child_path) < 0) {
return -errno;
}
@ -905,6 +1043,9 @@ static int handle_rmdir(struct fuse* fuse, struct fuse_handler* handler,
child_path, sizeof(child_path), 1)) {
return -ENOENT;
}
if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
return -EACCES;
}
if (rmdir(child_path) < 0) {
return -errno;
}
@ -938,6 +1079,14 @@ static int handle_rename(struct fuse* fuse, struct fuse_handler* handler,
res = -ENOENT;
goto lookup_error;
}
if (!check_caller_access_to_name(fuse, hdr, old_parent_node, old_name, W_OK)) {
res = -EACCES;
goto lookup_error;
}
if (!check_caller_access_to_name(fuse, hdr, new_parent_node, new_name, W_OK)) {
res = -EACCES;
goto lookup_error;
}
child_node = lookup_child_by_name_locked(old_parent_node, old_name);
if (!child_node || get_node_path_locked(child_node,
old_child_path, sizeof(old_child_path)) < 0) {
@ -983,6 +1132,17 @@ lookup_error:
return res;
}
static int open_flags_to_access_mode(int open_flags) {
if ((open_flags & O_ACCMODE) == O_RDONLY) {
return R_OK;
} else if ((open_flags & O_ACCMODE) == O_WRONLY) {
return W_OK;
} else {
/* Probably O_RDRW, but treat as default to be safe */
return R_OK | W_OK;
}
}
static int handle_open(struct fuse* fuse, struct fuse_handler* handler,
const struct fuse_in_header* hdr, const struct fuse_open_in* req)
{
@ -1000,6 +1160,9 @@ static int handle_open(struct fuse* fuse, struct fuse_handler* handler,
if (!node) {
return -ENOENT;
}
if (!check_caller_access_to_node(fuse, hdr, node, open_flags_to_access_mode(req->flags))) {
return -EACCES;
}
h = malloc(sizeof(*h));
if (!h) {
return -ENOMEM;
@ -1144,6 +1307,9 @@ static int handle_opendir(struct fuse* fuse, struct fuse_handler* handler,
if (!node) {
return -ENOENT;
}
if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) {
return -EACCES;
}
h = malloc(sizeof(*h));
if (!h) {
return -ENOMEM;
@ -1389,18 +1555,24 @@ static void* start_handler(void* data)
return NULL;
}
static bool hashmap_remove(void *key, void *value, void *context) {
static bool remove_str_to_int(void *key, void *value, void *context) {
Hashmap* map = context;
hashmapRemove(map, key);
free(key);
free(value);
return true;
}
static bool remove_int_to_null(void *key, void *value, void *context) {
Hashmap* map = context;
hashmapRemove(map, key);
return true;
}
static int read_package_list(struct fuse *fuse) {
pthread_mutex_lock(&fuse->lock);
hashmapForEach(fuse->package_to_appid, hashmap_remove, fuse->package_to_appid);
hashmapForEach(fuse->package_to_appid, remove_str_to_int, fuse->package_to_appid);
hashmapForEach(fuse->appid_with_rw, remove_int_to_null, fuse->appid_with_rw);
FILE* file = fopen(kPackagesListFile, "r");
if (!file) {
@ -1413,20 +1585,26 @@ static int read_package_list(struct fuse *fuse) {
while (fgets(buf, sizeof(buf), file) != NULL) {
char package_name[512];
int appid;
if (sscanf(buf, "%s %d", package_name, &appid) == 2) {
char* package_name_dup = strdup(package_name);
struct package* package = malloc(sizeof(struct package));
if (!package_name_dup || !package) {
ERROR("cannot allocate package details\n");
return -ENOMEM;
}
char gids[512];
package->appid = appid;
hashmapPut(fuse->package_to_appid, package_name_dup, package);
if (sscanf(buf, "%s %d %*d %*s %*s %s", package_name, &appid, gids) == 3) {
char* package_name_dup = strdup(package_name);
hashmapPut(fuse->package_to_appid, package_name_dup, (void*) appid);
char* token = strtok(gids, ",");
while (token != NULL) {
if (strtoul(token, NULL, 10) == AID_SDCARD_RW) {
hashmapPut(fuse->appid_with_rw, (void*) appid, (void*) 1);
break;
}
token = strtok(NULL, ",");
}
}
}
TRACE("read_package_list: found %d packages\n", hashmapSize(fuse->package_to_appid));
TRACE("read_package_list: found %d packages, %d with sdcard_rw\n",
hashmapSize(fuse->package_to_appid),
hashmapSize(fuse->appid_with_rw));
fclose(file);
pthread_mutex_unlock(&fuse->lock);
return 0;
@ -1512,7 +1690,7 @@ static int ignite_fuse(struct fuse* fuse, int num_threads)
/* When deriving permissions, this thread is used to process inotify events,
* otherwise it becomes one of the FUSE handlers. */
i = fuse->derive_perms ? 0 : 1;
i = (fuse->derive == DERIVE_NONE) ? 1 : 0;
for (; i < num_threads; i++) {
pthread_t thread;
int res = pthread_create(&thread, NULL, start_handler, &handlers[i]);
@ -1522,10 +1700,10 @@ static int ignite_fuse(struct fuse* fuse, int num_threads)
}
}
if (fuse->derive_perms) {
watch_package_list(fuse);
} else {
if (fuse->derive == DERIVE_NONE) {
handle_fuse_requests(&handlers[0]);
} else {
watch_package_list(fuse);
}
ERROR("terminated prematurely\n");
@ -1541,14 +1719,17 @@ static int usage()
ERROR("usage: sdcard [OPTIONS] <source_path> <dest_path>\n"
" -u: specify UID to run as\n"
" -g: specify GID to run as\n"
" -G: specify default GID for files (default sdcard_r, requires -d or -l)\n"
" -t: specify number of threads to use (default %d)\n"
" -d: derive file permissions based on path\n"
" -l: derive file permissions based on legacy internal layout\n"
" -s: split derived permissions for pics, av\n"
"\n", DEFAULT_NUM_THREADS);
return 1;
}
static int run(const char* source_path, const char* dest_path, uid_t uid, gid_t gid,
int num_threads, bool derive_perms) {
static int run(const char* source_path, const char* dest_path, uid_t uid,
gid_t gid, gid_t fs_gid, int num_threads, derive_t derive, bool split_perms) {
int fd;
char opts[256];
int res;
@ -1591,7 +1772,7 @@ static int run(const char* source_path, const char* dest_path, uid_t uid, gid_t
goto error;
}
fuse_init(&fuse, fd, source_path, derive_perms);
fuse_init(&fuse, fd, source_path, fs_gid, derive, split_perms);
umask(0);
res = ignite_fuse(&fuse, num_threads);
@ -1611,13 +1792,15 @@ int main(int argc, char **argv)
const char *dest_path = NULL;
uid_t uid = 0;
gid_t gid = 0;
gid_t fs_gid = AID_SDCARD_R;
int num_threads = DEFAULT_NUM_THREADS;
bool derive_perms = false;
derive_t derive = DERIVE_NONE;
bool split_perms = false;
int i;
struct rlimit rlim;
int opt;
while ((opt = getopt(argc, argv, "u:g:t:d")) != -1) {
while ((opt = getopt(argc, argv, "u:g:G:t:dls")) != -1) {
switch (opt) {
case 'u':
uid = strtoul(optarg, NULL, 10);
@ -1625,11 +1808,20 @@ int main(int argc, char **argv)
case 'g':
gid = strtoul(optarg, NULL, 10);
break;
case 'G':
fs_gid = strtoul(optarg, NULL, 10);
break;
case 't':
num_threads = strtoul(optarg, NULL, 10);
break;
case 'd':
derive_perms = true;
derive = DERIVE_UNIFIED;
break;
case 'l':
derive = DERIVE_LEGACY;
break;
case 's':
split_perms = true;
break;
case '?':
default:
@ -1669,6 +1861,10 @@ int main(int argc, char **argv)
ERROR("number of threads must be at least 1\n");
return usage();
}
if (split_perms && derive == DERIVE_NONE) {
ERROR("cannot split permissions without deriving\n");
return usage();
}
rlim.rlim_cur = 8192;
rlim.rlim_max = 8192;
@ -1676,6 +1872,6 @@ int main(int argc, char **argv)
ERROR("Error setting RLIMIT_NOFILE, errno = %d\n", errno);
}
res = run(source_path, dest_path, uid, gid, num_threads, derive_perms);
res = run(source_path, dest_path, uid, gid, fs_gid, num_threads, derive, split_perms);
return res < 0 ? 1 : 0;
}