2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2018-07-18 16:44:41 +03:00
/*
* Copyright ( C ) 2017 Red Hat , Inc .
*/
# include <linux/cred.h>
# include <linux/file.h>
2018-07-18 16:44:42 +03:00
# include <linux/mount.h>
2018-07-18 16:44:41 +03:00
# include <linux/xattr.h>
2018-07-18 16:44:41 +03:00
# include <linux/uio.h>
2019-05-06 10:41:02 +03:00
# include <linux/uaccess.h>
2018-07-18 16:44:41 +03:00
# include "overlayfs.h"
2018-05-11 18:49:31 +03:00
static char ovl_whatisit ( struct inode * inode , struct inode * realinode )
{
if ( realinode ! = ovl_inode_upper ( inode ) )
return ' l ' ;
if ( ovl_has_upperdata ( inode ) )
return ' u ' ;
else
return ' m ' ;
}
static struct file * ovl_open_realfile ( const struct file * file ,
struct inode * realinode )
2018-07-18 16:44:41 +03:00
{
struct inode * inode = file_inode ( file ) ;
struct file * realfile ;
const struct cred * old_cred ;
2019-04-24 19:39:50 +03:00
int flags = file - > f_flags | O_NOATIME | FMODE_NONOTIFY ;
2018-07-18 16:44:41 +03:00
old_cred = ovl_override_creds ( inode - > i_sb ) ;
2019-04-24 19:39:50 +03:00
realfile = open_with_fake_path ( & file - > f_path , flags , realinode ,
current_cred ( ) ) ;
2018-07-18 16:44:41 +03:00
revert_creds ( old_cred ) ;
pr_debug ( " open(%p[%pD2/%c], 0%o) -> (%p, 0%o) \n " ,
2018-05-11 18:49:31 +03:00
file , file , ovl_whatisit ( inode , realinode ) , file - > f_flags ,
2018-07-18 16:44:41 +03:00
realfile , IS_ERR ( realfile ) ? 0 : realfile - > f_flags ) ;
return realfile ;
}
2018-07-18 16:44:41 +03:00
# define OVL_SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | O_DIRECT)
static int ovl_change_flags ( struct file * file , unsigned int flags )
{
struct inode * inode = file_inode ( file ) ;
int err ;
/* No atime modificaton on underlying */
2019-04-24 19:39:50 +03:00
flags | = O_NOATIME | FMODE_NONOTIFY ;
2018-07-18 16:44:41 +03:00
/* If some flag changed that cannot be changed then something's amiss */
if ( WARN_ON ( ( file - > f_flags ^ flags ) & ~ OVL_SETFL_MASK ) )
return - EIO ;
flags & = OVL_SETFL_MASK ;
if ( ( ( flags ^ file - > f_flags ) & O_APPEND ) & & IS_APPEND ( inode ) )
return - EPERM ;
if ( flags & O_DIRECT ) {
if ( ! file - > f_mapping - > a_ops | |
! file - > f_mapping - > a_ops - > direct_IO )
return - EINVAL ;
}
if ( file - > f_op - > check_flags ) {
err = file - > f_op - > check_flags ( flags ) ;
if ( err )
return err ;
}
spin_lock ( & file - > f_lock ) ;
file - > f_flags = ( file - > f_flags & ~ OVL_SETFL_MASK ) | flags ;
spin_unlock ( & file - > f_lock ) ;
return 0 ;
}
2018-05-11 18:49:31 +03:00
static int ovl_real_fdget_meta ( const struct file * file , struct fd * real ,
bool allow_meta )
2018-07-18 16:44:41 +03:00
{
struct inode * inode = file_inode ( file ) ;
2018-05-11 18:49:31 +03:00
struct inode * realinode ;
2018-07-18 16:44:41 +03:00
real - > flags = 0 ;
real - > file = file - > private_data ;
2018-05-11 18:49:31 +03:00
if ( allow_meta )
realinode = ovl_inode_real ( inode ) ;
else
realinode = ovl_inode_realdata ( inode ) ;
2018-07-18 16:44:41 +03:00
/* Has it been copied up since we'd opened it? */
2018-05-11 18:49:31 +03:00
if ( unlikely ( file_inode ( real - > file ) ! = realinode ) ) {
2018-07-18 16:44:41 +03:00
real - > flags = FDPUT_FPUT ;
2018-05-11 18:49:31 +03:00
real - > file = ovl_open_realfile ( file , realinode ) ;
2018-07-18 16:44:41 +03:00
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 ;
}
2018-05-11 18:49:31 +03:00
static int ovl_real_fdget ( const struct file * file , struct fd * real )
{
return ovl_real_fdget_meta ( file , real , false ) ;
}
2018-07-18 16:44:41 +03:00
static int ovl_open ( struct inode * inode , struct file * file )
{
struct file * realfile ;
int err ;
2019-01-22 08:01:39 +03:00
err = ovl_maybe_copy_up ( file_dentry ( file ) , file - > f_flags ) ;
2018-07-18 16:44:41 +03:00
if ( err )
return err ;
/* No longer need these flags, so don't pass them on to underlying fs */
file - > f_flags & = ~ ( O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC ) ;
2018-05-11 18:49:31 +03:00
realfile = ovl_open_realfile ( file , ovl_inode_realdata ( inode ) ) ;
2018-07-18 16:44:41 +03:00
if ( IS_ERR ( realfile ) )
return PTR_ERR ( realfile ) ;
file - > private_data = realfile ;
return 0 ;
}
static int ovl_release ( struct inode * inode , struct file * file )
{
fput ( file - > private_data ) ;
return 0 ;
}
static loff_t ovl_llseek ( struct file * file , loff_t offset , int whence )
{
2019-02-27 14:32:11 +03:00
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 .
*/
2019-12-21 12:42:29 +03:00
ovl_inode_lock ( inode ) ;
2019-02-27 14:32:11 +03:00
real . file - > f_pos = file - > f_pos ;
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 ;
2019-12-21 12:42:29 +03:00
ovl_inode_unlock ( inode ) ;
2019-02-27 14:32:11 +03:00
fdput ( real ) ;
2018-07-18 16:44:41 +03:00
2019-02-27 14:32:11 +03:00
return ret ;
2018-07-18 16:44:41 +03:00
}
2018-07-18 16:44:41 +03:00
static void ovl_file_accessed ( struct file * file )
{
struct inode * inode , * upperinode ;
if ( file - > f_flags & O_NOATIME )
return ;
inode = file_inode ( file ) ;
upperinode = ovl_inode_upper ( inode ) ;
if ( ! upperinode )
return ;
if ( ( ! timespec64_equal ( & inode - > i_mtime , & upperinode - > i_mtime ) | |
! timespec64_equal ( & inode - > i_ctime , & upperinode - > i_ctime ) ) ) {
inode - > i_mtime = upperinode - > i_mtime ;
inode - > i_ctime = upperinode - > i_ctime ;
}
touch_atime ( & file - > f_path ) ;
}
static rwf_t ovl_iocb_to_rwf ( struct kiocb * iocb )
{
int ifl = iocb - > ki_flags ;
rwf_t flags = 0 ;
if ( ifl & IOCB_NOWAIT )
flags | = RWF_NOWAIT ;
if ( ifl & IOCB_HIPRI )
flags | = RWF_HIPRI ;
if ( ifl & IOCB_DSYNC )
flags | = RWF_DSYNC ;
if ( ifl & IOCB_SYNC )
flags | = RWF_SYNC ;
return flags ;
}
static ssize_t ovl_read_iter ( struct kiocb * iocb , struct iov_iter * iter )
{
struct file * file = iocb - > ki_filp ;
struct fd real ;
const struct cred * old_cred ;
ssize_t ret ;
if ( ! iov_iter_count ( iter ) )
return 0 ;
ret = ovl_real_fdget ( file , & real ) ;
if ( ret )
return ret ;
old_cred = ovl_override_creds ( file_inode ( file ) - > i_sb ) ;
ret = vfs_iter_read ( real . file , iter , & iocb - > ki_pos ,
ovl_iocb_to_rwf ( iocb ) ) ;
revert_creds ( old_cred ) ;
ovl_file_accessed ( file ) ;
fdput ( real ) ;
return ret ;
}
2018-07-18 16:44:41 +03:00
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 ) ;
2018-09-18 16:34:32 +03:00
file_start_write ( real . file ) ;
2018-07-18 16:44:41 +03:00
ret = vfs_iter_write ( real . file , iter , & iocb - > ki_pos ,
ovl_iocb_to_rwf ( iocb ) ) ;
2018-09-18 16:34:32 +03:00
file_end_write ( real . file ) ;
2018-07-18 16:44:41 +03:00
revert_creds ( old_cred ) ;
/* Update size */
ovl_copyattr ( ovl_inode_real ( inode ) , inode ) ;
fdput ( real ) ;
out_unlock :
inode_unlock ( inode ) ;
return ret ;
}
2018-07-18 16:44:42 +03:00
static int ovl_fsync ( struct file * file , loff_t start , loff_t end , int datasync )
{
struct fd real ;
const struct cred * old_cred ;
int ret ;
2018-05-11 18:49:31 +03:00
ret = ovl_real_fdget_meta ( file , & real , ! datasync ) ;
2018-07-18 16:44:42 +03:00
if ( ret )
return ret ;
/* Don't sync lower file for fear of receiving EROFS error */
if ( file_inode ( real . file ) = = ovl_inode_upper ( file_inode ( file ) ) ) {
old_cred = ovl_override_creds ( file_inode ( file ) - > i_sb ) ;
ret = vfs_fsync_range ( real . file , start , end , datasync ) ;
revert_creds ( old_cred ) ;
}
fdput ( real ) ;
return ret ;
}
2018-07-18 16:44:42 +03:00
static int ovl_mmap ( struct file * file , struct vm_area_struct * vma )
{
struct file * realfile = file - > private_data ;
const struct cred * old_cred ;
int ret ;
if ( ! realfile - > f_op - > mmap )
return - ENODEV ;
if ( WARN_ON ( file ! = vma - > vm_file ) )
return - EIO ;
vma - > vm_file = get_file ( realfile ) ;
old_cred = ovl_override_creds ( file_inode ( file ) - > i_sb ) ;
ret = call_mmap ( vma - > vm_file , vma ) ;
revert_creds ( old_cred ) ;
if ( ret ) {
/* Drop reference count from new vm_file value */
fput ( realfile ) ;
} else {
/* Drop reference count from previous vm_file value */
fput ( file ) ;
}
ovl_file_accessed ( file ) ;
return ret ;
}
2018-07-18 16:44:42 +03:00
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 ;
}
2018-08-28 10:58:41 +03:00
static int ovl_fadvise ( struct file * file , loff_t offset , loff_t len , int advice )
{
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_fadvise ( real . file , offset , len , advice ) ;
revert_creds ( old_cred ) ;
fdput ( real ) ;
return ret ;
}
2018-07-18 16:44:42 +03:00
static long ovl_real_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
struct fd real ;
const struct cred * old_cred ;
long ret ;
ret = ovl_real_fdget ( file , & real ) ;
if ( ret )
return ret ;
old_cred = ovl_override_creds ( file_inode ( file ) - > i_sb ) ;
ret = vfs_ioctl ( real . file , cmd , arg ) ;
revert_creds ( old_cred ) ;
fdput ( real ) ;
return ret ;
}
2019-05-26 09:28:25 +03:00
static long ovl_ioctl_set_flags ( struct file * file , unsigned int cmd ,
2019-06-11 18:09:28 +03:00
unsigned long arg , unsigned int iflags )
2018-07-18 16:44:42 +03:00
{
long ret ;
struct inode * inode = file_inode ( file ) ;
2019-06-11 18:09:28 +03:00
unsigned int old_iflags ;
2019-05-06 10:41:02 +03:00
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 ;
2019-06-11 18:09:28 +03:00
old_iflags = READ_ONCE ( inode - > i_flags ) ;
if ( ( ( iflags ^ old_iflags ) & ( S_APPEND | S_IMMUTABLE ) ) & &
2019-05-06 10:41:02 +03:00
! capable ( CAP_LINUX_IMMUTABLE ) )
goto unlock ;
ret = ovl_maybe_copy_up ( file_dentry ( file ) , O_WRONLY ) ;
if ( ret )
goto unlock ;
2019-05-26 09:28:25 +03:00
ret = ovl_real_ioctl ( file , cmd , arg ) ;
2019-05-06 10:41:02 +03:00
ovl_copyflags ( ovl_inode_real ( inode ) , inode ) ;
unlock :
inode_unlock ( inode ) ;
mnt_drop_write_file ( file ) ;
return ret ;
}
2019-06-11 18:09:28 +03:00
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 ) ) ;
}
2019-05-06 10:41:02 +03:00
static long ovl_ioctl ( struct file * file , unsigned int cmd , unsigned long arg )
{
long ret ;
2018-07-18 16:44:42 +03:00
switch ( cmd ) {
case FS_IOC_GETFLAGS :
2019-05-26 09:28:25 +03:00
case FS_IOC_FSGETXATTR :
2018-07-18 16:44:42 +03:00
ret = ovl_real_ioctl ( file , cmd , arg ) ;
break ;
case FS_IOC_SETFLAGS :
2019-06-11 18:09:28 +03:00
ret = ovl_ioctl_set_fsflags ( file , cmd , arg ) ;
break ;
2019-05-26 09:28:25 +03:00
case FS_IOC_FSSETXATTR :
2019-06-11 18:09:28 +03:00
ret = ovl_ioctl_set_fsxflags ( file , cmd , arg ) ;
2018-07-18 16:44:42 +03:00
break ;
default :
ret = - ENOTTY ;
}
return ret ;
}
static long ovl_compat_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
switch ( cmd ) {
case FS_IOC32_GETFLAGS :
cmd = FS_IOC_GETFLAGS ;
break ;
case FS_IOC32_SETFLAGS :
cmd = FS_IOC_SETFLAGS ;
break ;
default :
return - ENOIOCTLCMD ;
}
return ovl_ioctl ( file , cmd , arg ) ;
}
2018-07-18 16:44:42 +03:00
enum ovl_copyop {
OVL_COPY ,
OVL_CLONE ,
OVL_DEDUPE ,
} ;
2018-10-30 02:41:49 +03:00
static loff_t ovl_copyfile ( struct file * file_in , loff_t pos_in ,
2018-07-18 16:44:42 +03:00
struct file * file_out , loff_t pos_out ,
2018-10-30 02:41:49 +03:00
loff_t len , unsigned int flags , enum ovl_copyop op )
2018-07-18 16:44:42 +03:00
{
struct inode * inode_out = file_inode ( file_out ) ;
struct fd real_in , real_out ;
const struct cred * old_cred ;
2018-10-30 02:41:49 +03:00
loff_t ret ;
2018-07-18 16:44:42 +03:00
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 :
2018-09-18 16:34:34 +03:00
ret = vfs_clone_file_range ( real_in . file , pos_in ,
2018-10-30 02:41:56 +03:00
real_out . file , pos_out , len , flags ) ;
2018-07-18 16:44:42 +03:00
break ;
case OVL_DEDUPE :
ret = vfs_dedupe_file_range_one ( real_in . file , pos_in ,
2018-10-30 02:42:03 +03:00
real_out . file , pos_out , len ,
flags ) ;
2018-07-18 16:44:42 +03:00
break ;
}
revert_creds ( old_cred ) ;
/* Update size */
ovl_copyattr ( ovl_inode_real ( inode_out ) , inode_out ) ;
fdput ( real_in ) ;
fdput ( real_out ) ;
return ret ;
}
static ssize_t ovl_copy_file_range ( struct file * file_in , loff_t pos_in ,
struct file * file_out , loff_t pos_out ,
size_t len , unsigned int flags )
{
return ovl_copyfile ( file_in , pos_in , file_out , pos_out , len , flags ,
OVL_COPY ) ;
}
2018-10-30 02:41:49 +03:00
static loff_t ovl_remap_file_range ( struct file * file_in , loff_t pos_in ,
struct file * file_out , loff_t pos_out ,
loff_t len , unsigned int remap_flags )
2018-07-18 16:44:42 +03:00
{
2018-10-30 02:41:21 +03:00
enum ovl_copyop op ;
if ( remap_flags & ~ ( REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY ) )
return - EINVAL ;
if ( remap_flags & REMAP_FILE_DEDUP )
op = OVL_DEDUPE ;
else
op = OVL_CLONE ;
2018-07-18 16:44:42 +03:00
/*
* Don ' t copy up because of a dedupe request , this wouldn ' t make sense
* most of the time ( data would be duplicated instead of deduplicated ) .
*/
2018-10-30 02:41:21 +03:00
if ( op = = OVL_DEDUPE & &
( ! ovl_inode_upper ( file_inode ( file_in ) ) | |
! ovl_inode_upper ( file_inode ( file_out ) ) ) )
2018-07-18 16:44:42 +03:00
return - EPERM ;
2018-10-30 02:41:56 +03:00
return ovl_copyfile ( file_in , pos_in , file_out , pos_out , len ,
remap_flags , op ) ;
2018-07-18 16:44:42 +03:00
}
2018-07-18 16:44:41 +03:00
const struct file_operations ovl_file_operations = {
. open = ovl_open ,
. release = ovl_release ,
. llseek = ovl_llseek ,
2018-07-18 16:44:41 +03:00
. read_iter = ovl_read_iter ,
2018-07-18 16:44:41 +03:00
. write_iter = ovl_write_iter ,
2018-07-18 16:44:42 +03:00
. fsync = ovl_fsync ,
2018-07-18 16:44:42 +03:00
. mmap = ovl_mmap ,
2018-07-18 16:44:42 +03:00
. fallocate = ovl_fallocate ,
2018-08-28 10:58:41 +03:00
. fadvise = ovl_fadvise ,
2018-07-18 16:44:42 +03:00
. unlocked_ioctl = ovl_ioctl ,
. compat_ioctl = ovl_compat_ioctl ,
2018-07-18 16:44:42 +03:00
. copy_file_range = ovl_copy_file_range ,
2018-10-30 02:41:21 +03:00
. remap_file_range = ovl_remap_file_range ,
2018-07-18 16:44:41 +03:00
} ;