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 | |
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')
-rw-r--r-- | fs/overlayfs/copy_up.c | 1 | ||||
-rw-r--r-- | fs/overlayfs/dir.c | 226 | ||||
-rw-r--r-- | fs/overlayfs/inode.c | 246 | ||||
-rw-r--r-- | fs/overlayfs/overlayfs.h | 30 | ||||
-rw-r--r-- | fs/overlayfs/super.c | 178 |
5 files changed, 445 insertions, 236 deletions
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 80aa6f1eb336..54e5d6681786 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -292,6 +292,7 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, goto out_cleanup; ovl_dentry_update(dentry, newdentry); + ovl_inode_update(d_inode(dentry), d_inode(newdentry)); newdentry = NULL; /* diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 5c9d2d80ff70..12bcd07b9e32 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -138,9 +138,12 @@ static int ovl_dir_getattr(struct vfsmount *mnt, struct dentry *dentry, int err; enum ovl_path_type type; struct path realpath; + const struct cred *old_cred; type = ovl_path_real(dentry, &realpath); + old_cred = ovl_override_creds(dentry->d_sb); err = vfs_getattr(&realpath, stat); + revert_creds(old_cred); if (err) return err; @@ -158,6 +161,22 @@ static int ovl_dir_getattr(struct vfsmount *mnt, struct dentry *dentry, return 0; } +/* Common operations required to be done after creation of file on upper */ +static void ovl_instantiate(struct dentry *dentry, struct inode *inode, + struct dentry *newdentry, bool hardlink) +{ + ovl_dentry_version_inc(dentry->d_parent); + ovl_dentry_update(dentry, newdentry); + if (!hardlink) { + ovl_inode_update(inode, d_inode(newdentry)); + ovl_copyattr(newdentry->d_inode, inode); + } else { + WARN_ON(ovl_inode_real(inode, NULL) != d_inode(newdentry)); + inc_nlink(inode); + } + d_instantiate(dentry, inode); +} + static int ovl_create_upper(struct dentry *dentry, struct inode *inode, struct kstat *stat, const char *link, struct dentry *hardlink) @@ -177,10 +196,7 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode, if (err) goto out_dput; - ovl_dentry_version_inc(dentry->d_parent); - ovl_dentry_update(dentry, newdentry); - ovl_copyattr(newdentry->d_inode, inode); - d_instantiate(dentry, inode); + ovl_instantiate(dentry, inode, newdentry, !!hardlink); newdentry = NULL; out_dput: dput(newdentry); @@ -291,23 +307,29 @@ static struct dentry *ovl_check_empty_and_clear(struct dentry *dentry) { int err; struct dentry *ret = NULL; + enum ovl_path_type type = ovl_path_type(dentry); LIST_HEAD(list); err = ovl_check_empty_dir(dentry, &list); - if (err) + if (err) { ret = ERR_PTR(err); - else { - /* - * If no upperdentry then skip clearing whiteouts. - * - * Can race with copy-up, since we don't hold the upperdir - * mutex. Doesn't matter, since copy-up can't create a - * non-empty directory from an empty one. - */ - if (ovl_dentry_upper(dentry)) - ret = ovl_clear_empty(dentry, &list); + goto out_free; } + /* + * When removing an empty opaque directory, then it makes no sense to + * replace it with an exact replica of itself. + * + * If no upperdentry then skip clearing whiteouts. + * + * Can race with copy-up, since we don't hold the upperdir mutex. + * Doesn't matter, since copy-up can't create a non-empty directory + * from an empty one. + */ + if (OVL_TYPE_UPPER(type) && OVL_TYPE_MERGE(type)) + ret = ovl_clear_empty(dentry, &list); + +out_free: ovl_cache_free(&list); return ret; @@ -347,7 +369,23 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, if (err) goto out_dput2; - if (S_ISDIR(stat->mode)) { + /* + * mode could have been mutilated due to umask (e.g. sgid directory) + */ + if (!hardlink && + !S_ISLNK(stat->mode) && newdentry->d_inode->i_mode != stat->mode) { + struct iattr attr = { + .ia_valid = ATTR_MODE, + .ia_mode = stat->mode, + }; + inode_lock(newdentry->d_inode); + err = notify_change(newdentry, &attr, NULL); + inode_unlock(newdentry->d_inode); + if (err) + goto out_cleanup; + } + + if (!hardlink && S_ISDIR(stat->mode)) { err = ovl_set_opaque(newdentry); if (err) goto out_cleanup; @@ -363,10 +401,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, if (err) goto out_cleanup; } - ovl_dentry_version_inc(dentry->d_parent); - ovl_dentry_update(dentry, newdentry); - ovl_copyattr(newdentry->d_inode, inode); - d_instantiate(dentry, inode); + ovl_instantiate(dentry, inode, newdentry, !!hardlink); newdentry = NULL; out_dput2: dput(upper); @@ -382,52 +417,42 @@ out_cleanup: goto out_dput2; } -static int ovl_create_or_link(struct dentry *dentry, int mode, dev_t rdev, - const char *link, struct dentry *hardlink) +static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, + struct kstat *stat, const char *link, + struct dentry *hardlink) { int err; - struct inode *inode; - struct kstat stat = { - .mode = mode, - .rdev = rdev, - }; - - err = -ENOMEM; - inode = ovl_new_inode(dentry->d_sb, mode, dentry->d_fsdata); - if (!inode) - goto out; + const struct cred *old_cred; + struct cred *override_cred; err = ovl_copy_up(dentry->d_parent); if (err) - goto out_iput; - - if (!ovl_dentry_is_opaque(dentry)) { - err = ovl_create_upper(dentry, inode, &stat, link, hardlink); - } else { - const struct cred *old_cred; - struct cred *override_cred; - - old_cred = ovl_override_creds(dentry->d_sb); - - err = -ENOMEM; - override_cred = prepare_creds(); - if (override_cred) { - override_cred->fsuid = old_cred->fsuid; - override_cred->fsgid = old_cred->fsgid; - put_cred(override_creds(override_cred)); - put_cred(override_cred); + return err; - err = ovl_create_over_whiteout(dentry, inode, &stat, - link, hardlink); - } - revert_creds(old_cred); + old_cred = ovl_override_creds(dentry->d_sb); + err = -ENOMEM; + override_cred = prepare_creds(); + if (override_cred) { + override_cred->fsuid = inode->i_uid; + override_cred->fsgid = inode->i_gid; + put_cred(override_creds(override_cred)); + put_cred(override_cred); + + if (!ovl_dentry_is_opaque(dentry)) + err = ovl_create_upper(dentry, inode, stat, link, + hardlink); + else + err = ovl_create_over_whiteout(dentry, inode, stat, + link, hardlink); } + revert_creds(old_cred); + if (!err) { + struct inode *realinode = d_inode(ovl_dentry_upper(dentry)); - if (!err) - inode = NULL; -out_iput: - iput(inode); -out: + WARN_ON(inode->i_mode != realinode->i_mode); + WARN_ON(!uid_eq(inode->i_uid, realinode->i_uid)); + WARN_ON(!gid_eq(inode->i_gid, realinode->i_gid)); + } return err; } @@ -435,13 +460,30 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, const char *link) { int err; + struct inode *inode; + struct kstat stat = { + .rdev = rdev, + }; err = ovl_want_write(dentry); - if (!err) { - err = ovl_create_or_link(dentry, mode, rdev, link, NULL); - ovl_drop_write(dentry); - } + if (err) + goto out; + + err = -ENOMEM; + inode = ovl_new_inode(dentry->d_sb, mode); + if (!inode) + goto out_drop_write; + + inode_init_owner(inode, dentry->d_parent->d_inode, mode); + stat.mode = inode->i_mode; + err = ovl_create_or_link(dentry, inode, &stat, link, NULL); + if (err) + iput(inode); + +out_drop_write: + ovl_drop_write(dentry); +out: return err; } @@ -476,7 +518,7 @@ static int ovl_link(struct dentry *old, struct inode *newdir, struct dentry *new) { int err; - struct dentry *upper; + struct inode *inode; err = ovl_want_write(old); if (err) @@ -486,8 +528,12 @@ static int ovl_link(struct dentry *old, struct inode *newdir, if (err) goto out_drop_write; - upper = ovl_dentry_upper(old); - err = ovl_create_or_link(new, upper->d_inode->i_mode, 0, NULL, upper); + inode = d_inode(old); + ihold(inode); + + err = ovl_create_or_link(new, inode, NULL, NULL, ovl_dentry_upper(old)); + if (err) + iput(inode); out_drop_write: ovl_drop_write(old); @@ -511,24 +557,10 @@ static int ovl_remove_and_whiteout(struct dentry *dentry, bool is_dir) return -EROFS; if (is_dir) { - if (OVL_TYPE_MERGE_OR_LOWER(ovl_path_type(dentry))) { - opaquedir = ovl_check_empty_and_clear(dentry); - err = PTR_ERR(opaquedir); - if (IS_ERR(opaquedir)) - goto out; - } else { - LIST_HEAD(list); - - /* - * When removing an empty opaque directory, then it - * makes no sense to replace it with an exact replica of - * itself. But emptiness still needs to be checked. - */ - err = ovl_check_empty_dir(dentry, &list); - ovl_cache_free(&list); - if (err) - goto out; - } + opaquedir = ovl_check_empty_and_clear(dentry); + err = PTR_ERR(opaquedir); + if (IS_ERR(opaquedir)) + goto out; } err = ovl_lock_rename_workdir(workdir, upperdir); @@ -633,6 +665,8 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir) { enum ovl_path_type type; int err; + const struct cred *old_cred; + err = ovl_check_sticky(dentry); if (err) @@ -647,14 +681,18 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir) goto out_drop_write; type = ovl_path_type(dentry); - if (OVL_TYPE_PURE_UPPER(type)) { - err = ovl_remove_upper(dentry, is_dir); - } else { - const struct cred *old_cred = ovl_override_creds(dentry->d_sb); + old_cred = ovl_override_creds(dentry->d_sb); + if (OVL_TYPE_PURE_UPPER(type)) + err = ovl_remove_upper(dentry, is_dir); + else err = ovl_remove_and_whiteout(dentry, is_dir); - - revert_creds(old_cred); + revert_creds(old_cred); + if (!err) { + if (is_dir) + clear_nlink(dentry->d_inode); + else + drop_nlink(dentry->d_inode); } out_drop_write: ovl_drop_write(dentry); @@ -760,8 +798,7 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, old_opaque = !OVL_TYPE_PURE_UPPER(old_type); new_opaque = !OVL_TYPE_PURE_UPPER(new_type); - if (old_opaque || new_opaque) - old_cred = ovl_override_creds(old->d_sb); + old_cred = ovl_override_creds(old->d_sb); if (overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) { opaquedir = ovl_check_empty_and_clear(new); @@ -891,8 +928,7 @@ out_dput_old: out_unlock: unlock_rename(new_upperdir, old_upperdir); out_revert_creds: - if (old_opaque || new_opaque) - revert_creds(old_cred); + revert_creds(old_cred); out_drop_write: ovl_drop_write(old); out: @@ -913,8 +949,10 @@ const struct inode_operations ovl_dir_inode_operations = { .mknod = ovl_mknod, .permission = ovl_permission, .getattr = ovl_dir_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, }; 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; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 0d3f2ad45708..e4f5c9536bfe 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -23,9 +23,11 @@ enum ovl_path_type { #define OVL_TYPE_MERGE_OR_LOWER(type) \ (OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type)) -#define OVL_XATTR_PRE_NAME "trusted.overlay." -#define OVL_XATTR_PRE_LEN 16 -#define OVL_XATTR_OPAQUE OVL_XATTR_PRE_NAME"opaque" + +#define OVL_XATTR_PREFIX XATTR_TRUSTED_PREFIX "overlay" +#define OVL_XATTR_OPAQUE OVL_XATTR_PREFIX ".opaque" + +#define OVL_ISUPPER_MASK 1UL static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry) { @@ -131,6 +133,16 @@ static inline int ovl_do_whiteout(struct inode *dir, struct dentry *dentry) return err; } +static inline struct inode *ovl_inode_real(struct inode *inode, bool *is_upper) +{ + unsigned long x = (unsigned long) READ_ONCE(inode->i_private); + + if (is_upper) + *is_upper = x & OVL_ISUPPER_MASK; + + return (struct inode *) (x & ~OVL_ISUPPER_MASK); +} + enum ovl_path_type ovl_path_type(struct dentry *dentry); u64 ovl_dentry_version_get(struct dentry *dentry); void ovl_dentry_version_inc(struct dentry *dentry); @@ -141,11 +153,9 @@ int ovl_path_next(int idx, struct dentry *dentry, struct path *path); struct dentry *ovl_dentry_upper(struct dentry *dentry); struct dentry *ovl_dentry_lower(struct dentry *dentry); struct dentry *ovl_dentry_real(struct dentry *dentry); -struct dentry *ovl_entry_real(struct ovl_entry *oe, bool *is_upper); struct vfsmount *ovl_entry_mnt_real(struct ovl_entry *oe, struct inode *inode, bool is_upper); struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry); -bool ovl_is_default_permissions(struct inode *inode); void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); struct dentry *ovl_workdir(struct dentry *dentry); int ovl_want_write(struct dentry *dentry); @@ -155,6 +165,7 @@ void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque); bool ovl_is_whiteout(struct dentry *dentry); const struct cred *ovl_override_creds(struct super_block *sb); void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry); +void ovl_inode_update(struct inode *inode, struct inode *upperinode); struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); struct file *ovl_path_open(struct path *path, int flags); @@ -179,15 +190,20 @@ ssize_t ovl_getxattr(struct dentry *dentry, struct inode *inode, const char *name, void *value, size_t size); ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size); int ovl_removexattr(struct dentry *dentry, const char *name); +struct posix_acl *ovl_get_acl(struct inode *inode, int type); int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags); +int ovl_update_time(struct inode *inode, struct timespec *ts, int flags); -struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, - struct ovl_entry *oe); +struct inode *ovl_new_inode(struct super_block *sb, umode_t mode); +struct inode *ovl_get_inode(struct super_block *sb, struct inode *realinode); static inline void ovl_copyattr(struct inode *from, struct inode *to) { to->i_uid = from->i_uid; to->i_gid = from->i_gid; to->i_mode = from->i_mode; + to->i_atime = from->i_atime; + to->i_mtime = from->i_mtime; + to->i_ctime = from->i_ctime; } /* dir.c */ diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 5e254b3a8c56..4036132842b5 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -16,10 +16,10 @@ #include <linux/slab.h> #include <linux/parser.h> #include <linux/module.h> -#include <linux/pagemap.h> #include <linux/sched.h> #include <linux/statfs.h> #include <linux/seq_file.h> +#include <linux/posix_acl_xattr.h> #include "overlayfs.h" MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>"); @@ -145,18 +145,11 @@ struct dentry *ovl_dentry_real(struct dentry *dentry) return realdentry; } -struct dentry *ovl_entry_real(struct ovl_entry *oe, bool *is_upper) +static void ovl_inode_init(struct inode *inode, struct inode *realinode, + bool is_upper) { - struct dentry *realdentry; - - realdentry = ovl_upperdentry_dereference(oe); - if (realdentry) { - *is_upper = true; - } else { - realdentry = __ovl_dentry_lower(oe); - *is_upper = false; - } - return realdentry; + WRITE_ONCE(inode->i_private, (unsigned long) realinode | + (is_upper ? OVL_ISUPPER_MASK : 0)); } struct vfsmount *ovl_entry_mnt_real(struct ovl_entry *oe, struct inode *inode, @@ -178,13 +171,6 @@ struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry) return oe->cache; } -bool ovl_is_default_permissions(struct inode *inode) -{ - struct ovl_fs *ofs = inode->i_sb->s_fs_info; - - return ofs->config.default_permissions; -} - void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache) { struct ovl_entry *oe = dentry->d_fsdata; @@ -235,7 +221,6 @@ void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) WARN_ON(!inode_is_locked(upperdentry->d_parent->d_inode)); WARN_ON(oe->__upperdentry); - BUG_ON(!upperdentry->d_inode); /* * Make sure upperdentry is consistent before making it visible to * ovl_upperdentry_dereference(). @@ -244,6 +229,16 @@ void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) oe->__upperdentry = upperdentry; } +void ovl_inode_update(struct inode *inode, struct inode *upperinode) +{ + WARN_ON(!upperinode); + WARN_ON(!inode_unhashed(inode)); + WRITE_ONCE(inode->i_private, + (unsigned long) upperinode | OVL_ISUPPER_MASK); + if (!S_ISDIR(upperinode->i_mode)) + __insert_inode_hash(inode, (unsigned long) upperinode); +} + void ovl_dentry_version_inc(struct dentry *dentry) { struct ovl_entry *oe = dentry->d_fsdata; @@ -340,7 +335,7 @@ static struct dentry *ovl_d_real(struct dentry *dentry, /* Handle recursion */ return d_real(real, inode, open_flags); bug: - WARN(1, "ovl_d_real(%pd4, %s:%lu\n): real dentry not found\n", dentry, + WARN(1, "ovl_d_real(%pd4, %s:%lu): real dentry not found\n", dentry, inode ? inode->i_sb->s_id : "NULL", inode ? inode->i_ino : 0); return dentry; } @@ -412,7 +407,8 @@ static struct ovl_entry *ovl_alloc_entry(unsigned int numlower) static bool ovl_dentry_remote(struct dentry *dentry) { return dentry->d_flags & - (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE); + (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE | + DCACHE_OP_REAL); } static bool ovl_dentry_weird(struct dentry *dentry) @@ -423,12 +419,16 @@ static bool ovl_dentry_weird(struct dentry *dentry) DCACHE_OP_COMPARE); } -static inline struct dentry *ovl_lookup_real(struct dentry *dir, - struct qstr *name) +static inline struct dentry *ovl_lookup_real(struct super_block *ovl_sb, + struct dentry *dir, + const struct qstr *name) { + const struct cred *old_cred; struct dentry *dentry; - dentry = lookup_hash(name, dir); + old_cred = ovl_override_creds(ovl_sb); + dentry = lookup_one_len_unlocked(name->name, dir, name->len); + revert_creds(old_cred); if (IS_ERR(dentry)) { if (PTR_ERR(dentry) == -ENOENT) @@ -481,7 +481,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, upperdir = ovl_upperdentry_dereference(poe); if (upperdir) { - this = ovl_lookup_real(upperdir, &dentry->d_name); + this = ovl_lookup_real(dentry->d_sb, upperdir, &dentry->d_name); err = PTR_ERR(this); if (IS_ERR(this)) goto out; @@ -514,7 +514,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, bool opaque = false; struct path lowerpath = poe->lowerstack[i]; - this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name); + this = ovl_lookup_real(dentry->d_sb, + lowerpath.dentry, &dentry->d_name); err = PTR_ERR(this); if (IS_ERR(this)) { /* @@ -569,12 +570,19 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (upperdentry || ctr) { struct dentry *realdentry; + struct inode *realinode; realdentry = upperdentry ? upperdentry : stack[0].dentry; + realinode = d_inode(realdentry); err = -ENOMEM; - inode = ovl_new_inode(dentry->d_sb, realdentry->d_inode->i_mode, - oe); + if (upperdentry && !d_is_dir(upperdentry)) { + inode = ovl_get_inode(dentry->d_sb, realinode); + } else { + inode = ovl_new_inode(dentry->d_sb, realinode->i_mode); + if (inode) + ovl_inode_init(inode, realinode, !!upperdentry); + } if (!inode) goto out_free_oe; ovl_copyattr(realdentry->d_inode, inode); @@ -603,7 +611,7 @@ out: struct file *ovl_path_open(struct path *path, int flags) { - return dentry_open(path, flags, current_cred()); + return dentry_open(path, flags | O_NOATIME, current_cred()); } static void ovl_put_super(struct super_block *sb) @@ -686,6 +694,7 @@ static const struct super_operations ovl_super_operations = { .statfs = ovl_statfs, .show_options = ovl_show_options, .remount_fs = ovl_remount, + .drop_inode = generic_delete_inode, }; enum { @@ -958,11 +967,102 @@ static unsigned int ovl_split_lowerdirs(char *str) return ctr; } +static int ovl_posix_acl_xattr_set(const struct xattr_handler *handler, + struct dentry *dentry, struct inode *inode, + const char *name, const void *value, + size_t size, int flags) +{ + struct dentry *workdir = ovl_workdir(dentry); + struct inode *realinode = ovl_inode_real(inode, NULL); + struct posix_acl *acl = NULL; + int err; + + /* Check that everything is OK before copy-up */ + if (value) { + acl = posix_acl_from_xattr(&init_user_ns, value, size); + if (IS_ERR(acl)) + return PTR_ERR(acl); + } + err = -EOPNOTSUPP; + if (!IS_POSIXACL(d_inode(workdir))) + goto out_acl_release; + if (!realinode->i_op->set_acl) + goto out_acl_release; + if (handler->flags == ACL_TYPE_DEFAULT && !S_ISDIR(inode->i_mode)) { + err = acl ? -EACCES : 0; + goto out_acl_release; + } + err = -EPERM; + if (!inode_owner_or_capable(inode)) + goto out_acl_release; + + posix_acl_release(acl); + + return ovl_setxattr(dentry, inode, handler->name, value, size, flags); + +out_acl_release: + posix_acl_release(acl); + return err; +} + +static int ovl_other_xattr_set(const struct xattr_handler *handler, + struct dentry *dentry, struct inode *inode, + const char *name, const void *value, + size_t size, int flags) +{ + return ovl_setxattr(dentry, inode, name, value, size, flags); +} + +static int ovl_own_xattr_set(const struct xattr_handler *handler, + struct dentry *dentry, struct inode *inode, + const char *name, const void *value, + size_t size, int flags) +{ + return -EPERM; +} + +static const struct xattr_handler ovl_posix_acl_access_xattr_handler = { + .name = XATTR_NAME_POSIX_ACL_ACCESS, + .flags = ACL_TYPE_ACCESS, + .set = ovl_posix_acl_xattr_set, +}; + +static const struct xattr_handler ovl_posix_acl_default_xattr_handler = { + .name = XATTR_NAME_POSIX_ACL_DEFAULT, + .flags = ACL_TYPE_DEFAULT, + .set = ovl_posix_acl_xattr_set, +}; + +static const struct xattr_handler ovl_own_xattr_handler = { + .prefix = OVL_XATTR_PREFIX, + .set = ovl_own_xattr_set, +}; + +static const struct xattr_handler ovl_other_xattr_handler = { + .prefix = "", /* catch all */ + .set = ovl_other_xattr_set, +}; + +static const struct xattr_handler *ovl_xattr_handlers[] = { + &ovl_posix_acl_access_xattr_handler, + &ovl_posix_acl_default_xattr_handler, + &ovl_own_xattr_handler, + &ovl_other_xattr_handler, + NULL +}; + +static const struct xattr_handler *ovl_xattr_noacl_handlers[] = { + &ovl_own_xattr_handler, + &ovl_other_xattr_handler, + NULL, +}; + static int ovl_fill_super(struct super_block *sb, void *data, int silent) { struct path upperpath = { NULL, NULL }; struct path workpath = { NULL, NULL }; struct dentry *root_dentry; + struct inode *realinode; struct ovl_entry *oe; struct ovl_fs *ufs; struct path *stack = NULL; @@ -1069,6 +1169,10 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) pr_err("overlayfs: failed to clone upperpath\n"); goto out_put_lowerpath; } + /* Don't inherit atime flags */ + ufs->upper_mnt->mnt_flags &= ~(MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME); + + sb->s_time_gran = ufs->upper_mnt->mnt_sb->s_time_gran; ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry); err = PTR_ERR(ufs->workdir); @@ -1116,7 +1220,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) * Make lower_mnt R/O. That way fchmod/fchown on lower file * will fail instead of modifying lower fs. */ - mnt->mnt_flags |= MNT_READONLY; + mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME; ufs->lower_mnt[ufs->numlower] = mnt; ufs->numlower++; @@ -1140,7 +1244,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (!oe) goto out_put_cred; - root_dentry = d_make_root(ovl_new_inode(sb, S_IFDIR, oe)); + root_dentry = d_make_root(ovl_new_inode(sb, S_IFDIR)); if (!root_dentry) goto out_free_oe; @@ -1159,13 +1263,19 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) root_dentry->d_fsdata = oe; - ovl_copyattr(ovl_dentry_real(root_dentry)->d_inode, - root_dentry->d_inode); + realinode = d_inode(ovl_dentry_real(root_dentry)); + ovl_inode_init(d_inode(root_dentry), realinode, !!upperpath.dentry); + ovl_copyattr(realinode, d_inode(root_dentry)); sb->s_magic = OVERLAYFS_SUPER_MAGIC; sb->s_op = &ovl_super_operations; + if (IS_ENABLED(CONFIG_FS_POSIX_ACL)) + sb->s_xattr = ovl_xattr_handlers; + else + sb->s_xattr = ovl_xattr_noacl_handlers; sb->s_root = root_dentry; sb->s_fs_info = ufs; + sb->s_flags |= MS_POSIXACL; return 0; |