blob: 1f0fdc0dbdf4779c2009f1075ea7390912f41bfb [file] [log] [blame]
/*
* Copyright (c) 2013-2014 Motorola Mobility LLC
* Copyright (C) 2017 Google, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/proc_fs.h>
#include <linux/hashtable.h>
#include <linux/syscalls.h>
#include <linux/fcntl.h>
#include <linux/ctype.h>
#include <linux/vmalloc.h>
#include <linux/security.h>
#include <linux/uaccess.h>
#include "esdfs.h"
static struct qstr names_secure[] = {
QSTR_LITERAL("autorun.inf"),
QSTR_LITERAL(".android_secure"),
QSTR_LITERAL("android_secure"),
QSTR_LITERAL("")
};
/* special path name searches */
static inline bool match_name(struct qstr *name, struct qstr names[])
{
int i = 0;
BUG_ON(!name);
for (i = 0; *names[i].name; i++)
if (qstr_case_eq(name, &names[i]))
return true;
return false;
}
unsigned esdfs_package_list_version;
static void fixup_perms_by_flag(int flags, const struct qstr *key,
uint32_t userid)
{
esdfs_package_list_version++;
}
static struct pkg_list esdfs_pkg_list = {
.update = fixup_perms_by_flag,
};
int esdfs_init_package_list(void)
{
pkglist_register_update_listener(&esdfs_pkg_list);
return 0;
}
void esdfs_destroy_package_list(void)
{
pkglist_unregister_update_listener(&esdfs_pkg_list);
}
/*
* Derive an entry's premissions tree position based on its parent.
*/
void esdfs_derive_perms(struct dentry *dentry)
{
struct esdfs_inode_info *inode_i = ESDFS_I(dentry->d_inode);
bool is_root;
int ret;
kuid_t appid;
struct qstr q_Download = QSTR_LITERAL("Download");
struct qstr q_Android = QSTR_LITERAL("Android");
struct qstr q_data = QSTR_LITERAL("data");
struct qstr q_obb = QSTR_LITERAL("obb");
struct qstr q_media = QSTR_LITERAL("media");
struct qstr q_cache = QSTR_LITERAL("cache");
struct qstr q_user = QSTR_LITERAL("user");
struct esdfs_inode_info *parent_i = ESDFS_I(dentry->d_parent->d_inode);
spin_lock(&dentry->d_lock);
is_root = IS_ROOT(dentry);
spin_unlock(&dentry->d_lock);
if (is_root)
return;
/* Inherit from the parent to start */
inode_i->tree = parent_i->tree;
inode_i->userid = parent_i->userid;
inode_i->appid = parent_i->appid;
inode_i->under_obb = parent_i->under_obb;
/*
* ESDFS_TREE_MEDIA* are intentionally dead ends.
*/
switch (inode_i->tree) {
case ESDFS_TREE_ROOT_LEGACY:
inode_i->tree = ESDFS_TREE_ROOT;
ret = kstrtou32(dentry->d_name.name, 0, &inode_i->userid);
if (qstr_case_eq(&dentry->d_name, &q_obb))
inode_i->tree = ESDFS_TREE_ANDROID_OBB;
break;
case ESDFS_TREE_ROOT:
inode_i->tree = ESDFS_TREE_MEDIA;
if (qstr_case_eq(&dentry->d_name, &q_Download))
inode_i->tree = ESDFS_TREE_DOWNLOAD;
else if (qstr_case_eq(&dentry->d_name, &q_Android))
inode_i->tree = ESDFS_TREE_ANDROID;
break;
case ESDFS_TREE_ANDROID:
if (qstr_case_eq(&dentry->d_name, &q_data)) {
inode_i->tree = ESDFS_TREE_ANDROID_DATA;
} else if (qstr_case_eq(&dentry->d_name, &q_obb)) {
inode_i->tree = ESDFS_TREE_ANDROID_OBB;
inode_i->under_obb = true;
} else if (qstr_case_eq(&dentry->d_name, &q_media)) {
inode_i->tree = ESDFS_TREE_ANDROID_MEDIA;
} else if (ESDFS_RESTRICT_PERMS(ESDFS_SB(dentry->d_sb)) &&
qstr_case_eq(&dentry->d_name, &q_user)) {
inode_i->tree = ESDFS_TREE_ANDROID_USER;
}
break;
case ESDFS_TREE_ANDROID_DATA:
case ESDFS_TREE_ANDROID_OBB:
case ESDFS_TREE_ANDROID_MEDIA:
appid = pkglist_get_allowed_appid(dentry->d_name.name,
inode_i->userid);
if (uid_valid(appid))
inode_i->appid = esdfs_from_kuid(
ESDFS_SB(dentry->d_sb), appid);
else
inode_i->appid = 0;
inode_i->tree = ESDFS_TREE_ANDROID_APP;
break;
case ESDFS_TREE_ANDROID_APP:
if (qstr_case_eq(&dentry->d_name, &q_cache))
inode_i->tree = ESDFS_TREE_ANDROID_APP_CACHE;
break;
case ESDFS_TREE_ANDROID_USER:
/* Another user, so start over */
inode_i->tree = ESDFS_TREE_ROOT;
ret = kstrtou32(dentry->d_name.name, 0, &inode_i->userid);
break;
}
}
/* Apply tree position-specific permissions */
void esdfs_set_derived_perms(struct inode *inode)
{
struct esdfs_sb_info *sbi = ESDFS_SB(inode->i_sb);
struct esdfs_inode_info *inode_i = ESDFS_I(inode);
gid_t gid = sbi->upper_perms.gid;
esdfs_i_uid_write(inode, sbi->upper_perms.uid);
inode->i_mode &= S_IFMT;
if (ESDFS_RESTRICT_PERMS(sbi))
esdfs_i_gid_write(inode, gid);
else {
if (gid == AID_SDCARD_RW && !test_opt(sbi, DEFAULT_NORMAL))
esdfs_i_gid_write(inode, AID_SDCARD_RW);
else
esdfs_i_gid_write(inode, derive_uid(inode_i, gid));
inode->i_mode |= sbi->upper_perms.dmask;
}
switch (inode_i->tree) {
case ESDFS_TREE_ROOT_LEGACY:
if (ESDFS_RESTRICT_PERMS(sbi))
inode->i_mode |= sbi->upper_perms.dmask;
else if (test_opt(sbi, DERIVE_MULTI)) {
inode->i_mode &= S_IFMT;
inode->i_mode |= 0711;
}
break;
case ESDFS_TREE_NONE:
case ESDFS_TREE_ROOT:
if (ESDFS_RESTRICT_PERMS(sbi)) {
esdfs_i_gid_write(inode, AID_SDCARD_R);
inode->i_mode |= sbi->upper_perms.dmask;
} else if (test_opt(sbi, DERIVE_PUBLIC) &&
test_opt(ESDFS_SB(inode->i_sb), DERIVE_CONFINE)) {
inode->i_mode &= S_IFMT;
inode->i_mode |= 0771;
}
break;
case ESDFS_TREE_MEDIA:
if (ESDFS_RESTRICT_PERMS(sbi)) {
esdfs_i_gid_write(inode, AID_SDCARD_R);
inode->i_mode |= 0770;
}
break;
case ESDFS_TREE_DOWNLOAD:
case ESDFS_TREE_ANDROID:
case ESDFS_TREE_ANDROID_DATA:
case ESDFS_TREE_ANDROID_OBB:
case ESDFS_TREE_ANDROID_MEDIA:
if (ESDFS_RESTRICT_PERMS(sbi))
inode->i_mode |= 0771;
break;
case ESDFS_TREE_ANDROID_APP:
case ESDFS_TREE_ANDROID_APP_CACHE:
if (inode_i->appid)
esdfs_i_uid_write(inode, derive_uid(inode_i,
inode_i->appid));
if (ESDFS_RESTRICT_PERMS(sbi))
inode->i_mode |= 0770;
break;
case ESDFS_TREE_ANDROID_USER:
if (ESDFS_RESTRICT_PERMS(sbi)) {
esdfs_i_gid_write(inode, AID_SDCARD_ALL);
inode->i_mode |= 0770;
}
inode->i_mode |= 0770;
break;
}
/* strip execute bits from any non-directories */
if (!S_ISDIR(inode->i_mode))
inode->i_mode &= ~S_IXUGO;
}
/*
* Before rerouting a lookup to follow a pseudo hard link, make sure that
* a stub exists at the source. Without it, readdir won't see an entry there
* resulting in a strange user experience.
*/
static int lookup_link_source(struct dentry *dentry, struct dentry *parent)
{
struct path lower_parent_path, lower_path;
int err;
esdfs_get_lower_path(parent, &lower_parent_path);
/* Check if the stub user profile folder is there. */
err = esdfs_lookup_nocase(&lower_parent_path, &dentry->d_name,
&lower_path);
/* Remember it to handle renames and removal. */
if (!err)
esdfs_set_lower_stub_path(dentry, &lower_path);
esdfs_put_lower_path(parent, &lower_parent_path);
return err;
}
int esdfs_is_dl_lookup(struct dentry *dentry, struct dentry *parent)
{
struct esdfs_sb_info *sbi = ESDFS_SB(parent->d_sb);
struct esdfs_inode_info *parent_i = ESDFS_I(parent->d_inode);
/*
* Return 1 if this is the Download directory:
* The test for download checks:
* 1. The parent is the mount root.
* 2. The directory is named 'Download'.
* 3. The stub for the directory exists.
*/
if (test_opt(sbi, SPECIAL_DOWNLOAD) &&
parent_i->tree == ESDFS_TREE_ROOT &&
ESDFS_DENTRY_NEEDS_DL_LINK(dentry) &&
lookup_link_source(dentry, parent) == 0) {
return 1;
}
return 0;
}
int esdfs_derived_lookup(struct dentry *dentry, struct dentry **parent)
{
struct esdfs_sb_info *sbi = ESDFS_SB((*parent)->d_sb);
struct esdfs_inode_info *parent_i = ESDFS_I((*parent)->d_inode);
struct qstr q_Android = QSTR_LITERAL("Android");
/* Deny access to security-sensitive entries. */
if (ESDFS_I((*parent)->d_inode)->tree == ESDFS_TREE_ROOT &&
match_name(&dentry->d_name, names_secure)) {
pr_debug("esdfs: denying access to: %s", dentry->d_name.name);
return -EACCES;
}
/* Pin the unified mode obb link parent as it flies by. */
if (!sbi->obb_parent &&
test_opt(sbi, DERIVE_UNIFIED) &&
parent_i->tree == ESDFS_TREE_ROOT &&
parent_i->userid == 0 &&
qstr_case_eq(&dentry->d_name, &q_Android))
sbi->obb_parent = dget(dentry); /* keep it pinned */
/*
* Handle obb directory "grafting" as a pseudo hard link by overriding
* its parent to point to the target obb directory's parent. The rest
* of the lookup process will take care of setting up the bottom half
* to point to the real obb directory.
*/
if (parent_i->tree == ESDFS_TREE_ANDROID &&
ESDFS_DENTRY_NEEDS_LINK(dentry) &&
lookup_link_source(dentry, *parent) == 0) {
BUG_ON(!sbi->obb_parent);
if (ESDFS_INODE_CAN_LINK((*parent)->d_inode))
*parent = dget(sbi->obb_parent);
}
return 0;
}
int esdfs_derived_revalidate(struct dentry *dentry, struct dentry *parent)
{
/*
* If obb is not linked yet, it means the dentry is pointing to the
* stub. Invalidate the dentry to force another lookup.
*/
if (ESDFS_I(parent->d_inode)->tree == ESDFS_TREE_ANDROID &&
ESDFS_INODE_CAN_LINK(dentry->d_inode) &&
ESDFS_DENTRY_NEEDS_LINK(dentry) &&
!ESDFS_DENTRY_IS_LINKED(dentry))
return -ESTALE;
if (ESDFS_I(parent->d_inode)->tree == ESDFS_TREE_ROOT &&
ESDFS_DENTRY_NEEDS_DL_LINK(dentry) &&
!ESDFS_DENTRY_IS_LINKED(dentry))
return -ESTALE;
return 0;
}
/*
* Implement the extra checking that is done based on the caller's package
* list-based access rights.
*/
int esdfs_check_derived_permission(struct inode *inode, int mask)
{
const struct cred *cred;
uid_t uid, appid;
/*
* If we don't need to restrict access based on app GIDs and confine
* writes to outside of the Android/... tree, we can skip all of this.
*/
if (!ESDFS_RESTRICT_PERMS(ESDFS_SB(inode->i_sb)) &&
!test_opt(ESDFS_SB(inode->i_sb), DERIVE_CONFINE))
return 0;
cred = current_cred();
uid = from_kuid(&init_user_ns, cred->uid);
appid = uid % PKG_APPID_PER_USER;
/* Reads, owners, and root are always granted access */
if (!(mask & (MAY_WRITE | ESDFS_MAY_CREATE)) ||
uid == 0 || uid_eq(cred->uid, inode->i_uid))
return 0;
/*
* Grant access to sdcard_rw holders, unless we are in unified mode
* and we are trying to write to the protected /Android tree or to
* create files in the root (aka, "confined" access).
*/
if ((!test_opt(ESDFS_SB(inode->i_sb), DERIVE_UNIFIED) ||
(ESDFS_I(inode)->tree != ESDFS_TREE_ANDROID &&
ESDFS_I(inode)->tree != ESDFS_TREE_DOWNLOAD &&
ESDFS_I(inode)->tree != ESDFS_TREE_ANDROID_DATA &&
ESDFS_I(inode)->tree != ESDFS_TREE_ANDROID_OBB &&
ESDFS_I(inode)->tree != ESDFS_TREE_ANDROID_MEDIA &&
ESDFS_I(inode)->tree != ESDFS_TREE_ANDROID_APP &&
ESDFS_I(inode)->tree != ESDFS_TREE_ANDROID_APP_CACHE &&
(ESDFS_I(inode)->tree != ESDFS_TREE_ROOT ||
!(mask & ESDFS_MAY_CREATE)))))
return 0;
pr_debug("esdfs: %s: denying access to appid: %u\n", __func__, appid);
return -EACCES;
}
static gid_t get_type(struct esdfs_sb_info *sbi, const char *name)
{
const char *ext = strrchr(name, '.');
kgid_t id;
if (ext && ext[0]) {
ext = &ext[1];
id = pkglist_get_ext_gid(ext);
return gid_valid(id)?esdfs_from_kgid(sbi, id):AID_MEDIA_RW;
}
return AID_MEDIA_RW;
}
static kuid_t esdfs_get_derived_lower_uid(struct esdfs_sb_info *sbi,
struct esdfs_inode_info *info)
{
uid_t uid = sbi->lower_perms.uid;
int perm;
perm = info->tree;
if (info->under_obb)
perm = ESDFS_TREE_ANDROID_OBB;
switch (perm) {
case ESDFS_TREE_DOWNLOAD:
if (test_opt(sbi, SPECIAL_DOWNLOAD))
return make_kuid(sbi->dl_ns,
sbi->lower_dl_perms.raw_uid);
case ESDFS_TREE_ROOT:
case ESDFS_TREE_MEDIA:
case ESDFS_TREE_ANDROID:
case ESDFS_TREE_ANDROID_DATA:
case ESDFS_TREE_ANDROID_MEDIA:
case ESDFS_TREE_ANDROID_APP:
case ESDFS_TREE_ANDROID_APP_CACHE:
uid = derive_uid(info, uid);
break;
case ESDFS_TREE_ANDROID_OBB:
uid = AID_MEDIA_OBB;
break;
case ESDFS_TREE_ROOT_LEGACY:
default:
break;
}
return esdfs_make_kuid(sbi, uid);
}
static kgid_t esdfs_get_derived_lower_gid(struct esdfs_sb_info *sbi,
struct esdfs_inode_info *info, const char *name)
{
gid_t gid = sbi->lower_perms.gid;
uid_t upper_uid;
int perm;
upper_uid = esdfs_i_uid_read(&info->vfs_inode);
perm = info->tree;
if (info->under_obb)
perm = ESDFS_TREE_ANDROID_OBB;
switch (perm) {
case ESDFS_TREE_DOWNLOAD:
if (test_opt(sbi, SPECIAL_DOWNLOAD))
return make_kgid(sbi->dl_ns,
sbi->lower_dl_perms.raw_gid);
case ESDFS_TREE_ROOT:
case ESDFS_TREE_MEDIA:
case ESDFS_TREE_ANDROID:
case ESDFS_TREE_ANDROID_DATA:
case ESDFS_TREE_ANDROID_MEDIA:
if (S_ISDIR(info->vfs_inode.i_mode))
gid = derive_uid(info, AID_MEDIA_RW);
else
gid = derive_uid(info, get_type(sbi, name));
break;
case ESDFS_TREE_ANDROID_OBB:
gid = AID_MEDIA_OBB;
break;
case ESDFS_TREE_ANDROID_APP:
if (uid_is_app(upper_uid))
gid = multiuser_get_ext_gid(upper_uid);
else
gid = derive_uid(info, AID_MEDIA_RW);
break;
case ESDFS_TREE_ANDROID_APP_CACHE:
if (uid_is_app(upper_uid))
gid = multiuser_get_ext_cache_gid(upper_uid);
else
gid = derive_uid(info, AID_MEDIA_RW);
break;
case ESDFS_TREE_ROOT_LEGACY:
default:
break;
}
return esdfs_make_kgid(sbi, gid);
}
void esdfs_derive_lower_ownership(struct dentry *dentry, const char *name)
{
struct path path;
struct inode *inode;
struct inode *delegated_inode = NULL;
int error;
struct esdfs_sb_info *sbi = ESDFS_SB(dentry->d_sb);
struct esdfs_inode_info *info = ESDFS_I(dentry->d_inode);
kuid_t kuid;
kgid_t kgid;
struct iattr newattrs;
if (!test_opt(sbi, GID_DERIVATION))
return;
esdfs_get_lower_path(dentry, &path);
inode = path.dentry->d_inode;
kuid = esdfs_get_derived_lower_uid(sbi, info);
kgid = esdfs_get_derived_lower_gid(sbi, info, name);
if (!gid_eq(path.dentry->d_inode->i_gid, kgid)
|| !uid_eq(path.dentry->d_inode->i_uid, kuid)) {
retry_deleg:
newattrs.ia_valid = ATTR_GID | ATTR_UID | ATTR_FORCE;
newattrs.ia_uid = kuid;
newattrs.ia_gid = kgid;
if (!S_ISDIR(inode->i_mode))
newattrs.ia_valid |= ATTR_KILL_SUID | ATTR_KILL_SGID
| ATTR_KILL_PRIV;
inode_lock(inode);
error = security_path_chown(&path, newattrs.ia_uid,
newattrs.ia_gid);
if (!error)
error = notify_change(path.dentry, &newattrs,
&delegated_inode);
inode_unlock(inode);
if (delegated_inode) {
error = break_deleg_wait(&delegated_inode);
if (!error)
goto retry_deleg;
}
if (error)
pr_debug("esdfs: Failed to touch up lower fs gid/uid for %s\n", name);
}
esdfs_put_lower_path(dentry, &path);
}
/*
* The sdcard service has a hack that creates .nomedia files along certain
* paths to stop MediaScanner. Create those here.
*/
int esdfs_derive_mkdir_contents(struct dentry *dir_dentry)
{
struct esdfs_inode_info *inode_i;
struct qstr nomedia;
struct dentry *lower_dentry;
struct path lower_dir_path, lower_path;
struct dentry *lower_parent_dentry = NULL;
umode_t mode;
int err = 0;
const struct cred *creds;
int mask = 0;
if (!dir_dentry->d_inode)
return 0;
inode_i = ESDFS_I(dir_dentry->d_inode);
/*
* Only create .nomedia in Android/data and Android/obb, but never in
* pseudo link stubs.
*/
if ((inode_i->tree != ESDFS_TREE_ANDROID_DATA &&
inode_i->tree != ESDFS_TREE_ANDROID_OBB) ||
(ESDFS_INODE_CAN_LINK(dir_dentry->d_inode) &&
ESDFS_DENTRY_NEEDS_LINK(dir_dentry) &&
!ESDFS_DENTRY_IS_LINKED(dir_dentry)))
return 0;
esdfs_get_lower_path(dir_dentry, &lower_dir_path);
nomedia.name = ".nomedia";
nomedia.len = strlen(nomedia.name);
nomedia.hash = full_name_hash(lower_dir_path.dentry, nomedia.name,
nomedia.len);
/* check if lower has its own hash */
if (lower_dir_path.dentry->d_flags & DCACHE_OP_HASH)
lower_dir_path.dentry->d_op->d_hash(lower_dir_path.dentry,
&nomedia);
creds = esdfs_override_creds(ESDFS_SB(dir_dentry->d_sb),
inode_i, &mask);
/* See if the lower file is there already. */
err = vfs_path_lookup(lower_dir_path.dentry, lower_dir_path.mnt,
nomedia.name, 0, &lower_path);
if (!err)
path_put(&lower_path);
/* If it's there or there was an error, we're done */
if (!err || err != -ENOENT)
goto out;
/* The lower file is not there. See if the dentry is in the cache. */
lower_dentry = d_lookup(lower_dir_path.dentry, &nomedia);
if (!lower_dentry) {
/* It's not there, so create a negative lower dentry. */
lower_dentry = d_alloc(lower_dir_path.dentry, &nomedia);
if (!lower_dentry) {
err = -ENOMEM;
goto out;
}
d_add(lower_dentry, NULL);
}
/* Now create the lower file. */
mode = S_IFREG;
lower_parent_dentry = lock_parent(lower_dentry);
esdfs_set_lower_mode(ESDFS_SB(dir_dentry->d_sb), inode_i, &mode);
err = vfs_create(lower_dir_path.dentry->d_inode, lower_dentry, mode,
true);
unlock_dir(lower_parent_dentry);
dput(lower_dentry);
out:
esdfs_put_lower_path(dir_dentry, &lower_dir_path);
esdfs_revert_creds(creds, &mask);
return err;
}