On Thu, Jun 13, 2019 at 5:32 PM J . Bruce Fields <bfields@xxxxxxxxxxxx> wrote: > > On Thu, Jun 13, 2019 at 04:13:15PM +0200, Miklos Szeredi wrote: > > On Wed, Jun 12, 2019 at 8:31 PM J . Bruce Fields <bfields@xxxxxxxxxxxx> wrote: > > > > > > How do opens for execute work? I guess they create a struct file with > > > FMODE_EXEC and FMODE_RDONLY set and they decrement i_writecount. Do > > > they also increment i_readcount? Reading do_open_execat and alloc_file, > > > looks like it does, so, good, they should conflict with write leases, > > > which sounds right. > > > > Right, but then why this: > > > > > > + /* Eliminate deny writes from actual writers count */ > > > > + if (wcount < 0) > > > > + wcount = 0; > > > > It's basically a no-op, as you say. And it doesn't make any sense > > logically, since denying writes *should* deny write leases as well... > > Yes. I feel like the negative writecount case is a little nonobvious, > so maybe replace that by a comment, something like this?: > > --b. > > diff --git a/fs/locks.c b/fs/locks.c > index 2056595751e8..379829b913c1 100644 > --- a/fs/locks.c > +++ b/fs/locks.c > @@ -1772,11 +1772,12 @@ check_conflicting_open(struct file *filp, const long arg, int flags) > if (arg == F_RDLCK && wcount > 0) > return -EAGAIN; > > - /* Eliminate deny writes from actual writers count */ > - if (wcount < 0) > - wcount = 0; > - > - /* Make sure that only read/write count is from lease requestor */ > + /* > + * Make sure that only read/write count is from lease requestor. > + * Note that this will result in denying write leases when wcount > + * is negative, which is what we want. (We shouldn't grant > + * write leases on files open for execution.) > + */ > if (filp->f_mode & FMODE_WRITE) > self_wcount = 1; > else if (filp->f_mode & FMODE_READ) I'm fine with targeting 5.3 and I'm fine with all suggested changes and adding some of my own. At this point we no longer need wcount variable and code becomes more readable without it. See attached patch (also tested). Thanks, Amir.
From d1b029a778c9100bf1a295c7035319913ef25333 Mon Sep 17 00:00:00 2001 From: Amir Goldstein <amir73il@xxxxxxxxx> Date: Fri, 7 Jun 2019 17:24:38 +0300 Subject: [PATCH v3] locks: eliminate false positive conflicts for write lease check_conflicting_open() is checking for existing fd's open for read or for write before allowing to take a write lease. The check that was implemented using i_count and d_count is an approximation that has several false positives. For example, overlayfs since v4.19, takes an extra reference on the dentry; An open with O_PATH takes a reference on the dentry although the file cannot be read nor written. Change the implementation to use i_readcount and i_writecount to eliminate the false positive conflicts and allow a write lease to be taken on an overlayfs file. The change of behavior with existing fd's open with O_PATH is symmetric w.r.t. current behavior of lease breakers - an open with O_PATH currently does not break a write lease. This increases the size of struct inode by 4 bytes on 32bit archs when CONFIG_FILE_LOCKING is defined and CONFIG_IMA was not already defined. Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx> --- fs/locks.c | 37 +++++++++++++++++++++++++------------ include/linux/fs.h | 4 ++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/fs/locks.c b/fs/locks.c index ec1e4a5df629..633512100842 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -1753,10 +1753,10 @@ int fcntl_getlease(struct file *filp) } /** - * check_conflicting_open - see if the given dentry points to a file that has + * check_conflicting_open - see if the given file points to an inode that has * an existing open that would conflict with the * desired lease. - * @dentry: dentry to check + * @filp: file to check * @arg: type of lease that we're trying to acquire * @flags: current lock flags * @@ -1764,19 +1764,33 @@ int fcntl_getlease(struct file *filp) * conflict with the lease we're trying to set. */ static int -check_conflicting_open(const struct dentry *dentry, const long arg, int flags) +check_conflicting_open(struct file *filp, const long arg, int flags) { int ret = 0; - struct inode *inode = dentry->d_inode; + struct inode *inode = locks_inode(filp); + int self_wcount = 0, self_rcount = 0; if (flags & FL_LAYOUT) return 0; - if ((arg == F_RDLCK) && inode_is_open_for_write(inode)) - return -EAGAIN; + if (arg == F_RDLCK) + return inode_is_open_for_write(inode) ? -EAGAIN : 0; + else if (arg != F_WRLCK) + return 0; - if ((arg == F_WRLCK) && ((d_count(dentry) > 1) || - (atomic_read(&inode->i_count) > 1))) + /* + * Make sure that only read/write count is from lease requestor. + * Note that this will result in denying write leases when i_writecount + * is negative, which is what we want. (We shouldn't grant write leases + * on files open for execution.) + */ + if (filp->f_mode & FMODE_WRITE) + self_wcount = 1; + else if (filp->f_mode & FMODE_READ) + self_rcount = 1; + + if (atomic_read(&inode->i_writecount) != self_wcount || + atomic_read(&inode->i_readcount) != self_rcount) ret = -EAGAIN; return ret; @@ -1786,8 +1800,7 @@ static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **priv) { struct file_lock *fl, *my_fl = NULL, *lease; - struct dentry *dentry = filp->f_path.dentry; - struct inode *inode = dentry->d_inode; + struct inode *inode = locks_inode(filp); struct file_lock_context *ctx; bool is_deleg = (*flp)->fl_flags & FL_DELEG; int error; @@ -1822,7 +1835,7 @@ generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **pr percpu_down_read(&file_rwsem); spin_lock(&ctx->flc_lock); time_out_leases(inode, &dispose); - error = check_conflicting_open(dentry, arg, lease->fl_flags); + error = check_conflicting_open(filp, arg, lease->fl_flags); if (error) goto out; @@ -1879,7 +1892,7 @@ generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **pr * precedes these checks. */ smp_mb(); - error = check_conflicting_open(dentry, arg, lease->fl_flags); + error = check_conflicting_open(filp, arg, lease->fl_flags); if (error) { locks_unlink_lock_ctx(lease); goto out; diff --git a/include/linux/fs.h b/include/linux/fs.h index 79ffa2958bd8..2d55f1b64014 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -694,7 +694,7 @@ struct inode { atomic_t i_count; atomic_t i_dio_count; atomic_t i_writecount; -#ifdef CONFIG_IMA +#if defined(CONFIG_IMA) || defined(CONFIG_FILE_LOCKING) atomic_t i_readcount; /* struct files open RO */ #endif union { @@ -2895,7 +2895,7 @@ static inline bool inode_is_open_for_write(const struct inode *inode) return atomic_read(&inode->i_writecount) > 0; } -#ifdef CONFIG_IMA +#if defined(CONFIG_IMA) || defined(CONFIG_FILE_LOCKING) static inline void i_readcount_dec(struct inode *inode) { BUG_ON(!atomic_read(&inode->i_readcount)); -- 2.17.1