Commit message: None
CVE: CVE-2016-2494
CWE: CWE-264
Vulnerability description: Off-by-one error in sdcard/sdcard.c in Android 4.x before 4.4.4, 5.0.x before 5.0.2, 5.1.x before 5.1.1, and 6.x before 2016-06-01 allows attackers to gain privileges via a crafted application, as demonstrated by obtaining Signature or SignatureOrSystem access, aka internal bug 28085658.

Project: Android

static struct node* acquire_or_create_child_locked(
 struct fuse* fuse, struct node* parent,
 const char* name, const char* actual_name)
{
 struct node* child = lookup_child_by_name_locked(parent, name);
 if (child) {
        acquire_node_locked(child);
 } else {
        child = create_node_locked(fuse, parent, name, actual_name);
 }
 return child;
}

Project: Android

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 (!strcasecmp(name, "autorun.inf")
 || !strcasecmp(name, ".android_secure")
 || !strcasecmp(name, "android_secure")) {
 return false;
 }
 }

 /* Root always has access; access for any other UIDs should always
     * be controlled through packages.list. */
 if (hdr->uid == 0) {
 return true;
 }

 /* No extra permissions to enforce */
 return true;
}

Project: Android

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);
}

Project: Android

static void fuse_status(struct fuse *fuse, __u64 unique, int err)
{
 struct fuse_out_header hdr;
    hdr.len = sizeof(hdr);
    hdr.error = err;
    hdr.unique = unique;
    write(fuse->fd, &hdr, sizeof(hdr));
}

Project: Android

static int handle_forget(struct fuse* fuse, struct fuse_handler* handler,
 const struct fuse_in_header *hdr, const struct fuse_forget_in *req)
{
 struct node* node;

    pthread_mutex_lock(&fuse->global->lock);
    node = lookup_node_by_id_locked(fuse, hdr->nodeid);
    TRACE("[%d] FORGET #%"PRIu64" @ %"PRIx64" (%s)\n", handler->token, req->nlookup,
            hdr->nodeid, node ? node->name : "?");
 if (node) {
        __u64 n = req->nlookup;
 while (n--) {
            release_node_locked(node);
 }
 }
    pthread_mutex_unlock(&fuse->global->lock);
 return NO_STATUS; /* no reply */
}

Project: Android

static int handle_fuse_request(struct fuse *fuse, struct fuse_handler* handler,
 const struct fuse_in_header *hdr, const void *data, size_t data_len)
{
 switch (hdr->opcode) {
 case FUSE_LOOKUP: { /* bytez[] -> entry_out */
 const char* name = data;
 return handle_lookup(fuse, handler, hdr, name);
 }

 case FUSE_FORGET: {
 const struct fuse_forget_in *req = data;
 return handle_forget(fuse, handler, hdr, req);
 }

 case FUSE_GETATTR: { /* getattr_in -> attr_out */
 const struct fuse_getattr_in *req = data;
 return handle_getattr(fuse, handler, hdr, req);
 }

 case FUSE_SETATTR: { /* setattr_in -> attr_out */
 const struct fuse_setattr_in *req = data;
 return handle_setattr(fuse, handler, hdr, req);
 }

 case FUSE_MKNOD: { /* mknod_in, bytez[] -> entry_out */
 const struct fuse_mknod_in *req = data;
 const char *name = ((const char*) data) + sizeof(*req);
 return handle_mknod(fuse, handler, hdr, req, name);
 }

 case FUSE_MKDIR: { /* mkdir_in, bytez[] -> entry_out */
 const struct fuse_mkdir_in *req = data;
 const char *name = ((const char*) data) + sizeof(*req);
 return handle_mkdir(fuse, handler, hdr, req, name);
 }

 case FUSE_UNLINK: { /* bytez[] -> */
 const char* name = data;
 return handle_unlink(fuse, handler, hdr, name);
 }

 case FUSE_RMDIR: { /* bytez[] -> */
 const char* name = data;
 return handle_rmdir(fuse, handler, hdr, name);
 }

 case FUSE_RENAME: { /* rename_in, oldname, newname ->  */
 const struct fuse_rename_in *req = data;
 const char *old_name = ((const char*) data) + sizeof(*req);
 const char *new_name = old_name + strlen(old_name) + 1;
 return handle_rename(fuse, handler, hdr, req, old_name, new_name);
 }

 case FUSE_OPEN: { /* open_in -> open_out */
 const struct fuse_open_in *req = data;
 return handle_open(fuse, handler, hdr, req);
 }

 case FUSE_READ: { /* read_in -> byte[] */
 const struct fuse_read_in *req = data;
 return handle_read(fuse, handler, hdr, req);
 }

 case FUSE_WRITE: { /* write_in, byte[write_in.size] -> write_out */
 const struct fuse_write_in *req = data;
 const void* buffer = (const __u8*)data + sizeof(*req);
 return handle_write(fuse, handler, hdr, req, buffer);
 }

 case FUSE_STATFS: { /* getattr_in -> attr_out */
 return handle_statfs(fuse, handler, hdr);
 }

 case FUSE_RELEASE: { /* release_in -> */
 const struct fuse_release_in *req = data;
 return handle_release(fuse, handler, hdr, req);
 }

 case FUSE_FSYNC:
 case FUSE_FSYNCDIR: {
 const struct fuse_fsync_in *req = data;
 return handle_fsync(fuse, handler, hdr, req);
 }

 case FUSE_FLUSH: {
 return handle_flush(fuse, handler, hdr);
 }

 case FUSE_OPENDIR: { /* open_in -> open_out */
 const struct fuse_open_in *req = data;
 return handle_opendir(fuse, handler, hdr, req);
 }

 case FUSE_READDIR: {
 const struct fuse_read_in *req = data;
 return handle_readdir(fuse, handler, hdr, req);
 }

 case FUSE_RELEASEDIR: { /* release_in -> */
 const struct fuse_release_in *req = data;
 return handle_releasedir(fuse, handler, hdr, req);
 }

 case FUSE_INIT: { /* init_in -> init_out */
 const struct fuse_init_in *req = data;
 return handle_init(fuse, handler, hdr, req);
 }

 default: {
        TRACE("[%d] NOTIMPL op=%d uniq=%"PRIx64" nid=%"PRIx64"\n",
                handler->token, hdr->opcode, hdr->unique, hdr->nodeid);
 return -ENOSYS;
 }
 }
}

Project: Android

static int handle_mkdir(struct fuse* fuse, struct fuse_handler* handler,
 const struct fuse_in_header* hdr, const struct fuse_mkdir_in* req, const char* name)
{
 struct node* parent_node;
 char parent_path[PATH_MAX];
 char child_path[PATH_MAX];
 const char* actual_name;

    pthread_mutex_lock(&fuse->global->lock);
    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
            parent_path, sizeof(parent_path));
    TRACE("[%d] MKDIR %s 0%o @ %"PRIx64" (%s)\n", handler->token,
            name, req->mode, hdr->nodeid, parent_node ? parent_node->name : "?");
    pthread_mutex_unlock(&fuse->global->lock);

 if (!parent_node || !(actual_name = find_file_within(parent_path, name,
            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;
 }

 /* When creating /Android/data and /Android/obb, mark them as .nomedia */
 if (parent_node->perm == PERM_ANDROID && !strcasecmp(name, "data")) {
 char nomedia[PATH_MAX];
        snprintf(nomedia, PATH_MAX, "%s/.nomedia", child_path);
 if (touch(nomedia, 0664) != 0) {
            ERROR("Failed to touch(%s): %s\n", nomedia, strerror(errno));
 return -ENOENT;
 }
 }
 if (parent_node->perm == PERM_ANDROID && !strcasecmp(name, "obb")) {
 char nomedia[PATH_MAX];
        snprintf(nomedia, PATH_MAX, "%s/.nomedia", fuse->global->obb_path);
 if (touch(nomedia, 0664) != 0) {
            ERROR("Failed to touch(%s): %s\n", nomedia, strerror(errno));
 return -ENOENT;
 }
 }

 return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
}

Project: Android

static int handle_release(struct fuse* fuse, struct fuse_handler* handler,
 const struct fuse_in_header* hdr, const struct fuse_release_in* req)
{
 struct handle *h = id_to_ptr(req->fh);

    TRACE("[%d] RELEASE %p(%d)\n", handler->token, h, h->fd);
    close(h->fd);
    free(h);
 return 0;
}

Project: Android

static int handle_rename(struct fuse* fuse, struct fuse_handler* handler,
 const struct fuse_in_header* hdr, const struct fuse_rename_in* req,
 const char* old_name, const char* new_name)
{
 struct node* old_parent_node;
 struct node* new_parent_node;
 struct node* child_node;
 char old_parent_path[PATH_MAX];
 char new_parent_path[PATH_MAX];
 char old_child_path[PATH_MAX];
 char new_child_path[PATH_MAX];
 const char* new_actual_name;
 int res;

    pthread_mutex_lock(&fuse->global->lock);
    old_parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
            old_parent_path, sizeof(old_parent_path));
    new_parent_node = lookup_node_and_path_by_id_locked(fuse, req->newdir,
            new_parent_path, sizeof(new_parent_path));
    TRACE("[%d] RENAME %s->%s @ %"PRIx64" (%s) -> %"PRIx64" (%s)\n", handler->token,
            old_name, new_name,
            hdr->nodeid, old_parent_node ? old_parent_node->name : "?",
            req->newdir, new_parent_node ? new_parent_node->name : "?");
 if (!old_parent_node || !new_parent_node) {
        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) {
        res = -ENOENT;
 goto lookup_error;
 }
    acquire_node_locked(child_node);
    pthread_mutex_unlock(&fuse->global->lock);

 /* Special case for renaming a file where destination is same path
     * differing only by case.  In this case we don't want to look for a case
     * insensitive match.  This allows commands like "mv foo FOO" to work as expected.
     */
 int search = old_parent_node != new_parent_node
 || strcasecmp(old_name, new_name);
 if (!(new_actual_name = find_file_within(new_parent_path, new_name,
            new_child_path, sizeof(new_child_path), search))) {
        res = -ENOENT;
 goto io_error;
 }

    TRACE("[%d] RENAME %s->%s\n", handler->token, old_child_path, new_child_path);
    res = rename(old_child_path, new_child_path);
 if (res < 0) {
        res = -errno;
 goto io_error;
 }

    pthread_mutex_lock(&fuse->global->lock);
    res = rename_node_locked(child_node, new_name, new_actual_name);
 if (!res) {
        remove_node_from_parent_locked(child_node);
        derive_permissions_locked(fuse, new_parent_node, child_node);
        derive_permissions_recursive_locked(fuse, child_node);
        add_node_to_parent_locked(child_node, new_parent_node);
 }
 goto done;

io_error:
    pthread_mutex_lock(&fuse->global->lock);
done:
    release_node_locked(child_node);
lookup_error:
    pthread_mutex_unlock(&fuse->global->lock);
 return res;
}

Project: Android

static int handle_statfs(struct fuse* fuse, struct fuse_handler* handler,
 const struct fuse_in_header* hdr)
{
 char path[PATH_MAX];
 struct statfs stat;
 struct fuse_statfs_out out;
 int res;

    pthread_mutex_lock(&fuse->global->lock);
    TRACE("[%d] STATFS\n", handler->token);
    res = get_node_path_locked(&fuse->global->root, path, sizeof(path));
    pthread_mutex_unlock(&fuse->global->lock);
 if (res < 0) {
 return -ENOENT;
 }
 if (statfs(fuse->global->root.name, &stat) < 0) {
 return -errno;
 }
    memset(&out, 0, sizeof(out));
    out.st.blocks = stat.f_blocks;
    out.st.bfree = stat.f_bfree;
    out.st.bavail = stat.f_bavail;
    out.st.files = stat.f_files;
    out.st.ffree = stat.f_ffree;
    out.st.bsize = stat.f_bsize;
    out.st.namelen = stat.f_namelen;
    out.st.frsize = stat.f_frsize;
    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
 return NO_STATUS;
}

Project: Android

static int handle_unlink(struct fuse* fuse, struct fuse_handler* handler,
 const struct fuse_in_header* hdr, const char* name)
{
 struct node* parent_node;
 struct node* child_node;
 char parent_path[PATH_MAX];
 char child_path[PATH_MAX];

    pthread_mutex_lock(&fuse->global->lock);
    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
            parent_path, sizeof(parent_path));
    TRACE("[%d] UNLINK %s @ %"PRIx64" (%s)\n", handler->token,
            name, hdr->nodeid, parent_node ? parent_node->name : "?");
    pthread_mutex_unlock(&fuse->global->lock);

 if (!parent_node || !find_file_within(parent_path, name,
            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;
 }
    pthread_mutex_lock(&fuse->global->lock);
    child_node = lookup_child_by_name_locked(parent_node, name);
 if (child_node) {
        child_node->deleted = true;
 }
    pthread_mutex_unlock(&fuse->global->lock);
 if (parent_node && child_node) {
 /* Tell all other views that node is gone */
        TRACE("[%d] fuse_notify_delete parent=%"PRIx64", child=%"PRIx64", name=%s\n",
                handler->token, (uint64_t) parent_node->nid, (uint64_t) child_node->nid, name);
 if (fuse != fuse->global->fuse_default) {
            fuse_notify_delete(fuse->global->fuse_default, parent_node->nid, child_node->nid, name);
 }
 if (fuse != fuse->global->fuse_read) {
            fuse_notify_delete(fuse->global->fuse_read, parent_node->nid, child_node->nid, name);
 }
 if (fuse != fuse->global->fuse_write) {
            fuse_notify_delete(fuse->global->fuse_write, parent_node->nid, child_node->nid, name);
 }
 }
 return 0;
}

Project: Android

static struct node* lookup_node_and_path_by_id_locked(struct fuse* fuse, __u64 nid,
 char* buf, size_t bufsize)
{
 struct node* node = lookup_node_by_id_locked(fuse, nid);
 if (node && get_node_path_locked(node, buf, bufsize) < 0) {
        node = NULL;
 }
 return node;
}

Project: Android

static struct node *lookup_node_by_id_locked(struct fuse *fuse, __u64 nid)
{
 if (nid == FUSE_ROOT_ID) {
 return &fuse->global->root;
 } else {
 return id_to_ptr(nid);
 }
}

Project: Android

static void* start_handler(void* data)
{
 struct fuse_handler* handler = data;
    handle_fuse_requests(handler);
 return NULL;
}