diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-07-29 22:13:07 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-07-29 22:13:07 +0300 |
commit | e7b4f2d8edbbc58c8e2c3134ff884611433ba3db (patch) | |
tree | 62e5a07a9f3de98e18d23333082e0990ddcde89f /fs/overlayfs/inode.c | |
parent | 0a7736d03720a450727c6ab906e13b60d4d34e42 (diff) | |
parent | 30c17ebfb2a11468fe825de19afa3934ee98bfd2 (diff) | |
download | linux-e7b4f2d8edbbc58c8e2c3134ff884611433ba3db.tar.xz |
Merge branch 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
Pull overlayfs update from Miklos Szeredi:
"First of all, this fixes a regression in overlayfs introduced by the
dentry hash salting. I've moved the patch fixing this to the front of
the queue, so if (god forbid) something needs to be bisected in
overlayfs this regression won't interfere with that.
The biggest part is preparation for selinux support, done by Vivek
Goyal. Essentially this makes all operations on underlying
filesystems be done with credentials of mounter. This makes
everything nicely consistent.
There are also fixes for a number of known and recently discovered
non-standard behavior (thanks to Eryu Guan for testing and improving
the test suites)"
* 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs: (23 commits)
ovl: simplify empty checking
qstr: constify instances in overlayfs
ovl: clear nlink on rmdir
ovl: disallow overlayfs as upperdir
ovl: fix warning
ovl: remove duplicated include from super.c
ovl: append MAY_READ when diluting write checks
ovl: dilute permission checks on lower only if not special file
ovl: fix POSIX ACL setting
ovl: share inode for hard link
ovl: store real inode pointer in ->i_private
ovl: permission: return ECHILD instead of ENOENT
ovl: update atime on upper
ovl: fix sgid on directory
ovl: simplify permission checking
ovl: do not require mounter to have MAY_WRITE on lower
ovl: do operations on underlying file system in mounter's context
ovl: modify ovl_permission() to do checks on two inodes
ovl: define ->get_acl() for overlay inodes
ovl: move some common code in a function
...
Diffstat (limited to 'fs/overlayfs/inode.c')
-rw-r--r-- | fs/overlayfs/inode.c | 246 |
1 files changed, 145 insertions, 101 deletions
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index d554e86abbe3..1b885c156028 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -41,6 +41,7 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr) { int err; struct dentry *upperdentry; + const struct cred *old_cred; /* * Check for permissions before trying to copy-up. This is redundant @@ -84,7 +85,9 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr) attr->ia_valid &= ~ATTR_MODE; inode_lock(upperdentry->d_inode); + old_cred = ovl_override_creds(dentry->d_sb); err = notify_change(upperdentry, attr, NULL); + revert_creds(old_cred); if (!err) ovl_copyattr(upperdentry->d_inode, dentry->d_inode); inode_unlock(upperdentry->d_inode); @@ -102,96 +105,46 @@ static int ovl_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) { struct path realpath; + const struct cred *old_cred; + int err; ovl_path_real(dentry, &realpath); - return vfs_getattr(&realpath, stat); + old_cred = ovl_override_creds(dentry->d_sb); + err = vfs_getattr(&realpath, stat); + revert_creds(old_cred); + return err; } int ovl_permission(struct inode *inode, int mask) { - struct ovl_entry *oe; - struct dentry *alias = NULL; - struct inode *realinode; - struct dentry *realdentry; bool is_upper; + struct inode *realinode = ovl_inode_real(inode, &is_upper); + const struct cred *old_cred; int err; - if (S_ISDIR(inode->i_mode)) { - oe = inode->i_private; - } else if (mask & MAY_NOT_BLOCK) { - return -ECHILD; - } else { - /* - * For non-directories find an alias and get the info - * from there. - */ - alias = d_find_any_alias(inode); - if (WARN_ON(!alias)) - return -ENOENT; - - oe = alias->d_fsdata; - } - - realdentry = ovl_entry_real(oe, &is_upper); - - if (ovl_is_default_permissions(inode)) { - struct kstat stat; - struct path realpath = { .dentry = realdentry }; - - if (mask & MAY_NOT_BLOCK) - return -ECHILD; - - realpath.mnt = ovl_entry_mnt_real(oe, inode, is_upper); - - err = vfs_getattr(&realpath, &stat); - if (err) - goto out_dput; - - err = -ESTALE; - if ((stat.mode ^ inode->i_mode) & S_IFMT) - goto out_dput; - - inode->i_mode = stat.mode; - inode->i_uid = stat.uid; - inode->i_gid = stat.gid; - - err = generic_permission(inode, mask); - goto out_dput; - } - /* Careful in RCU walk mode */ - realinode = ACCESS_ONCE(realdentry->d_inode); if (!realinode) { WARN_ON(!(mask & MAY_NOT_BLOCK)); - err = -ENOENT; - goto out_dput; + return -ECHILD; } - if (mask & MAY_WRITE) { - umode_t mode = realinode->i_mode; - - /* - * Writes will always be redirected to upper layer, so - * ignore lower layer being read-only. - * - * If the overlay itself is read-only then proceed - * with the permission check, don't return EROFS. - * This will only happen if this is the lower layer of - * another overlayfs. - * - * If upper fs becomes read-only after the overlay was - * constructed return EROFS to prevent modification of - * upper layer. - */ - err = -EROFS; - if (is_upper && !IS_RDONLY(inode) && IS_RDONLY(realinode) && - (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) - goto out_dput; + /* + * Check overlay inode with the creds of task and underlying inode + * with creds of mounter + */ + err = generic_permission(inode, mask); + if (err) + return err; + + old_cred = ovl_override_creds(inode->i_sb); + if (!is_upper && !special_file(realinode->i_mode) && mask & MAY_WRITE) { + mask &= ~(MAY_WRITE | MAY_APPEND); + /* Make sure mounter can read file for copy up later */ + mask |= MAY_READ; } + err = inode_permission(realinode, mask); + revert_creds(old_cred); - err = __inode_permission(realinode, mask); -out_dput: - dput(alias); return err; } @@ -201,6 +154,8 @@ static const char *ovl_get_link(struct dentry *dentry, { struct dentry *realdentry; struct inode *realinode; + const struct cred *old_cred; + const char *p; if (!dentry) return ERR_PTR(-ECHILD); @@ -211,13 +166,18 @@ static const char *ovl_get_link(struct dentry *dentry, if (WARN_ON(!realinode->i_op->get_link)) return ERR_PTR(-EPERM); - return realinode->i_op->get_link(realdentry, realinode, done); + old_cred = ovl_override_creds(dentry->d_sb); + p = realinode->i_op->get_link(realdentry, realinode, done); + revert_creds(old_cred); + return p; } static int ovl_readlink(struct dentry *dentry, char __user *buf, int bufsiz) { struct path realpath; struct inode *realinode; + const struct cred *old_cred; + int err; ovl_path_real(dentry, &realpath); realinode = realpath.dentry->d_inode; @@ -225,15 +185,17 @@ static int ovl_readlink(struct dentry *dentry, char __user *buf, int bufsiz) if (!realinode->i_op->readlink) return -EINVAL; - touch_atime(&realpath); - - return realinode->i_op->readlink(realpath.dentry, buf, bufsiz); + old_cred = ovl_override_creds(dentry->d_sb); + err = realinode->i_op->readlink(realpath.dentry, buf, bufsiz); + revert_creds(old_cred); + return err; } - static bool ovl_is_private_xattr(const char *name) { - return strncmp(name, OVL_XATTR_PRE_NAME, OVL_XATTR_PRE_LEN) == 0; +#define OVL_XATTR_PRE_NAME OVL_XATTR_PREFIX "." + return strncmp(name, OVL_XATTR_PRE_NAME, + sizeof(OVL_XATTR_PRE_NAME) - 1) == 0; } int ovl_setxattr(struct dentry *dentry, struct inode *inode, @@ -242,21 +204,20 @@ int ovl_setxattr(struct dentry *dentry, struct inode *inode, { int err; struct dentry *upperdentry; + const struct cred *old_cred; err = ovl_want_write(dentry); if (err) goto out; - err = -EPERM; - if (ovl_is_private_xattr(name)) - goto out_drop_write; - err = ovl_copy_up(dentry); if (err) goto out_drop_write; upperdentry = ovl_dentry_upper(dentry); + old_cred = ovl_override_creds(dentry->d_sb); err = vfs_setxattr(upperdentry, name, value, size, flags); + revert_creds(old_cred); out_drop_write: ovl_drop_write(dentry); @@ -268,11 +229,16 @@ ssize_t ovl_getxattr(struct dentry *dentry, struct inode *inode, const char *name, void *value, size_t size) { struct dentry *realdentry = ovl_dentry_real(dentry); + ssize_t res; + const struct cred *old_cred; if (ovl_is_private_xattr(name)) return -ENODATA; - return vfs_getxattr(realdentry, name, value, size); + old_cred = ovl_override_creds(dentry->d_sb); + res = vfs_getxattr(realdentry, name, value, size); + revert_creds(old_cred); + return res; } ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size) @@ -280,8 +246,11 @@ ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size) struct dentry *realdentry = ovl_dentry_real(dentry); ssize_t res; int off; + const struct cred *old_cred; + old_cred = ovl_override_creds(dentry->d_sb); res = vfs_listxattr(realdentry, list, size); + revert_creds(old_cred); if (res <= 0 || size == 0) return res; @@ -308,6 +277,7 @@ int ovl_removexattr(struct dentry *dentry, const char *name) int err; struct path realpath; enum ovl_path_type type = ovl_path_real(dentry, &realpath); + const struct cred *old_cred; err = ovl_want_write(dentry); if (err) @@ -329,13 +299,34 @@ int ovl_removexattr(struct dentry *dentry, const char *name) ovl_path_upper(dentry, &realpath); } + old_cred = ovl_override_creds(dentry->d_sb); err = vfs_removexattr(realpath.dentry, name); + revert_creds(old_cred); out_drop_write: ovl_drop_write(dentry); out: return err; } +struct posix_acl *ovl_get_acl(struct inode *inode, int type) +{ + struct inode *realinode = ovl_inode_real(inode, NULL); + const struct cred *old_cred; + struct posix_acl *acl; + + if (!IS_POSIXACL(realinode)) + return NULL; + + if (!realinode->i_op->get_acl) + return NULL; + + old_cred = ovl_override_creds(inode->i_sb); + acl = realinode->i_op->get_acl(realinode, type); + revert_creds(old_cred); + + return acl; +} + static bool ovl_open_need_copy_up(int flags, enum ovl_path_type type, struct dentry *realdentry) { @@ -372,14 +363,39 @@ int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags) return err; } +int ovl_update_time(struct inode *inode, struct timespec *ts, int flags) +{ + struct dentry *alias; + struct path upperpath; + + if (!(flags & S_ATIME)) + return 0; + + alias = d_find_any_alias(inode); + if (!alias) + return 0; + + ovl_path_upper(alias, &upperpath); + if (upperpath.dentry) { + touch_atime(&upperpath); + inode->i_atime = d_inode(upperpath.dentry)->i_atime; + } + + dput(alias); + + return 0; +} + static const struct inode_operations ovl_file_inode_operations = { .setattr = ovl_setattr, .permission = ovl_permission, .getattr = ovl_getattr, - .setxattr = ovl_setxattr, + .setxattr = generic_setxattr, .getxattr = ovl_getxattr, .listxattr = ovl_listxattr, .removexattr = ovl_removexattr, + .get_acl = ovl_get_acl, + .update_time = ovl_update_time, }; static const struct inode_operations ovl_symlink_inode_operations = { @@ -387,29 +403,22 @@ static const struct inode_operations ovl_symlink_inode_operations = { .get_link = ovl_get_link, .readlink = ovl_readlink, .getattr = ovl_getattr, - .setxattr = ovl_setxattr, + .setxattr = generic_setxattr, .getxattr = ovl_getxattr, .listxattr = ovl_listxattr, .removexattr = ovl_removexattr, + .update_time = ovl_update_time, }; -struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, - struct ovl_entry *oe) +static void ovl_fill_inode(struct inode *inode, umode_t mode) { - struct inode *inode; - - inode = new_inode(sb); - if (!inode) - return NULL; - inode->i_ino = get_next_ino(); inode->i_mode = mode; - inode->i_flags |= S_NOATIME | S_NOCMTIME; + inode->i_flags |= S_NOCMTIME; mode &= S_IFMT; switch (mode) { case S_IFDIR: - inode->i_private = oe; inode->i_op = &ovl_dir_inode_operations; inode->i_fop = &ovl_dir_operations; break; @@ -418,6 +427,10 @@ struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, inode->i_op = &ovl_symlink_inode_operations; break; + default: + WARN(1, "illegal file type: %i\n", mode); + /* Fall through */ + case S_IFREG: case S_IFSOCK: case S_IFBLK: @@ -425,11 +438,42 @@ struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, case S_IFIFO: inode->i_op = &ovl_file_inode_operations; break; + } +} - default: - WARN(1, "illegal file type: %i\n", mode); - iput(inode); - inode = NULL; +struct inode *ovl_new_inode(struct super_block *sb, umode_t mode) +{ + struct inode *inode; + + inode = new_inode(sb); + if (inode) + ovl_fill_inode(inode, mode); + + return inode; +} + +static int ovl_inode_test(struct inode *inode, void *data) +{ + return ovl_inode_real(inode, NULL) == data; +} + +static int ovl_inode_set(struct inode *inode, void *data) +{ + inode->i_private = (void *) (((unsigned long) data) | OVL_ISUPPER_MASK); + return 0; +} + +struct inode *ovl_get_inode(struct super_block *sb, struct inode *realinode) + +{ + struct inode *inode; + + inode = iget5_locked(sb, (unsigned long) realinode, + ovl_inode_test, ovl_inode_set, realinode); + if (inode && inode->i_state & I_NEW) { + ovl_fill_inode(inode, realinode->i_mode); + set_nlink(inode, realinode->i_nlink); + unlock_new_inode(inode); } return inode; |