static int esdfs_permission(struct inode *inode, int mask) { struct esdfs_sb_info *sbi = ESDFS_SB(inode->i_sb); struct inode *lower_inode; int err; /* First, check the upper permissions */ err = generic_permission(inode, mask); /* Basic checking of the lower inode (can't override creds here) */ lower_inode = esdfs_lower_inode(inode); if (lower_inode->i_uid != sbi->lower_perms.uid || lower_inode->i_gid != sbi->lower_perms.gid || S_ISSOCK(lower_inode->i_mode) || S_ISLNK(lower_inode->i_mode) || S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || S_ISFIFO(lower_inode->i_mode)) err = -EACCES; /* Finally, check the derived permissions */ if (!err && ESDFS_DERIVE_PERMS(ESDFS_SB(inode->i_sb))) err = esdfs_check_derived_permission(inode, mask); return err; }
static void esdfs_kill_sb(struct super_block *sb) { if (ESDFS_SB(sb)->obb_parent) dput(ESDFS_SB(sb)->obb_parent); generic_shutdown_super(sb); }
static int esdfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, struct nameidata *nd) { int err = 0; struct dentry *lower_dentry; struct dentry *lower_parent_dentry = NULL; struct path lower_path, saved_path; struct inode *lower_inode; int mask; const struct cred *creds; /* * Need to recheck derived permissions unified mode to prevent certain * applications from creating files at the root. */ if (test_opt(ESDFS_SB(dir->i_sb), DERIVE_UNIFIED) && esdfs_check_derived_permission(dir, ESDFS_MAY_CREATE) != 0) return -EACCES; creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), &mask); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; lower_parent_dentry = lock_parent(lower_dentry); err = mnt_want_write(lower_path.mnt); if (err) goto out_unlock; esdfs_set_lower_mode(ESDFS_SB(dir->i_sb), &mode); lower_inode = esdfs_lower_inode(dir); pathcpy(&saved_path, &nd->path); pathcpy(&nd->path, &lower_path); err = vfs_create(lower_inode, lower_dentry, mode, nd); pathcpy(&nd->path, &saved_path); if (err) goto out; err = esdfs_interpose(dentry, dir->i_sb, &lower_path); if (err) goto out; fsstack_copy_attr_times(dir, esdfs_lower_inode(dir)); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); out: mnt_drop_write(lower_path.mnt); out_unlock: unlock_dir(lower_parent_dentry); esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, &mask); return err; }
static int esdfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { int err = 0; struct dentry *lower_dentry; struct dentry *lower_parent_dentry = NULL; struct path lower_path; int mask; const struct cred *creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), &mask); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; lower_parent_dentry = lock_parent(lower_dentry); mode |= S_IFDIR; esdfs_set_lower_mode(ESDFS_SB(dir->i_sb), &mode); err = mnt_want_write(lower_path.mnt); if (err) goto out_unlock; err = vfs_mkdir(lower_parent_dentry->d_inode, lower_dentry, mode); if (err) goto out; err = esdfs_interpose(dentry, dir->i_sb, &lower_path); if (err) goto out; fsstack_copy_attr_times(dir, esdfs_lower_inode(dir)); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update number of links on parent directory */ set_nlink(dir, esdfs_lower_inode(dir)->i_nlink); if (ESDFS_DERIVE_PERMS(ESDFS_SB(dir->i_sb))) err = esdfs_derive_mkdir_contents(dentry); out: mnt_drop_write(lower_path.mnt); out_unlock: unlock_dir(lower_parent_dentry); esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, &mask); return err; }
static int esdfs_unlink(struct inode *dir, struct dentry *dentry) { int err; struct dentry *lower_dentry; struct inode *lower_dir_inode = esdfs_lower_inode(dir); struct dentry *lower_dir_dentry; struct path lower_path; const struct cred *creds; creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), NULL); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; dget(lower_dentry); lower_dir_dentry = lock_parent(lower_dentry); err = mnt_want_write(lower_path.mnt); if (err) goto out_unlock; err = vfs_unlink(lower_dir_inode, lower_dentry); /* * Note: unlinking on top of NFS can cause silly-renamed files. * Trying to delete such files results in EBUSY from NFS * below. Silly-renamed files will get deleted by NFS later on, so * we just need to detect them here and treat such EBUSY errors as * if the upper file was successfully deleted. */ if (err == -EBUSY && lower_dentry->d_flags & DCACHE_NFSFS_RENAMED) err = 0; if (err) goto out; fsstack_copy_attr_times(dir, lower_dir_inode); fsstack_copy_inode_size(dir, lower_dir_inode); set_nlink(dentry->d_inode, esdfs_lower_inode(dentry->d_inode)->i_nlink); dentry->d_inode->i_ctime = dir->i_ctime; d_drop(dentry); /* this is needed, else LTP fails (VFS won't do it) */ out: mnt_drop_write(lower_path.mnt); out_unlock: unlock_dir(lower_dir_dentry); dput(lower_dentry); esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, NULL); return err; }
/* final actions when unmounting a file system */ static void esdfs_put_super(struct super_block *sb) { struct esdfs_sb_info *spd; struct super_block *s; spd = ESDFS_SB(sb); if (!spd) return; /* decrement lower super references */ s = esdfs_lower_super(sb); esdfs_set_lower_super(sb, NULL); atomic_dec(&s->s_active); kfree(spd); sb->s_fs_info = NULL; }
static int esdfs_rmdir(struct inode *dir, struct dentry *dentry) { struct dentry *lower_dentry; struct dentry *lower_dir_dentry; int err; struct path lower_path; const struct cred *creds = esdfs_override_creds(ESDFS_SB(dir->i_sb), NULL); if (!creds) return -ENOMEM; /* Never remove a pseudo link target. Only the source. */ if (ESDFS_DENTRY_HAS_STUB(dentry)) esdfs_get_lower_stub_path(dentry, &lower_path); else esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; lower_dir_dentry = lock_parent(lower_dentry); err = mnt_want_write(lower_path.mnt); if (err) goto out_unlock; err = vfs_rmdir(lower_dir_dentry->d_inode, lower_dentry); if (err) goto out; d_drop(dentry); /* drop our dentry on success (why not VFS's job?) */ if (dentry->d_inode) clear_nlink(dentry->d_inode); fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode); fsstack_copy_inode_size(dir, lower_dir_dentry->d_inode); set_nlink(dir, lower_dir_dentry->d_inode->i_nlink); out: mnt_drop_write(lower_path.mnt); out_unlock: unlock_dir(lower_dir_dentry); esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, NULL); return err; }
static int esdfs_show_options(struct seq_file *seq, struct dentry *root) { struct esdfs_sb_info *sbi = ESDFS_SB(root->d_sb); if (memcmp(&sbi->lower_perms, &esdfs_perms_table[ESDFS_PERMS_LOWER_DEFAULT], sizeof(struct esdfs_perms))) seq_printf(seq, ",lower=%u:%u:%ho:%ho", sbi->lower_perms.uid, sbi->lower_perms.gid, sbi->lower_perms.fmask, sbi->lower_perms.dmask); if (memcmp(&sbi->upper_perms, &esdfs_perms_table[ESDFS_PERMS_UPPER_LEGACY], sizeof(struct esdfs_perms))) seq_printf(seq, ",upper=%u:%u:%ho:%ho", sbi->upper_perms.uid, sbi->upper_perms.gid, sbi->upper_perms.fmask, sbi->upper_perms.dmask); if (test_opt(sbi, DERIVE_PUBLIC)) seq_puts(seq, ",derive=public"); else if (test_opt(sbi, DERIVE_MULTI)) seq_puts(seq, ",derive=multi"); else if (test_opt(sbi, DERIVE_UNIFIED)) seq_puts(seq, ",derive=unified"); else if (test_opt(sbi, DERIVE_LEGACY)) seq_puts(seq, ",derive=legacy"); else seq_puts(seq, ",derive=none"); if (test_opt(sbi, DERIVE_CONFINE)) seq_puts(seq, ",confine"); else seq_puts(seq, ",noconfine"); return 0; }
static int esdfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) { int err; struct path lower_path; struct vfsmount *lower_mount; struct dentry *lower_dentry; struct kstat lower_stat; struct inode *lower_inode; struct inode *inode = dentry->d_inode; const struct cred *creds = esdfs_override_creds(ESDFS_SB(inode->i_sb), NULL); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_mount = lower_path.mnt; lower_dentry = lower_path.dentry; /* We need the lower getattr to calculate stat->blocks for us. */ err = vfs_getattr(lower_mount, lower_dentry, &lower_stat); if (err) goto out; lower_inode = esdfs_lower_inode(inode); esdfs_copy_attr(inode, lower_inode); fsstack_copy_inode_size(inode, lower_inode); generic_fillattr(inode, stat); stat->blocks = lower_stat.blocks; out: esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, NULL); return err; }
static int esdfs_setattr(struct dentry *dentry, struct iattr *ia) { int err = 0; struct dentry *lower_dentry; struct inode *inode; struct inode *lower_inode; struct path lower_path; struct iattr lower_ia; const struct cred *creds; /* We don't allow chmod or chown, so skip those */ ia->ia_valid &= ~(ATTR_UID | ATTR_GID | ATTR_MODE); if (!ia->ia_valid) return 0; inode = dentry->d_inode; /* * Check if user has permission to change inode. We don't check if * this user can change the lower inode: that should happen when * calling notify_change on the lower inode. */ err = inode_change_ok(inode, ia); if (err) return err; creds = esdfs_override_creds(ESDFS_SB(dentry->d_inode->i_sb), NULL); if (!creds) return -ENOMEM; esdfs_get_lower_path(dentry, &lower_path); lower_dentry = lower_path.dentry; lower_inode = esdfs_lower_inode(inode); /* prepare our own lower struct iattr (with the lower file) */ memcpy(&lower_ia, ia, sizeof(lower_ia)); if (ia->ia_valid & ATTR_FILE) lower_ia.ia_file = esdfs_lower_file(ia->ia_file); /* * If shrinking, first truncate upper level to cancel writing dirty * pages beyond the new eof; and also if its' maxbytes is more * limiting (fail with -EFBIG before making any change to the lower * level). There is no need to vmtruncate the upper level * afterwards in the other cases: we fsstack_copy_inode_size from * the lower level. */ if (ia->ia_valid & ATTR_SIZE) { err = inode_newsize_ok(inode, ia->ia_size); if (err) goto out; truncate_setsize(inode, ia->ia_size); } /* * mode change is for clearing setuid/setgid bits. Allow lower fs * to interpret this in its own way. */ if (lower_ia.ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) lower_ia.ia_valid &= ~ATTR_MODE; /* notify the (possibly copied-up) lower inode */ /* * Note: we use lower_dentry->d_inode, because lower_inode may be * unlinked (no inode->i_sb and i_ino==0. This happens if someone * tries to open(), unlink(), then ftruncate() a file. */ mutex_lock(&lower_dentry->d_inode->i_mutex); err = notify_change(lower_dentry, &lower_ia); /* note: lower_ia */ mutex_unlock(&lower_dentry->d_inode->i_mutex); if (err) goto out; /* get attributes from the lower inode */ esdfs_copy_attr(inode, lower_inode); /* * Not running fsstack_copy_inode_size(inode, lower_inode), because * VFS should update our inode size, and notify_change on * lower_inode should update its size. */ out: esdfs_put_lower_path(dentry, &lower_path); esdfs_revert_creds(creds, NULL); return err; }
/* * The locking rules in esdfs_rename are complex. We could use a simpler * superblock-level name-space lock for renames and copy-ups. */ static int esdfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { int err = 0; struct dentry *lower_old_dentry = NULL; struct dentry *lower_new_dentry = NULL; struct dentry *lower_old_dir_dentry = NULL; struct dentry *lower_new_dir_dentry = NULL; struct dentry *trap = NULL; struct path lower_old_path, lower_new_path; int mask; const struct cred *creds = esdfs_override_creds(ESDFS_SB(old_dir->i_sb), &mask); if (!creds) return -ENOMEM; /* Never rename to or from a pseudo hard link target. */ if (ESDFS_DENTRY_HAS_STUB(old_dentry)) esdfs_get_lower_stub_path(old_dentry, &lower_old_path); else esdfs_get_lower_path(old_dentry, &lower_old_path); if (ESDFS_DENTRY_HAS_STUB(new_dentry)) esdfs_get_lower_stub_path(new_dentry, &lower_new_path); else esdfs_get_lower_path(new_dentry, &lower_new_path); lower_old_dentry = lower_old_path.dentry; lower_new_dentry = lower_new_path.dentry; esdfs_get_lower_parent(old_dentry, lower_old_dentry, &lower_old_dir_dentry); esdfs_get_lower_parent(new_dentry, lower_new_dentry, &lower_new_dir_dentry); trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry); /* source should not be ancestor of target */ if (trap == lower_old_dentry) { err = -EINVAL; goto out; } /* target should not be ancestor of source */ if (trap == lower_new_dentry) { err = -ENOTEMPTY; goto out; } err = mnt_want_write(lower_old_path.mnt); if (err) goto out; err = mnt_want_write(lower_new_path.mnt); if (err) goto out_drop_old_write; err = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry, lower_new_dir_dentry->d_inode, lower_new_dentry); if (err) goto out_err; esdfs_copy_attr(new_dir, lower_new_dir_dentry->d_inode); fsstack_copy_inode_size(new_dir, lower_new_dir_dentry->d_inode); if (new_dir != old_dir) { esdfs_copy_attr(old_dir, lower_old_dir_dentry->d_inode); fsstack_copy_inode_size(old_dir, lower_old_dir_dentry->d_inode); } /* Drop any old links */ if (ESDFS_DENTRY_HAS_STUB(old_dentry)) d_drop(old_dentry); if (ESDFS_DENTRY_HAS_STUB(new_dentry)) d_drop(new_dentry); out_err: mnt_drop_write(lower_new_path.mnt); out_drop_old_write: mnt_drop_write(lower_old_path.mnt); out: unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry); esdfs_put_lower_parent(old_dentry, &lower_old_dir_dentry); esdfs_put_lower_parent(new_dentry, &lower_new_dir_dentry); esdfs_put_lower_path(old_dentry, &lower_old_path); esdfs_put_lower_path(new_dentry, &lower_new_path); esdfs_revert_creds(creds, &mask); return err; }
static int parse_options(struct super_block *sb, char *options) { struct esdfs_sb_info *sbi = ESDFS_SB(sb); substring_t args[MAX_OPT_ARGS]; char *p; if (!options) return 0; while ((p = strsep(&options, ",")) != NULL) { int token; if (!*p) continue; /* * Initialize args struct so we know whether arg was * found; some options take optional arguments. */ args[0].to = args[0].from = NULL; token = match_token(p, esdfs_tokens, args); switch (token) { case Opt_lower_perms: if (args->from) { int ret; char *perms = match_strdup(args); ret = parse_perms(&sbi->lower_perms, perms); kfree(perms); if (ret) return -EINVAL; } else return -EINVAL; break; case Opt_upper_perms: if (args->from) { int ret; char *perms = match_strdup(args); ret = parse_perms(&sbi->upper_perms, perms); kfree(perms); if (ret) return -EINVAL; } else return -EINVAL; break; case Opt_derive_none: clear_opt(sbi, DERIVE_LEGACY); clear_opt(sbi, DERIVE_UNIFIED); break; case Opt_derive_legacy: set_opt(sbi, DERIVE_LEGACY); clear_opt(sbi, DERIVE_UNIFIED); break; case Opt_derive_unified: clear_opt(sbi, DERIVE_LEGACY); set_opt(sbi, DERIVE_UNIFIED); break; case Opt_split: set_opt(sbi, DERIVE_SPLIT); break; case Opt_nosplit: clear_opt(sbi, DERIVE_SPLIT); break; default: esdfs_msg(sb, KERN_ERR, "unrecognized mount option \"%s\" or missing value\n", p); return -EINVAL; } } return 0; }
/* * There is no need to lock the esdfs_super_info's rwsem as there is no * way anyone can have a reference to the superblock at this point in time. */ static int esdfs_read_super(struct super_block *sb, const char *dev_name, void *raw_data, int silent) { int err = 0; struct super_block *lower_sb; struct path lower_path; struct esdfs_sb_info *sbi; struct inode *inode; if (!dev_name) { esdfs_msg(sb, KERN_ERR, "missing dev_name argument\n"); err = -EINVAL; goto out; } /* parse lower path */ err = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &lower_path); if (err) { esdfs_msg(sb, KERN_ERR, "error accessing lower directory '%s'\n", dev_name); goto out; } /* allocate superblock private data */ sb->s_fs_info = kzalloc(sizeof(struct esdfs_sb_info), GFP_KERNEL); sbi = ESDFS_SB(sb); if (!sbi) { esdfs_msg(sb, KERN_CRIT, "read_super: out of memory\n"); err = -ENOMEM; goto out_pput; } /* set defaults and then parse the mount options */ memcpy(&sbi->lower_perms, &esdfs_perms_table[ESDFS_PERMS_LOWER_DEFAULT], sizeof(struct esdfs_perms)); memcpy(&sbi->upper_perms, &esdfs_perms_table[ESDFS_PERMS_UPPER_LEGACY], sizeof(struct esdfs_perms)); err = parse_options(sb, (char *)raw_data); if (err) goto out_free; /* set the lower superblock field of upper superblock */ lower_sb = lower_path.dentry->d_sb; atomic_inc(&lower_sb->s_active); esdfs_set_lower_super(sb, lower_sb); /* inherit maxbytes from lower file system */ sb->s_maxbytes = lower_sb->s_maxbytes; /* * Our c/m/atime granularity is 1 ns because we may stack on file * systems whose granularity is as good. */ sb->s_time_gran = 1; sb->s_op = &esdfs_sops; /* get a new inode and allocate our root dentry */ inode = esdfs_iget(sb, lower_path.dentry->d_inode); if (IS_ERR(inode)) { err = PTR_ERR(inode); goto out_sput; } sb->s_root = d_make_root(inode); if (!sb->s_root) { err = -ENOMEM; goto out_iput; } d_set_d_op(sb->s_root, &esdfs_dops); /* link the upper and lower dentries */ sb->s_root->d_fsdata = NULL; err = new_dentry_private_data(sb->s_root); if (err) goto out_freeroot; /* if get here: cannot have error */ /* set the lower dentries for s_root */ esdfs_set_lower_path(sb->s_root, &lower_path); #ifdef CONFIG_SECURITY_SELINUX security_secctx_to_secid(ESDFS_LOWER_SECCTX, strlen(ESDFS_LOWER_SECCTX), &sbi->lower_secid); #endif /* * No need to call interpose because we already have a positive * dentry, which was instantiated by d_make_root. Just need to * d_rehash it. */ d_rehash(sb->s_root); if (!silent) esdfs_msg(sb, KERN_INFO, "mounted on top of %s type %s\n", dev_name, lower_sb->s_type->name); if (!ESDFS_DERIVE_PERMS(sbi)) goto out; /* let user know that we ignore this option in derived mode */ if (memcmp(&sbi->upper_perms, &esdfs_perms_table[ESDFS_PERMS_UPPER_LEGACY], sizeof(struct esdfs_perms))) esdfs_msg(sb, KERN_WARNING, "'upper' mount option ignored in derived mode\n"); /* all derived modes start with the same, basic root */ memcpy(&sbi->upper_perms, &esdfs_perms_table[ESDFS_PERMS_UPPER_DERIVED], sizeof(struct esdfs_perms)); /* * In Android 3.0 all user conent in the emulated storage tree was * stored in /data/media. Android 4.2 introduced multi-user support, * which required that the primary user's content be migrated from * /data/media to /data/media/0. The framework then uses bind mounts * to create per-process namespaces to isolate each user's tree at * /data/media/N. This approach of having each user in a common root * is now considered "legacy" by the sdcard service. */ if (test_opt(sbi, DERIVE_LEGACY)) { ESDFS_I(inode)->tree = ESDFS_TREE_ROOT_LEGACY; sbi->obb_parent = dget(sb->s_root); /* * Android 4.4 reorganized this sturcture yet again, so that the * primary user's content was again at the root. Secondary users' * content is found in Android/user/N. Emulated internal storage still * seems to use the legacy tree, but secondary external storage uses * this method. */ } else if (test_opt(sbi, DERIVE_UNIFIED)) ESDFS_I(inode)->tree = ESDFS_TREE_ROOT; /* * Later versions of Android organize user content using quantum * entanglement, which has a low probability of being supported by * this driver. */ else esdfs_msg(sb, KERN_WARNING, "unsupported derived permissions mode\n"); /* initialize root inode */ esdfs_derive_perms(sb->s_root); goto out; out_freeroot: dput(sb->s_root); out_iput: iput(inode); out_sput: /* drop refs we took earlier */ atomic_dec(&lower_sb->s_active); out_free: kfree(ESDFS_SB(sb)); sb->s_fs_info = NULL; out_pput: path_put(&lower_path); out: return err; }