diff options
Diffstat (limited to 'fs/overlayfs/file.c')
| -rw-r--r-- | fs/overlayfs/file.c | 178 | 
1 files changed, 148 insertions, 30 deletions
diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index 84dd957efa24..e235a635d9ec 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -1,9 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only  /*   * Copyright (C) 2017 Red Hat, 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 as published by - * the Free Software Foundation.   */  #include <linux/cred.h> @@ -11,6 +8,7 @@  #include <linux/mount.h>  #include <linux/xattr.h>  #include <linux/uio.h> +#include <linux/uaccess.h>  #include "overlayfs.h"  static char ovl_whatisit(struct inode *inode, struct inode *realinode) @@ -29,10 +27,11 @@ static struct file *ovl_open_realfile(const struct file *file,  	struct inode *inode = file_inode(file);  	struct file *realfile;  	const struct cred *old_cred; +	int flags = file->f_flags | O_NOATIME | FMODE_NONOTIFY;  	old_cred = ovl_override_creds(inode->i_sb); -	realfile = open_with_fake_path(&file->f_path, file->f_flags | O_NOATIME, -				       realinode, current_cred()); +	realfile = open_with_fake_path(&file->f_path, flags, realinode, +				       current_cred());  	revert_creds(old_cred);  	pr_debug("open(%p[%pD2/%c], 0%o) -> (%p, 0%o)\n", @@ -50,7 +49,7 @@ static int ovl_change_flags(struct file *file, unsigned int flags)  	int err;  	/* No atime modificaton on underlying */ -	flags |= O_NOATIME; +	flags |= O_NOATIME | FMODE_NONOTIFY;  	/* If some flag changed that cannot be changed then something's amiss */  	if (WARN_ON((file->f_flags ^ flags) & ~OVL_SETFL_MASK)) @@ -116,11 +115,10 @@ static int ovl_real_fdget(const struct file *file, struct fd *real)  static int ovl_open(struct inode *inode, struct file *file)  { -	struct dentry *dentry = file_dentry(file);  	struct file *realfile;  	int err; -	err = ovl_open_maybe_copy_up(dentry, file->f_flags); +	err = ovl_maybe_copy_up(file_dentry(file), file->f_flags);  	if (err)  		return err; @@ -145,11 +143,47 @@ static int ovl_release(struct inode *inode, struct file *file)  static loff_t ovl_llseek(struct file *file, loff_t offset, int whence)  { -	struct inode *realinode = ovl_inode_real(file_inode(file)); +	struct inode *inode = file_inode(file); +	struct fd real; +	const struct cred *old_cred; +	ssize_t ret; + +	/* +	 * The two special cases below do not need to involve real fs, +	 * so we can optimizing concurrent callers. +	 */ +	if (offset == 0) { +		if (whence == SEEK_CUR) +			return file->f_pos; + +		if (whence == SEEK_SET) +			return vfs_setpos(file, 0, 0); +	} + +	ret = ovl_real_fdget(file, &real); +	if (ret) +		return ret; + +	/* +	 * Overlay file f_pos is the master copy that is preserved +	 * through copy up and modified on read/write, but only real +	 * fs knows how to SEEK_HOLE/SEEK_DATA and real fs may impose +	 * limitations that are more strict than ->s_maxbytes for specific +	 * files, so we use the real file to perform seeks. +	 */ +	inode_lock(inode); +	real.file->f_pos = file->f_pos; -	return generic_file_llseek_size(file, offset, whence, -					realinode->i_sb->s_maxbytes, -					i_size_read(realinode)); +	old_cred = ovl_override_creds(inode->i_sb); +	ret = vfs_llseek(real.file, offset, whence); +	revert_creds(old_cred); + +	file->f_pos = real.file->f_pos; +	inode_unlock(inode); + +	fdput(real); + +	return ret;  }  static void ovl_file_accessed(struct file *file) @@ -372,34 +406,118 @@ static long ovl_real_ioctl(struct file *file, unsigned int cmd,  	return ret;  } -static long ovl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +static long ovl_ioctl_set_flags(struct file *file, unsigned int cmd, +				unsigned long arg, unsigned int iflags)  {  	long ret;  	struct inode *inode = file_inode(file); +	unsigned int old_iflags; + +	if (!inode_owner_or_capable(inode)) +		return -EACCES; + +	ret = mnt_want_write_file(file); +	if (ret) +		return ret; + +	inode_lock(inode); + +	/* Check the capability before cred override */ +	ret = -EPERM; +	old_iflags = READ_ONCE(inode->i_flags); +	if (((iflags ^ old_iflags) & (S_APPEND | S_IMMUTABLE)) && +	    !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, cmd, arg); + +	ovl_copyflags(ovl_inode_real(inode), inode); +unlock: +	inode_unlock(inode); + +	mnt_drop_write_file(file); + +	return ret; + +} + +static unsigned int ovl_fsflags_to_iflags(unsigned int flags) +{ +	unsigned int iflags = 0; + +	if (flags & FS_SYNC_FL) +		iflags |= S_SYNC; +	if (flags & FS_APPEND_FL) +		iflags |= S_APPEND; +	if (flags & FS_IMMUTABLE_FL) +		iflags |= S_IMMUTABLE; +	if (flags & FS_NOATIME_FL) +		iflags |= S_NOATIME; + +	return iflags; +} + +static long ovl_ioctl_set_fsflags(struct file *file, unsigned int cmd, +				  unsigned long arg) +{ +	unsigned int flags; + +	if (get_user(flags, (int __user *) arg)) +		return -EFAULT; + +	return ovl_ioctl_set_flags(file, cmd, arg, +				   ovl_fsflags_to_iflags(flags)); +} + +static unsigned int ovl_fsxflags_to_iflags(unsigned int xflags) +{ +	unsigned int iflags = 0; + +	if (xflags & FS_XFLAG_SYNC) +		iflags |= S_SYNC; +	if (xflags & FS_XFLAG_APPEND) +		iflags |= S_APPEND; +	if (xflags & FS_XFLAG_IMMUTABLE) +		iflags |= S_IMMUTABLE; +	if (xflags & FS_XFLAG_NOATIME) +		iflags |= S_NOATIME; + +	return iflags; +} + +static long ovl_ioctl_set_fsxflags(struct file *file, unsigned int cmd, +				   unsigned long arg) +{ +	struct fsxattr fa; + +	memset(&fa, 0, sizeof(fa)); +	if (copy_from_user(&fa, (void __user *) arg, sizeof(fa))) +		return -EFAULT; + +	return ovl_ioctl_set_flags(file, cmd, arg, +				   ovl_fsxflags_to_iflags(fa.fsx_xflags)); +} + +static long ovl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ +	long ret;  	switch (cmd) {  	case FS_IOC_GETFLAGS: +	case FS_IOC_FSGETXATTR:  		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); -		} +		ret = ovl_ioctl_set_fsflags(file, cmd, arg); +		break; -		mnt_drop_write_file(file); +	case FS_IOC_FSSETXATTR: +		ret = ovl_ioctl_set_fsxflags(file, cmd, arg);  		break;  	default:  |