static int __maybe_unused 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); 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); /* * Check if sgid bit needs to be cleared (actual setacl operation will * be done with mounter's capabilities and so that won't do it for us). */ if (unlikely(inode->i_mode & S_ISGID) && handler->flags == ACL_TYPE_ACCESS && !in_group_p(inode->i_gid) && !capable_wrt_inode_uidgid(inode, CAP_FSETID)) { struct iattr iattr = { .ia_valid = ATTR_KILL_SGID }; err = ovl_setattr(dentry, &iattr); if (err) return err; } err = ovl_xattr_set(dentry, inode, handler->name, value, size, flags); if (!err) ovl_copyattr(ovl_inode_real(inode), inode); return err; out_acl_release: posix_acl_release(acl); return err; }
static int ovl_real_fdget_meta(const struct file *file, struct fd *real, bool allow_meta) { struct inode *inode = file_inode(file); struct inode *realinode; real->flags = 0; real->file = file->private_data; if (allow_meta) realinode = ovl_inode_real(inode); else realinode = ovl_inode_realdata(inode); /* Has it been copied up since we'd opened it? */ if (unlikely(file_inode(real->file) != realinode)) { real->flags = FDPUT_FPUT; real->file = ovl_open_realfile(file, realinode); return PTR_ERR_OR_ZERO(real->file); } /* Did the flags change since open? */ if (unlikely((file->f_flags ^ real->file->f_flags) & ~O_NOATIME)) return ovl_change_flags(real->file, file->f_flags); return 0; }
int ovl_permission(struct inode *inode, int mask) { bool is_upper; struct inode *realinode = ovl_inode_real(inode, &is_upper); const struct cred *old_cred; int err; /* Careful in RCU walk mode */ if (!realinode) { WARN_ON(!(mask & MAY_NOT_BLOCK)); return -ECHILD; } /* * 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); return err; }
static loff_t ovl_llseek(struct file *file, loff_t offset, int whence) { struct inode *realinode = ovl_inode_real(file_inode(file)); return generic_file_llseek_size(file, offset, whence, realinode->i_sb->s_maxbytes, i_size_read(realinode)); }
static ssize_t ovl_write_iter(struct kiocb *iocb, struct iov_iter *iter) { struct file *file = iocb->ki_filp; struct inode *inode = file_inode(file); struct fd real; const struct cred *old_cred; ssize_t ret; if (!iov_iter_count(iter)) return 0; inode_lock(inode); /* Update mode */ ovl_copyattr(ovl_inode_real(inode), inode); ret = file_remove_privs(file); if (ret) goto out_unlock; ret = ovl_real_fdget(file, &real); if (ret) goto out_unlock; old_cred = ovl_override_creds(file_inode(file)->i_sb); file_start_write(real.file); ret = vfs_iter_write(real.file, iter, &iocb->ki_pos, ovl_iocb_to_rwf(iocb)); file_end_write(real.file); revert_creds(old_cred); /* Update size */ ovl_copyattr(ovl_inode_real(inode), inode); fdput(real); out_unlock: inode_unlock(inode); return ret; }
/* 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); }
struct posix_acl *ovl_get_acl(struct inode *inode, int type) { struct inode *realinode = ovl_inode_real(inode); const struct cred *old_cred; struct posix_acl *acl; if (!IS_ENABLED(CONFIG_FS_POSIX_ACL) || !IS_POSIXACL(realinode)) return NULL; old_cred = ovl_override_creds(inode->i_sb); acl = get_acl(realinode, type); revert_creds(old_cred); return acl; }
static loff_t ovl_copyfile(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t len, unsigned int flags, enum ovl_copyop op) { struct inode *inode_out = file_inode(file_out); struct fd real_in, real_out; const struct cred *old_cred; loff_t ret; ret = ovl_real_fdget(file_out, &real_out); if (ret) return ret; ret = ovl_real_fdget(file_in, &real_in); if (ret) { fdput(real_out); return ret; } old_cred = ovl_override_creds(file_inode(file_out)->i_sb); switch (op) { case OVL_COPY: ret = vfs_copy_file_range(real_in.file, pos_in, real_out.file, pos_out, len, flags); break; case OVL_CLONE: ret = vfs_clone_file_range(real_in.file, pos_in, real_out.file, pos_out, len, flags); break; case OVL_DEDUPE: ret = vfs_dedupe_file_range_one(real_in.file, pos_in, real_out.file, pos_out, len, flags); break; } revert_creds(old_cred); /* Update size */ ovl_copyattr(ovl_inode_real(inode_out), inode_out); fdput(real_in); fdput(real_out); return ret; }
/* * Common operations required to be done after creation of file on upper. * If @hardlink is false, then @inode is a pre-allocated inode, we may or * may not use to instantiate the new dentry. */ static int ovl_instantiate(struct dentry *dentry, struct inode *inode, struct dentry *newdentry, bool hardlink) { struct ovl_inode_params oip = { .upperdentry = newdentry, .newinode = inode, }; ovl_dir_modified(dentry->d_parent, false); ovl_dentry_set_upper_alias(dentry); if (!hardlink) { /* * ovl_obtain_alias() can be called after ovl_create_real() * and before we get here, so we may get an inode from cache * with the same real upperdentry that is not the inode we * pre-allocated. In this case we will use the cached inode * to instantiate the new dentry. * * XXX: if we ever use ovl_obtain_alias() to decode directory * file handles, need to use ovl_get_inode_locked() and * d_instantiate_new() here to prevent from creating two * hashed directory inode aliases. */ inode = ovl_get_inode(dentry->d_sb, &oip); if (WARN_ON(IS_ERR(inode))) return PTR_ERR(inode); } else { WARN_ON(ovl_inode_real(inode) != d_inode(newdentry)); dput(newdentry); inc_nlink(inode); } d_instantiate(dentry, inode); if (inode != oip.newinode) { pr_warn_ratelimited("overlayfs: newly created inode found in cache (%pd2)\n", dentry); } /* Force lookup of new upper hardlink to find its lower */ if (hardlink) d_drop(dentry); return 0; }
static long ovl_ioctl_set_flags(struct file *file, unsigned long arg) { long ret; struct inode *inode = file_inode(file); unsigned int flags; unsigned int old_flags; if (!inode_owner_or_capable(inode)) return -EACCES; if (get_user(flags, (int __user *) arg)) return -EFAULT; ret = mnt_want_write_file(file); if (ret) return ret; inode_lock(inode); /* Check the capability before cred override */ ret = -EPERM; old_flags = ovl_get_inode_flags(inode); if (((flags ^ old_flags) & (FS_APPEND_FL | FS_IMMUTABLE_FL)) && !capable(CAP_LINUX_IMMUTABLE)) goto unlock; ret = ovl_maybe_copy_up(file_dentry(file), O_WRONLY); if (ret) goto unlock; ret = ovl_real_ioctl(file, FS_IOC_SETFLAGS, arg); ovl_copyflags(ovl_inode_real(inode), inode); unlock: inode_unlock(inode); mnt_drop_write_file(file); return ret; }
static long ovl_fallocate(struct file *file, int mode, loff_t offset, loff_t len) { struct inode *inode = file_inode(file); struct fd real; const struct cred *old_cred; int ret; ret = ovl_real_fdget(file, &real); if (ret) return ret; old_cred = ovl_override_creds(file_inode(file)->i_sb); ret = vfs_fallocate(real.file, mode, offset, len); revert_creds(old_cred); /* Update size */ ovl_copyattr(ovl_inode_real(inode), inode); fdput(real); return ret; }
static long ovl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { long ret; struct inode *inode = file_inode(file); switch (cmd) { case FS_IOC_GETFLAGS: ret = ovl_real_ioctl(file, cmd, arg); break; case FS_IOC_SETFLAGS: if (!inode_owner_or_capable(inode)) return -EACCES; ret = mnt_want_write_file(file); if (ret) return ret; ret = ovl_copy_up_with_data(file_dentry(file)); if (!ret) { ret = ovl_real_ioctl(file, cmd, arg); inode_lock(inode); ovl_copyflags(ovl_inode_real(inode), inode); inode_unlock(inode); } mnt_drop_write_file(file); break; default: ret = -ENOTTY; } return ret; }
static int ovl_inode_test(struct inode *inode, void *data) { return ovl_inode_real(inode, NULL) == data; }