2022-12-13 12:20:42

by Xiubo Li

[permalink] [raw]
Subject: [PATCH v4 0/2] ceph: fix the use-after-free bug for file_lock

From: Xiubo Li <[email protected]>

Changed in V4:
- repeat the afs in fs.h instead of adding ceph specific header file

Changed in V3:
- switched to vfs_inode_has_locks() helper to fix another ceph file lock
bug, thanks Jeff!
- this patch series is based on Jeff's previous VFS lock patch:
https://patchwork.kernel.org/project/ceph-devel/list/?series=695950

Changed in V2:
- switch to file_lock.fl_u to fix the race bug
- and the most code will be in the ceph layer

Xiubo Li (2):
ceph: switch to vfs_inode_has_locks() to fix file lock bug
ceph: add ceph specific member support for file_lock

fs/ceph/caps.c | 2 +-
fs/ceph/locks.c | 24 ++++++++++++++++++------
fs/ceph/super.h | 1 -
include/linux/fs.h | 3 +++
4 files changed, 22 insertions(+), 8 deletions(-)

--
2.31.1


2022-12-13 12:21:07

by Xiubo Li

[permalink] [raw]
Subject: [PATCH v4 2/2] ceph: add ceph specific member support for file_lock

From: Xiubo Li <[email protected]>

When ceph releasing the file_lock it will try to get the inode pointer
from the fl->fl_file, which the memory could already be released by
another thread in filp_close(). Because in VFS layer the fl->fl_file
doesn't increase the file's reference counter.

Will switch to use ceph dedicate lock info to track the inode.

And in ceph_fl_release_lock() we should skip all the operations if
the fl->fl_u.ceph_fl.fl_inode is not set, which should come from
the request file_lock. And we will set fl->fl_u.ceph_fl.fl_inode when
inserting it to the inode lock list, which is when copying the lock.

Cc: [email protected]
Cc: Jeff Layton <[email protected]>
URL: https://tracker.ceph.com/issues/57986
Signed-off-by: Xiubo Li <[email protected]>
---
fs/ceph/locks.c | 20 ++++++++++++++++++--
include/linux/fs.h | 3 +++
2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/fs/ceph/locks.c b/fs/ceph/locks.c
index b191426bf880..cf78608a3f9a 100644
--- a/fs/ceph/locks.c
+++ b/fs/ceph/locks.c
@@ -34,18 +34,34 @@ static void ceph_fl_copy_lock(struct file_lock *dst, struct file_lock *src)
{
struct inode *inode = file_inode(dst->fl_file);
atomic_inc(&ceph_inode(inode)->i_filelock_ref);
+ dst->fl_u.ceph.fl_inode = igrab(inode);
}

+/*
+ * Do not use the 'fl->fl_file' in release function, which
+ * is possibly already released by another thread.
+ */
static void ceph_fl_release_lock(struct file_lock *fl)
{
- struct inode *inode = file_inode(fl->fl_file);
- struct ceph_inode_info *ci = ceph_inode(inode);
+ struct inode *inode = fl->fl_u.ceph.fl_inode;
+ struct ceph_inode_info *ci;
+
+ /*
+ * If inode is NULL it should be a request file_lock,
+ * nothing we can do.
+ */
+ if (!inode)
+ return;
+
+ ci = ceph_inode(inode);
if (atomic_dec_and_test(&ci->i_filelock_ref)) {
/* clear error when all locks are released */
spin_lock(&ci->i_ceph_lock);
ci->i_ceph_flags &= ~CEPH_I_ERROR_FILELOCK;
spin_unlock(&ci->i_ceph_lock);
}
+ fl->fl_u.ceph.fl_inode = NULL;
+ iput(inode);
}

static const struct file_lock_operations ceph_fl_lock_ops = {
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 7b52fdfb6da0..6106374f5257 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1119,6 +1119,9 @@ struct file_lock {
int state; /* state of grant or error if -ve */
unsigned int debug_id;
} afs;
+ struct {
+ struct inode *fl_inode;
+ } ceph;
} fl_u;
} __randomize_layout;

--
2.31.1

2022-12-13 13:06:38

by Xiubo Li

[permalink] [raw]
Subject: [PATCH v4 1/2] ceph: switch to vfs_inode_has_locks() to fix file lock bug

From: Xiubo Li <[email protected]>

For the POSIX locks they are using the same owner, which is the
thread id. And multiple POSIX locks could be merged into single one,
so when checking whether the 'file' has locks may fail.

For a file where some openers use locking and others don't is a
really odd usage pattern though. Locks are like stoplights -- they
only work if everyone pays attention to them.

Just switch ceph_get_caps() to check whether any locks are set on
the inode. If there are POSIX/OFD/FLOCK locks on the file at the
time, we should set CHECK_FILELOCK, regardless of what fd was used
to set the lock.

Cc: [email protected]
Fixes: ff5d913dfc71 ("ceph: return -EIO if read/write against filp that lost file locks")
Cc: Jeff Layton <[email protected]>
Signed-off-by: Xiubo Li <[email protected]>
---
fs/ceph/caps.c | 2 +-
fs/ceph/locks.c | 4 ----
fs/ceph/super.h | 1 -
3 files changed, 1 insertion(+), 6 deletions(-)

diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c
index 065e9311b607..948136f81fc8 100644
--- a/fs/ceph/caps.c
+++ b/fs/ceph/caps.c
@@ -2964,7 +2964,7 @@ int ceph_get_caps(struct file *filp, int need, int want, loff_t endoff, int *got

while (true) {
flags &= CEPH_FILE_MODE_MASK;
- if (atomic_read(&fi->num_locks))
+ if (vfs_inode_has_locks(inode))
flags |= CHECK_FILELOCK;
_got = 0;
ret = try_get_cap_refs(inode, need, want, endoff,
diff --git a/fs/ceph/locks.c b/fs/ceph/locks.c
index 3e2843e86e27..b191426bf880 100644
--- a/fs/ceph/locks.c
+++ b/fs/ceph/locks.c
@@ -32,18 +32,14 @@ void __init ceph_flock_init(void)

static void ceph_fl_copy_lock(struct file_lock *dst, struct file_lock *src)
{
- struct ceph_file_info *fi = dst->fl_file->private_data;
struct inode *inode = file_inode(dst->fl_file);
atomic_inc(&ceph_inode(inode)->i_filelock_ref);
- atomic_inc(&fi->num_locks);
}

static void ceph_fl_release_lock(struct file_lock *fl)
{
- struct ceph_file_info *fi = fl->fl_file->private_data;
struct inode *inode = file_inode(fl->fl_file);
struct ceph_inode_info *ci = ceph_inode(inode);
- atomic_dec(&fi->num_locks);
if (atomic_dec_and_test(&ci->i_filelock_ref)) {
/* clear error when all locks are released */
spin_lock(&ci->i_ceph_lock);
diff --git a/fs/ceph/super.h b/fs/ceph/super.h
index 14454f464029..e7662ff6f149 100644
--- a/fs/ceph/super.h
+++ b/fs/ceph/super.h
@@ -804,7 +804,6 @@ struct ceph_file_info {
struct list_head rw_contexts;

u32 filp_gen;
- atomic_t num_locks;
};

struct ceph_dir_file_info {
--
2.31.1

2022-12-13 13:14:26

by Jeffrey Layton

[permalink] [raw]
Subject: Re: [PATCH v4 0/2] ceph: fix the use-after-free bug for file_lock

On Tue, 2022-12-13 at 20:11 +0800, [email protected] wrote:
> From: Xiubo Li <[email protected]>
>
> Changed in V4:
> - repeat the afs in fs.h instead of adding ceph specific header file
>
> Changed in V3:
> - switched to vfs_inode_has_locks() helper to fix another ceph file lock
> bug, thanks Jeff!
> - this patch series is based on Jeff's previous VFS lock patch:
> https://patchwork.kernel.org/project/ceph-devel/list/?series=695950
>
> Changed in V2:
> - switch to file_lock.fl_u to fix the race bug
> - and the most code will be in the ceph layer
>
> Xiubo Li (2):
> ceph: switch to vfs_inode_has_locks() to fix file lock bug
> ceph: add ceph specific member support for file_lock
>
> fs/ceph/caps.c | 2 +-
> fs/ceph/locks.c | 24 ++++++++++++++++++------
> fs/ceph/super.h | 1 -
> include/linux/fs.h | 3 +++
> 4 files changed, 22 insertions(+), 8 deletions(-)
>

Both patches look good to me. You can add:

Reviewed-by: Jeff Layton <[email protected]>

2022-12-13 18:42:59

by Ilya Dryomov

[permalink] [raw]
Subject: Re: [PATCH v4 2/2] ceph: add ceph specific member support for file_lock

On Tue, Dec 13, 2022 at 1:11 PM <[email protected]> wrote:
>
> From: Xiubo Li <[email protected]>
>
> When ceph releasing the file_lock it will try to get the inode pointer
> from the fl->fl_file, which the memory could already be released by
> another thread in filp_close(). Because in VFS layer the fl->fl_file
> doesn't increase the file's reference counter.
>
> Will switch to use ceph dedicate lock info to track the inode.
>
> And in ceph_fl_release_lock() we should skip all the operations if
> the fl->fl_u.ceph_fl.fl_inode is not set, which should come from
> the request file_lock. And we will set fl->fl_u.ceph_fl.fl_inode when
> inserting it to the inode lock list, which is when copying the lock.
>
> Cc: [email protected]
> Cc: Jeff Layton <[email protected]>
> URL: https://tracker.ceph.com/issues/57986
> Signed-off-by: Xiubo Li <[email protected]>
> ---
> fs/ceph/locks.c | 20 ++++++++++++++++++--
> include/linux/fs.h | 3 +++
> 2 files changed, 21 insertions(+), 2 deletions(-)
>
> diff --git a/fs/ceph/locks.c b/fs/ceph/locks.c
> index b191426bf880..cf78608a3f9a 100644
> --- a/fs/ceph/locks.c
> +++ b/fs/ceph/locks.c
> @@ -34,18 +34,34 @@ static void ceph_fl_copy_lock(struct file_lock *dst, struct file_lock *src)
> {
> struct inode *inode = file_inode(dst->fl_file);
> atomic_inc(&ceph_inode(inode)->i_filelock_ref);
> + dst->fl_u.ceph.fl_inode = igrab(inode);
> }
>
> +/*
> + * Do not use the 'fl->fl_file' in release function, which
> + * is possibly already released by another thread.
> + */
> static void ceph_fl_release_lock(struct file_lock *fl)
> {
> - struct inode *inode = file_inode(fl->fl_file);
> - struct ceph_inode_info *ci = ceph_inode(inode);
> + struct inode *inode = fl->fl_u.ceph.fl_inode;
> + struct ceph_inode_info *ci;
> +
> + /*
> + * If inode is NULL it should be a request file_lock,
> + * nothing we can do.
> + */
> + if (!inode)
> + return;
> +
> + ci = ceph_inode(inode);
> if (atomic_dec_and_test(&ci->i_filelock_ref)) {
> /* clear error when all locks are released */
> spin_lock(&ci->i_ceph_lock);
> ci->i_ceph_flags &= ~CEPH_I_ERROR_FILELOCK;
> spin_unlock(&ci->i_ceph_lock);
> }
> + fl->fl_u.ceph.fl_inode = NULL;
> + iput(inode);
> }
>
> static const struct file_lock_operations ceph_fl_lock_ops = {
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 7b52fdfb6da0..6106374f5257 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -1119,6 +1119,9 @@ struct file_lock {
> int state; /* state of grant or error if -ve */
> unsigned int debug_id;
> } afs;
> + struct {
> + struct inode *fl_inode;

Hi Xiubo,

Nit: I think it could be just "inode", without the prefix which is
already present in the union field name.

Thanks,

Ilya

2022-12-14 02:44:57

by Xiubo Li

[permalink] [raw]
Subject: Re: [PATCH v4 2/2] ceph: add ceph specific member support for file_lock


On 14/12/2022 02:05, Ilya Dryomov wrote:
> On Tue, Dec 13, 2022 at 1:11 PM <[email protected]> wrote:
>> From: Xiubo Li <[email protected]>
>>
>> When ceph releasing the file_lock it will try to get the inode pointer
>> from the fl->fl_file, which the memory could already be released by
>> another thread in filp_close(). Because in VFS layer the fl->fl_file
>> doesn't increase the file's reference counter.
>>
>> Will switch to use ceph dedicate lock info to track the inode.
>>
>> And in ceph_fl_release_lock() we should skip all the operations if
>> the fl->fl_u.ceph_fl.fl_inode is not set, which should come from
>> the request file_lock. And we will set fl->fl_u.ceph_fl.fl_inode when
>> inserting it to the inode lock list, which is when copying the lock.
>>
>> Cc: [email protected]
>> Cc: Jeff Layton <[email protected]>
>> URL: https://tracker.ceph.com/issues/57986
>> Signed-off-by: Xiubo Li <[email protected]>
>> ---
>> fs/ceph/locks.c | 20 ++++++++++++++++++--
>> include/linux/fs.h | 3 +++
>> 2 files changed, 21 insertions(+), 2 deletions(-)
>>
>> diff --git a/fs/ceph/locks.c b/fs/ceph/locks.c
>> index b191426bf880..cf78608a3f9a 100644
>> --- a/fs/ceph/locks.c
>> +++ b/fs/ceph/locks.c
>> @@ -34,18 +34,34 @@ static void ceph_fl_copy_lock(struct file_lock *dst, struct file_lock *src)
>> {
>> struct inode *inode = file_inode(dst->fl_file);
>> atomic_inc(&ceph_inode(inode)->i_filelock_ref);
>> + dst->fl_u.ceph.fl_inode = igrab(inode);
>> }
>>
>> +/*
>> + * Do not use the 'fl->fl_file' in release function, which
>> + * is possibly already released by another thread.
>> + */
>> static void ceph_fl_release_lock(struct file_lock *fl)
>> {
>> - struct inode *inode = file_inode(fl->fl_file);
>> - struct ceph_inode_info *ci = ceph_inode(inode);
>> + struct inode *inode = fl->fl_u.ceph.fl_inode;
>> + struct ceph_inode_info *ci;
>> +
>> + /*
>> + * If inode is NULL it should be a request file_lock,
>> + * nothing we can do.
>> + */
>> + if (!inode)
>> + return;
>> +
>> + ci = ceph_inode(inode);
>> if (atomic_dec_and_test(&ci->i_filelock_ref)) {
>> /* clear error when all locks are released */
>> spin_lock(&ci->i_ceph_lock);
>> ci->i_ceph_flags &= ~CEPH_I_ERROR_FILELOCK;
>> spin_unlock(&ci->i_ceph_lock);
>> }
>> + fl->fl_u.ceph.fl_inode = NULL;
>> + iput(inode);
>> }
>>
>> static const struct file_lock_operations ceph_fl_lock_ops = {
>> diff --git a/include/linux/fs.h b/include/linux/fs.h
>> index 7b52fdfb6da0..6106374f5257 100644
>> --- a/include/linux/fs.h
>> +++ b/include/linux/fs.h
>> @@ -1119,6 +1119,9 @@ struct file_lock {
>> int state; /* state of grant or error if -ve */
>> unsigned int debug_id;
>> } afs;
>> + struct {
>> + struct inode *fl_inode;
> Hi Xiubo,
>
> Nit: I think it could be just "inode", without the prefix which is
> already present in the union field name.

Okay, I can fix this in the next version.

Thanks.

- Xiubo


> Thanks,
>
> Ilya
>