From: Baokun Li <[email protected]>
Hello everyone!
Recently we found some bugs while doing tests on cachefiles ondemand mode,
and this patchset is a fix for some of those issues. The following is a
brief overview of the patches, see the patches for more details.
Patch 1-5: Holding reference counts of reqs and objects on read requests
to avoid malicious restore leading to use-after-free.
Patch 6-10: Add some consistency checks to copen/cread/get_fd to avoid
malicious copen/cread/close fd injections causing use-after-free or hung.
Patch 11: When cache is marked as CACHEFILES_DEAD, flush all requests,
otherwise the kernel may be hung. since this state is irreversible, the
daemon can read open requests but cannot copen.
Patch 12: Allow interrupting a read request being processed by killing
the read process as a way of avoiding hung in some special cases.
Comments and questions are, as always, welcome.
Thanks,
Baokun
Baokun Li (11):
cachefiles: remove request from xarry during flush requests
cachefiles: remove err_put_fd tag in cachefiles_ondemand_daemon_read()
cachefiles: fix slab-use-after-free in cachefiles_ondemand_get_fd()
cachefiles: fix slab-use-after-free in
cachefiles_ondemand_daemon_read()
cachefiles: add output string to cachefiles_obj_[get|put]_ondemand_fd
cachefiles: add consistency check for copen/cread
cachefiles: add spin_lock for cachefiles_ondemand_info
cachefiles: never get a new anon fd if ondemand_id is valid
cachefiles: defer exposing anon_fd until after copy_to_user() succeeds
cachefiles: flush all requests after setting CACHEFILES_DEAD
cachefiles: make on-demand read killable
Zizhi Wo (1):
cachefiles: Set object to close if ondemand_id < 0 in copen
fs/cachefiles/daemon.c | 3 +-
fs/cachefiles/internal.h | 5 +
fs/cachefiles/ondemand.c | 199 +++++++++++++++++++++---------
include/trace/events/cachefiles.h | 8 +-
4 files changed, 158 insertions(+), 57 deletions(-)
--
2.39.2
From: Baokun Li <[email protected]>
This prevents concurrency from causing access to a freed req.
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/daemon.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c
index 6465e2574230..ccb7b707ea4b 100644
--- a/fs/cachefiles/daemon.c
+++ b/fs/cachefiles/daemon.c
@@ -159,6 +159,7 @@ static void cachefiles_flush_reqs(struct cachefiles_cache *cache)
xa_for_each(xa, index, req) {
req->error = -EIO;
complete(&req->done);
+ __xa_erase(xa, index);
}
xa_unlock(xa);
--
2.39.2
From: Baokun Li <[email protected]>
The err_put_fd tag is only used once, so remove it to make the code more
readable.
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/ondemand.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index 4ba42f1fa3b4..fd49728d8bae 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -347,7 +347,9 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
if (copy_to_user(_buffer, msg, n) != 0) {
ret = -EFAULT;
- goto err_put_fd;
+ if (msg->opcode == CACHEFILES_OP_OPEN)
+ close_fd(((struct cachefiles_open *)msg->data)->fd);
+ goto error;
}
/* CLOSE request has no reply */
@@ -358,9 +360,6 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
return n;
-err_put_fd:
- if (msg->opcode == CACHEFILES_OP_OPEN)
- close_fd(((struct cachefiles_open *)msg->data)->fd);
error:
xa_erase(&cache->reqs, id);
req->error = ret;
--
2.39.2
From: Baokun Li <[email protected]>
We got the following issue in a fuzz test of randomly issuing the restore
command:
==================================================================
BUG: KASAN: slab-use-after-free in cachefiles_ondemand_daemon_read+0x609/0xab0
Write of size 4 at addr ffff888109164a80 by task ondemand-04-dae/4962
CPU: 11 PID: 4962 Comm: ondemand-04-dae Not tainted 6.8.0-rc7-dirty #542
Call Trace:
kasan_report+0x94/0xc0
cachefiles_ondemand_daemon_read+0x609/0xab0
vfs_read+0x169/0xb50
ksys_read+0xf5/0x1e0
Allocated by task 626:
__kmalloc+0x1df/0x4b0
cachefiles_ondemand_send_req+0x24d/0x690
cachefiles_create_tmpfile+0x249/0xb30
cachefiles_create_file+0x6f/0x140
cachefiles_look_up_object+0x29c/0xa60
cachefiles_lookup_cookie+0x37d/0xca0
fscache_cookie_state_machine+0x43c/0x1230
[...]
Freed by task 626:
kfree+0xf1/0x2c0
cachefiles_ondemand_send_req+0x568/0x690
cachefiles_create_tmpfile+0x249/0xb30
cachefiles_create_file+0x6f/0x140
cachefiles_look_up_object+0x29c/0xa60
cachefiles_lookup_cookie+0x37d/0xca0
fscache_cookie_state_machine+0x43c/0x1230
[...]
==================================================================
Following is the process that triggers the issue:
mount | daemon_thread1 | daemon_thread2
------------------------------------------------------------
cachefiles_ondemand_init_object
cachefiles_ondemand_send_req
REQ_A = kzalloc(sizeof(*req) + data_len)
wait_for_completion(&REQ_A->done)
cachefiles_daemon_read
cachefiles_ondemand_daemon_read
REQ_A = cachefiles_ondemand_select_req
cachefiles_ondemand_get_fd
copy_to_user(_buffer, msg, n)
process_open_req(REQ_A)
------ restore ------
cachefiles_ondemand_restore
xas_for_each(&xas, req, ULONG_MAX)
xas_set_mark(&xas, CACHEFILES_REQ_NEW);
cachefiles_daemon_read
cachefiles_ondemand_daemon_read
REQ_A = cachefiles_ondemand_select_req
write(devfd, ("copen %u,%llu", msg->msg_id, size));
cachefiles_ondemand_copen
xa_erase(&cache->reqs, id)
complete(&REQ_A->done)
kfree(REQ_A)
cachefiles_ondemand_get_fd(REQ_A)
fd = get_unused_fd_flags
file = anon_inode_getfile
fd_install(fd, file)
load = (void *)REQ_A->msg.data;
load->fd = fd;
// load UAF !!!
This issue is caused by issuing a restore command when the daemon is still
alive, which results in a request being processed multiple times thus
triggering a UAF. So to avoid this problem, add an additional reference
count to cachefiles_req, which is held while waiting and reading, and then
released when the waiting and reading is over.
Note that since there is only one reference count for waiting, we need to
avoid the same request being completed multiple times, so we can only
complete the request if it is successfully removed from the xarray.
Fixes: e73fa11a356c ("cachefiles: add restore command to recover inflight ondemand read requests")
Suggested-by: Hou Tao <[email protected]>
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/internal.h | 1 +
fs/cachefiles/ondemand.c | 44 ++++++++++++++++++++++------------------
2 files changed, 25 insertions(+), 20 deletions(-)
diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
index d33169f0018b..7745b8abc3aa 100644
--- a/fs/cachefiles/internal.h
+++ b/fs/cachefiles/internal.h
@@ -138,6 +138,7 @@ static inline bool cachefiles_in_ondemand_mode(struct cachefiles_cache *cache)
struct cachefiles_req {
struct cachefiles_object *object;
struct completion done;
+ refcount_t ref;
int error;
struct cachefiles_msg msg;
};
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index fd49728d8bae..56d12fe4bf73 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -4,6 +4,12 @@
#include <linux/uio.h>
#include "internal.h"
+static inline void cachefiles_req_put(struct cachefiles_req *req)
+{
+ if (refcount_dec_and_test(&req->ref))
+ kfree(req);
+}
+
static int cachefiles_ondemand_fd_release(struct inode *inode,
struct file *file)
{
@@ -299,7 +305,6 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
{
struct cachefiles_req *req;
struct cachefiles_msg *msg;
- unsigned long id = 0;
size_t n;
int ret = 0;
XA_STATE(xas, &cache->reqs, cache->req_id_next);
@@ -330,41 +335,39 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
xas_clear_mark(&xas, CACHEFILES_REQ_NEW);
cache->req_id_next = xas.xa_index + 1;
+ refcount_inc(&req->ref);
xa_unlock(&cache->reqs);
- id = xas.xa_index;
-
if (msg->opcode == CACHEFILES_OP_OPEN) {
ret = cachefiles_ondemand_get_fd(req);
if (ret) {
cachefiles_ondemand_set_object_close(req->object);
- goto error;
+ goto out;
}
}
- msg->msg_id = id;
+ msg->msg_id = xas.xa_index;
msg->object_id = req->object->ondemand->ondemand_id;
if (copy_to_user(_buffer, msg, n) != 0) {
ret = -EFAULT;
if (msg->opcode == CACHEFILES_OP_OPEN)
close_fd(((struct cachefiles_open *)msg->data)->fd);
- goto error;
}
-
- /* CLOSE request has no reply */
- if (msg->opcode == CACHEFILES_OP_CLOSE) {
- xa_erase(&cache->reqs, id);
- complete(&req->done);
+out:
+ /* Remove error request and CLOSE request has no reply */
+ if (ret || msg->opcode == CACHEFILES_OP_CLOSE) {
+ xas_reset(&xas);
+ xas_lock(&xas);
+ if (xas_load(&xas) == req) {
+ req->error = ret;
+ complete(&req->done);
+ xas_store(&xas, NULL);
+ }
+ xas_unlock(&xas);
}
-
- return n;
-
-error:
- xa_erase(&cache->reqs, id);
- req->error = ret;
- complete(&req->done);
- return ret;
+ cachefiles_req_put(req);
+ return ret ? ret : n;
}
typedef int (*init_req_fn)(struct cachefiles_req *req, void *private);
@@ -394,6 +397,7 @@ static int cachefiles_ondemand_send_req(struct cachefiles_object *object,
goto out;
}
+ refcount_set(&req->ref, 1);
req->object = object;
init_completion(&req->done);
req->msg.opcode = opcode;
@@ -455,7 +459,7 @@ static int cachefiles_ondemand_send_req(struct cachefiles_object *object,
wake_up_all(&cache->daemon_pollwq);
wait_for_completion(&req->done);
ret = req->error;
- kfree(req);
+ cachefiles_req_put(req);
return ret;
out:
/* Reset the object to close state in error handling path.
--
2.39.2
From: Baokun Li <[email protected]>
This lets us see the correct trace output.
Fixes: c8383054506c ("cachefiles: notify the user daemon when looking up cookie")
Signed-off-by: Baokun Li <[email protected]>
---
include/trace/events/cachefiles.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cachefiles.h
index 119a823fb5a0..bb56e3104b12 100644
--- a/include/trace/events/cachefiles.h
+++ b/include/trace/events/cachefiles.h
@@ -130,6 +130,8 @@ enum cachefiles_error_trace {
EM(cachefiles_obj_see_lookup_failed, "SEE lookup_failed") \
EM(cachefiles_obj_see_withdraw_cookie, "SEE withdraw_cookie") \
EM(cachefiles_obj_see_withdrawal, "SEE withdrawal") \
+ EM(cachefiles_obj_get_ondemand_fd, "GET ondemand_fd") \
+ EM(cachefiles_obj_put_ondemand_fd, "PUT ondemand_fd") \
EM(cachefiles_obj_get_read_req, "GET read_req") \
E_(cachefiles_obj_put_read_req, "PUT read_req")
--
2.39.2
From: Baokun Li <[email protected]>
This prevents malicious processes from completing random copen/cread
requests and crashing the system. Added checks are listed below:
* Generic, copen can only complete open requests, and cread can only
complete read requests.
* For copen, ondemand_id must not be 0, because this indicates that the
request has not been read by the daemon.
* For cread, the object corresponding to fd and req should be the same.
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/ondemand.c | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index bb94ef6a6f61..898fab68332b 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -82,12 +82,12 @@ static loff_t cachefiles_ondemand_fd_llseek(struct file *filp, loff_t pos,
}
static long cachefiles_ondemand_fd_ioctl(struct file *filp, unsigned int ioctl,
- unsigned long arg)
+ unsigned long id)
{
struct cachefiles_object *object = filp->private_data;
struct cachefiles_cache *cache = object->volume->cache;
struct cachefiles_req *req;
- unsigned long id;
+ XA_STATE(xas, &cache->reqs, id);
if (ioctl != CACHEFILES_IOC_READ_COMPLETE)
return -EINVAL;
@@ -95,10 +95,15 @@ static long cachefiles_ondemand_fd_ioctl(struct file *filp, unsigned int ioctl,
if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags))
return -EOPNOTSUPP;
- id = arg;
- req = xa_erase(&cache->reqs, id);
- if (!req)
+ xa_lock(&cache->reqs);
+ req = xas_load(&xas);
+ if (!req || req->msg.opcode != CACHEFILES_OP_READ ||
+ req->object != object) {
+ xa_unlock(&cache->reqs);
return -EINVAL;
+ }
+ xas_store(&xas, NULL);
+ xa_unlock(&cache->reqs);
trace_cachefiles_ondemand_cread(object, id);
complete(&req->done);
@@ -126,6 +131,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
unsigned long id;
long size;
int ret;
+ XA_STATE(xas, &cache->reqs, 0);
if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags))
return -EOPNOTSUPP;
@@ -149,9 +155,16 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
if (ret)
return ret;
- req = xa_erase(&cache->reqs, id);
- if (!req)
+ xa_lock(&cache->reqs);
+ xas.xa_index = id;
+ req = xas_load(&xas);
+ if (!req || req->msg.opcode != CACHEFILES_OP_OPEN ||
+ !req->object->ondemand->ondemand_id) {
+ xa_unlock(&cache->reqs);
return -EINVAL;
+ }
+ xas_store(&xas, NULL);
+ xa_unlock(&cache->reqs);
/* fail OPEN request if copen format is invalid */
ret = kstrtol(psize, 0, &size);
--
2.39.2
From: Baokun Li <[email protected]>
Now every time the daemon reads an open request, it requests a new anon fd
and ondemand_id. With the introduction of "restore", it is possible to read
the same open request more than once, and therefore have multiple anon fd's
for the same object.
To avoid this, allocate a new anon fd only if no anon fd has been allocated
(ondemand_id == 0) or if the previously allocated anon fd has been closed
(ondemand_id == -1). Returns an error if ondemand_id is valid, letting the
daemon know that the current userland restore logic is abnormal and needs
to be checked.
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/ondemand.c | 34 ++++++++++++++++++++++++++++------
1 file changed, 28 insertions(+), 6 deletions(-)
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index b5e6a851ef04..0cf63bfedc9e 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -14,11 +14,18 @@ static int cachefiles_ondemand_fd_release(struct inode *inode,
struct file *file)
{
struct cachefiles_object *object = file->private_data;
- struct cachefiles_cache *cache = object->volume->cache;
- struct cachefiles_ondemand_info *info = object->ondemand;
+ struct cachefiles_cache *cache;
+ struct cachefiles_ondemand_info *info;
int object_id;
struct cachefiles_req *req;
- XA_STATE(xas, &cache->reqs, 0);
+ XA_STATE(xas, NULL, 0);
+
+ if (!object)
+ return 0;
+
+ info = object->ondemand;
+ cache = object->volume->cache;
+ xas.xa = &cache->reqs;
xa_lock(&cache->reqs);
spin_lock(&info->lock);
@@ -269,22 +276,39 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
goto err_put_fd;
}
+ spin_lock(&object->ondemand->lock);
+ if (object->ondemand->ondemand_id > 0) {
+ spin_unlock(&object->ondemand->lock);
+ ret = -EEXIST;
+ /* Avoid performing cachefiles_ondemand_fd_release(). */
+ file->private_data = NULL;
+ goto err_put_file;
+ }
+
file->f_mode |= FMODE_PWRITE | FMODE_LSEEK;
fd_install(fd, file);
load = (void *)req->msg.data;
load->fd = fd;
object->ondemand->ondemand_id = object_id;
+ spin_unlock(&object->ondemand->lock);
cachefiles_get_unbind_pincount(cache);
trace_cachefiles_ondemand_open(object, &req->msg, load);
return 0;
+err_put_file:
+ fput(file);
err_put_fd:
put_unused_fd(fd);
err_free_id:
xa_erase(&cache->ondemand_ids, object_id);
err:
+ spin_lock(&object->ondemand->lock);
+ /* Avoid marking an opened object as closed. */
+ if (object->ondemand->ondemand_id <= 0)
+ cachefiles_ondemand_set_object_close(object);
+ spin_unlock(&object->ondemand->lock);
cachefiles_put_object(object, cachefiles_obj_put_ondemand_fd);
return ret;
}
@@ -367,10 +391,8 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
if (msg->opcode == CACHEFILES_OP_OPEN) {
ret = cachefiles_ondemand_get_fd(req);
- if (ret) {
- cachefiles_ondemand_set_object_close(req->object);
+ if (ret)
goto out;
- }
}
msg->msg_id = xas.xa_index;
--
2.39.2
From: Baokun Li <[email protected]>
The following concurrency may cause a read request to fail to be completed
and result in a hung:
t1 | t2
---------------------------------------------------------
cachefiles_ondemand_copen
req = xa_erase(&cache->reqs, id)
// Anon fd is maliciously closed.
cachefiles_ondemand_fd_release
xa_lock(&cache->reqs)
cachefiles_ondemand_set_object_close(object)
xa_unlock(&cache->reqs)
cachefiles_ondemand_set_object_open
// No one will ever close it again.
cachefiles_ondemand_daemon_read
cachefiles_ondemand_select_req
// Get a read req but its fd is already closed.
// The daemon can't issue a cread ioctl with an closed fd, then hung.
So add spin_lock for cachefiles_ondemand_info to protect ondemand_id and
state, thus we can avoid the above problem in cachefiles_ondemand_copen()
by using ondemand_id to determine if fd has been released.
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/internal.h | 1 +
fs/cachefiles/ondemand.c | 16 +++++++++++++++-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
index 7745b8abc3aa..45c8bed60538 100644
--- a/fs/cachefiles/internal.h
+++ b/fs/cachefiles/internal.h
@@ -55,6 +55,7 @@ struct cachefiles_ondemand_info {
int ondemand_id;
enum cachefiles_object_state state;
struct cachefiles_object *object;
+ spinlock_t lock;
};
/*
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index 898fab68332b..b5e6a851ef04 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -16,13 +16,16 @@ static int cachefiles_ondemand_fd_release(struct inode *inode,
struct cachefiles_object *object = file->private_data;
struct cachefiles_cache *cache = object->volume->cache;
struct cachefiles_ondemand_info *info = object->ondemand;
- int object_id = info->ondemand_id;
+ int object_id;
struct cachefiles_req *req;
XA_STATE(xas, &cache->reqs, 0);
xa_lock(&cache->reqs);
+ spin_lock(&info->lock);
+ object_id = info->ondemand_id;
info->ondemand_id = CACHEFILES_ONDEMAND_ID_CLOSED;
cachefiles_ondemand_set_object_close(object);
+ spin_unlock(&info->lock);
/* Only flush CACHEFILES_REQ_NEW marked req to avoid race with daemon_read */
xas_for_each_marked(&xas, req, ULONG_MAX, CACHEFILES_REQ_NEW) {
@@ -127,6 +130,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
{
struct cachefiles_req *req;
struct fscache_cookie *cookie;
+ struct cachefiles_ondemand_info *info;
char *pid, *psize;
unsigned long id;
long size;
@@ -185,6 +189,14 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
goto out;
}
+ info = req->object->ondemand;
+ spin_lock(&info->lock);
+ /* The anonymous fd was closed before copen ? */
+ if (info->ondemand_id == CACHEFILES_ONDEMAND_ID_CLOSED) {
+ spin_unlock(&info->lock);
+ req->error = -EBADFD;
+ goto out;
+ }
cookie = req->object->cookie;
cookie->object_size = size;
if (size)
@@ -194,6 +206,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
trace_cachefiles_ondemand_copen(req->object, id, size);
cachefiles_ondemand_set_object_open(req->object);
+ spin_unlock(&info->lock);
wake_up_all(&cache->daemon_pollwq);
out:
@@ -596,6 +609,7 @@ int cachefiles_ondemand_init_obj_info(struct cachefiles_object *object,
return -ENOMEM;
object->ondemand->object = object;
+ spin_lock_init(&object->ondemand->lock);
INIT_WORK(&object->ondemand->ondemand_work, ondemand_object_worker);
return 0;
}
--
2.39.2
From: Baokun Li <[email protected]>
In ondemand mode, when the daemon is processing an open request, if the
kernel flags the cache as CACHEFILES_DEAD, the cachefiles_daemon_write()
will always return -EIO, so the daemon can't pass the copen to the kernel.
Then the kernel process that is waiting for the copen triggers a hung_task.
Since the DEAD state is irreversible, it can only be exited by reopening
/dev/cachefiles. Therefore, after calling cachefiles_io_error() to mark
the cache as CACHEFILES_DEAD, if in ondemand mode, flush all requests to
avoid the above hungtask. We may still be able to read some of the cached
data before releasing the fd of /dev/cachefiles.
Note that this relies on the patch that adds reference counting to the req,
otherwise it may UAF.
Fixes: c8383054506c ("cachefiles: notify the user daemon when looking up cookie")
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/daemon.c | 2 +-
fs/cachefiles/internal.h | 3 +++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c
index ccb7b707ea4b..06cdf1a8a16f 100644
--- a/fs/cachefiles/daemon.c
+++ b/fs/cachefiles/daemon.c
@@ -133,7 +133,7 @@ static int cachefiles_daemon_open(struct inode *inode, struct file *file)
return 0;
}
-static void cachefiles_flush_reqs(struct cachefiles_cache *cache)
+void cachefiles_flush_reqs(struct cachefiles_cache *cache)
{
struct xarray *xa = &cache->reqs;
struct cachefiles_req *req;
diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
index 45c8bed60538..6845a90cdfcc 100644
--- a/fs/cachefiles/internal.h
+++ b/fs/cachefiles/internal.h
@@ -188,6 +188,7 @@ extern int cachefiles_has_space(struct cachefiles_cache *cache,
* daemon.c
*/
extern const struct file_operations cachefiles_daemon_fops;
+extern void cachefiles_flush_reqs(struct cachefiles_cache *cache);
extern void cachefiles_get_unbind_pincount(struct cachefiles_cache *cache);
extern void cachefiles_put_unbind_pincount(struct cachefiles_cache *cache);
@@ -426,6 +427,8 @@ do { \
pr_err("I/O Error: " FMT"\n", ##__VA_ARGS__); \
fscache_io_error((___cache)->cache); \
set_bit(CACHEFILES_DEAD, &(___cache)->flags); \
+ if (cachefiles_in_ondemand_mode(___cache)) \
+ cachefiles_flush_reqs(___cache); \
} while (0)
#define cachefiles_io_error_obj(object, FMT, ...) \
--
2.39.2
From: Zizhi Wo <[email protected]>
If copen is maliciously called in the user mode, it may delete the request
corresponding to the random id. And the request may have not been read yet.
Note that when the object is set to reopen, the open request will be done
with the still reopen state in above case. As a result, the request
corresponding to this object is always skipped in select_req function, so
the read request is never completed and blocks other process.
Fix this issue by simply set object to close if its id < 0 in copen.
Signed-off-by: Zizhi Wo <[email protected]>
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/ondemand.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index 7c2d43104120..673e7ad52041 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -182,6 +182,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
xas_store(&xas, NULL);
xa_unlock(&cache->reqs);
+ info = req->object->ondemand;
/* fail OPEN request if copen format is invalid */
ret = kstrtol(psize, 0, &size);
if (ret) {
@@ -201,7 +202,6 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
goto out;
}
- info = req->object->ondemand;
spin_lock(&info->lock);
/* The anonymous fd was closed before copen ? */
if (info->ondemand_id == CACHEFILES_ONDEMAND_ID_CLOSED) {
@@ -222,6 +222,11 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
wake_up_all(&cache->daemon_pollwq);
out:
+ spin_lock(&info->lock);
+ /* Need to set object close to avoid reopen status continuing */
+ if (info->ondemand_id == CACHEFILES_ONDEMAND_ID_CLOSED)
+ cachefiles_ondemand_set_object_close(req->object);
+ spin_unlock(&info->lock);
complete(&req->done);
return ret;
}
--
2.39.2
From: Baokun Li <[email protected]>
After installing the anonymous fd, we can now see it in userland and close
it. However, at this point we may not have gotten the reference count of
the cache, but we will put it during colse fd, so this may cause a cache
UAF.
To avoid this, we will make the anonymous fd accessible to the userland by
executing fd_install() after copy_to_user() has succeeded, and by this
point we must have already grabbed the reference count of the cache.
Suggested-by: Hou Tao <[email protected]>
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/ondemand.c | 53 +++++++++++++++++++++++++---------------
1 file changed, 33 insertions(+), 20 deletions(-)
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index 0cf63bfedc9e..7c2d43104120 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -4,6 +4,11 @@
#include <linux/uio.h>
#include "internal.h"
+struct anon_file {
+ struct file *file;
+ int fd;
+};
+
static inline void cachefiles_req_put(struct cachefiles_req *req)
{
if (refcount_dec_and_test(&req->ref))
@@ -244,14 +249,14 @@ int cachefiles_ondemand_restore(struct cachefiles_cache *cache, char *args)
return 0;
}
-static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
+static int cachefiles_ondemand_get_fd(struct cachefiles_req *req,
+ struct anon_file *anon_file)
{
struct cachefiles_object *object;
struct cachefiles_cache *cache;
struct cachefiles_open *load;
- struct file *file;
u32 object_id;
- int ret, fd;
+ int ret;
object = cachefiles_grab_object(req->object,
cachefiles_obj_get_ondemand_fd);
@@ -263,16 +268,16 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
if (ret < 0)
goto err;
- fd = get_unused_fd_flags(O_WRONLY);
- if (fd < 0) {
- ret = fd;
+ anon_file->fd = get_unused_fd_flags(O_WRONLY);
+ if (anon_file->fd < 0) {
+ ret = anon_file->fd;
goto err_free_id;
}
- file = anon_inode_getfile("[cachefiles]", &cachefiles_ondemand_fd_fops,
- object, O_WRONLY);
- if (IS_ERR(file)) {
- ret = PTR_ERR(file);
+ anon_file->file = anon_inode_getfile("[cachefiles]",
+ &cachefiles_ondemand_fd_fops, object, O_WRONLY);
+ if (IS_ERR(anon_file->file)) {
+ ret = PTR_ERR(anon_file->file);
goto err_put_fd;
}
@@ -281,15 +286,14 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
spin_unlock(&object->ondemand->lock);
ret = -EEXIST;
/* Avoid performing cachefiles_ondemand_fd_release(). */
- file->private_data = NULL;
+ anon_file->file->private_data = NULL;
goto err_put_file;
}
- file->f_mode |= FMODE_PWRITE | FMODE_LSEEK;
- fd_install(fd, file);
+ anon_file->file->f_mode |= FMODE_PWRITE | FMODE_LSEEK;
load = (void *)req->msg.data;
- load->fd = fd;
+ load->fd = anon_file->fd;
object->ondemand->ondemand_id = object_id;
spin_unlock(&object->ondemand->lock);
@@ -298,9 +302,11 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
return 0;
err_put_file:
- fput(file);
+ fput(anon_file->file);
+ anon_file->file = NULL;
err_put_fd:
- put_unused_fd(fd);
+ put_unused_fd(anon_file->fd);
+ anon_file->fd = ret;
err_free_id:
xa_erase(&cache->ondemand_ids, object_id);
err:
@@ -357,6 +363,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
struct cachefiles_msg *msg;
size_t n;
int ret = 0;
+ struct anon_file anon_file;
XA_STATE(xas, &cache->reqs, cache->req_id_next);
xa_lock(&cache->reqs);
@@ -390,7 +397,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
xa_unlock(&cache->reqs);
if (msg->opcode == CACHEFILES_OP_OPEN) {
- ret = cachefiles_ondemand_get_fd(req);
+ ret = cachefiles_ondemand_get_fd(req, &anon_file);
if (ret)
goto out;
}
@@ -398,10 +405,16 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
msg->msg_id = xas.xa_index;
msg->object_id = req->object->ondemand->ondemand_id;
- if (copy_to_user(_buffer, msg, n) != 0) {
+ if (copy_to_user(_buffer, msg, n) != 0)
ret = -EFAULT;
- if (msg->opcode == CACHEFILES_OP_OPEN)
- close_fd(((struct cachefiles_open *)msg->data)->fd);
+
+ if (msg->opcode == CACHEFILES_OP_OPEN) {
+ if (ret < 0) {
+ fput(anon_file.file);
+ put_unused_fd(anon_file.fd);
+ goto out;
+ }
+ fd_install(anon_file.fd, anon_file.file);
}
out:
cachefiles_put_object(req->object, cachefiles_obj_put_read_req);
--
2.39.2
From: Baokun Li <[email protected]>
We got the following issue in a fuzz test of randomly issuing the restore
command:
==================================================================
BUG: KASAN: slab-use-after-free in cachefiles_ondemand_daemon_read+0xb41/0xb60
Read of size 8 at addr ffff888122e84088 by task ondemand-04-dae/963
CPU: 13 PID: 963 Comm: ondemand-04-dae Not tainted 6.8.0-dirty #564
Call Trace:
kasan_report+0x93/0xc0
cachefiles_ondemand_daemon_read+0xb41/0xb60
vfs_read+0x169/0xb50
ksys_read+0xf5/0x1e0
Allocated by task 116:
kmem_cache_alloc+0x140/0x3a0
cachefiles_lookup_cookie+0x140/0xcd0
fscache_cookie_state_machine+0x43c/0x1230
[...]
Freed by task 792:
kmem_cache_free+0xfe/0x390
cachefiles_put_object+0x241/0x480
fscache_cookie_state_machine+0x5c8/0x1230
[...]
==================================================================
Following is the process that triggers the issue:
mount | daemon_thread1 | daemon_thread2
------------------------------------------------------------
cachefiles_withdraw_cookie
cachefiles_ondemand_clean_object(object)
cachefiles_ondemand_send_req
REQ_A = kzalloc(sizeof(*req) + data_len)
wait_for_completion(&REQ_A->done)
cachefiles_daemon_read
cachefiles_ondemand_daemon_read
REQ_A = cachefiles_ondemand_select_req
msg->object_id = req->object->ondemand->ondemand_id
------ restore ------
cachefiles_ondemand_restore
xas_for_each(&xas, req, ULONG_MAX)
xas_set_mark(&xas, CACHEFILES_REQ_NEW)
cachefiles_daemon_read
cachefiles_ondemand_daemon_read
REQ_A = cachefiles_ondemand_select_req
copy_to_user(_buffer, msg, n)
xa_erase(&cache->reqs, id)
complete(&REQ_A->done)
------ close(fd) ------
cachefiles_ondemand_fd_release
cachefiles_put_object
cachefiles_put_object
kmem_cache_free(cachefiles_object_jar, object)
REQ_A->object->ondemand->ondemand_id
// object UAF !!!
When we see the request within xa_lock, req->object must not have been
freed yet, so grab the reference count of object before xa_unlock to
avoid the above issue.
Fixes: 0a7e54c1959c ("cachefiles: resend an open request if the read request's object is closed")
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/ondemand.c | 2 ++
include/trace/events/cachefiles.h | 6 +++++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index 56d12fe4bf73..bb94ef6a6f61 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -336,6 +336,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
xas_clear_mark(&xas, CACHEFILES_REQ_NEW);
cache->req_id_next = xas.xa_index + 1;
refcount_inc(&req->ref);
+ cachefiles_grab_object(req->object, cachefiles_obj_get_read_req);
xa_unlock(&cache->reqs);
if (msg->opcode == CACHEFILES_OP_OPEN) {
@@ -355,6 +356,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
close_fd(((struct cachefiles_open *)msg->data)->fd);
}
out:
+ cachefiles_put_object(req->object, cachefiles_obj_put_read_req);
/* Remove error request and CLOSE request has no reply */
if (ret || msg->opcode == CACHEFILES_OP_CLOSE) {
xas_reset(&xas);
diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cachefiles.h
index cf4b98b9a9ed..119a823fb5a0 100644
--- a/include/trace/events/cachefiles.h
+++ b/include/trace/events/cachefiles.h
@@ -33,6 +33,8 @@ enum cachefiles_obj_ref_trace {
cachefiles_obj_see_withdrawal,
cachefiles_obj_get_ondemand_fd,
cachefiles_obj_put_ondemand_fd,
+ cachefiles_obj_get_read_req,
+ cachefiles_obj_put_read_req,
};
enum fscache_why_object_killed {
@@ -127,7 +129,9 @@ enum cachefiles_error_trace {
EM(cachefiles_obj_see_lookup_cookie, "SEE lookup_cookie") \
EM(cachefiles_obj_see_lookup_failed, "SEE lookup_failed") \
EM(cachefiles_obj_see_withdraw_cookie, "SEE withdraw_cookie") \
- E_(cachefiles_obj_see_withdrawal, "SEE withdrawal")
+ EM(cachefiles_obj_see_withdrawal, "SEE withdrawal") \
+ EM(cachefiles_obj_get_read_req, "GET read_req") \
+ E_(cachefiles_obj_put_read_req, "PUT read_req")
#define cachefiles_coherency_traces \
EM(cachefiles_coherency_check_aux, "BAD aux ") \
--
2.39.2
From: Baokun Li <[email protected]>
Replacing wait_for_completion() with wait_for_completion_killable() in
cachefiles_ondemand_send_req() allows us to kill processes that might
trigger a hunk_task if the daemon is abnormal.
But now only CACHEFILES_OP_READ is killable, because OP_CLOSE and OP_OPEN
is initiated from kworker context and the signal is prohibited in these
kworker.
Note that when the req in xas changes, i.e. xas_load(&xas) != req, it
means that a process will complete the current request soon, so wait
again for the request to be completed.
Suggested-by: Hou Tao <[email protected]>
Signed-off-by: Baokun Li <[email protected]>
---
fs/cachefiles/ondemand.c | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index 673e7ad52041..b766430f4abf 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -525,8 +525,25 @@ static int cachefiles_ondemand_send_req(struct cachefiles_object *object,
goto out;
wake_up_all(&cache->daemon_pollwq);
- wait_for_completion(&req->done);
- ret = req->error;
+wait:
+ ret = wait_for_completion_killable(&req->done);
+ if (!ret) {
+ ret = req->error;
+ } else {
+ xas_reset(&xas);
+ xas_lock(&xas);
+ if (xas_load(&xas) == req) {
+ xas_store(&xas, NULL);
+ ret = -EINTR;
+ }
+ xas_unlock(&xas);
+
+ /* Someone will complete it soon. */
+ if (ret != -EINTR) {
+ cpu_relax();
+ goto wait;
+ }
+ }
cachefiles_req_put(req);
return ret;
out:
--
2.39.2
在 2024/4/24 11:39, [email protected] 写道:
> From: Baokun Li <[email protected]>
>
> We got the following issue in a fuzz test of randomly issuing the restore
> command:
>
> ==================================================================
> BUG: KASAN: slab-use-after-free in cachefiles_ondemand_daemon_read+0x609/0xab0
> Write of size 4 at addr ffff888109164a80 by task ondemand-04-dae/4962
>
> CPU: 11 PID: 4962 Comm: ondemand-04-dae Not tainted 6.8.0-rc7-dirty #542
> Call Trace:
> kasan_report+0x94/0xc0
> cachefiles_ondemand_daemon_read+0x609/0xab0
> vfs_read+0x169/0xb50
> ksys_read+0xf5/0x1e0
>
> Allocated by task 626:
> __kmalloc+0x1df/0x4b0
> cachefiles_ondemand_send_req+0x24d/0x690
> cachefiles_create_tmpfile+0x249/0xb30
> cachefiles_create_file+0x6f/0x140
> cachefiles_look_up_object+0x29c/0xa60
> cachefiles_lookup_cookie+0x37d/0xca0
> fscache_cookie_state_machine+0x43c/0x1230
> [...]
>
> Freed by task 626:
> kfree+0xf1/0x2c0
> cachefiles_ondemand_send_req+0x568/0x690
> cachefiles_create_tmpfile+0x249/0xb30
> cachefiles_create_file+0x6f/0x140
> cachefiles_look_up_object+0x29c/0xa60
> cachefiles_lookup_cookie+0x37d/0xca0
> fscache_cookie_state_machine+0x43c/0x1230
> [...]
> ==================================================================
>
> Following is the process that triggers the issue:
>
> mount | daemon_thread1 | daemon_thread2
> ------------------------------------------------------------
> cachefiles_ondemand_init_object
> cachefiles_ondemand_send_req
> REQ_A = kzalloc(sizeof(*req) + data_len)
> wait_for_completion(&REQ_A->done)
>
> cachefiles_daemon_read
> cachefiles_ondemand_daemon_read
> REQ_A = cachefiles_ondemand_select_req
> cachefiles_ondemand_get_fd
> copy_to_user(_buffer, msg, n)
> process_open_req(REQ_A)
> ------ restore ------
> cachefiles_ondemand_restore
> xas_for_each(&xas, req, ULONG_MAX)
> xas_set_mark(&xas, CACHEFILES_REQ_NEW);
>
> cachefiles_daemon_read
> cachefiles_ondemand_daemon_read
> REQ_A = cachefiles_ondemand_select_req
>
> write(devfd, ("copen %u,%llu", msg->msg_id, size));
> cachefiles_ondemand_copen
> xa_erase(&cache->reqs, id)
> complete(&REQ_A->done)
> kfree(REQ_A)
> cachefiles_ondemand_get_fd(REQ_A)
> fd = get_unused_fd_flags
> file = anon_inode_getfile
> fd_install(fd, file)
> load = (void *)REQ_A->msg.data;
> load->fd = fd;
> // load UAF !!!
>
> This issue is caused by issuing a restore command when the daemon is still
> alive, which results in a request being processed multiple times thus
> triggering a UAF. So to avoid this problem, add an additional reference
> count to cachefiles_req, which is held while waiting and reading, and then
> released when the waiting and reading is over.
Hi Baokun,
Thank you for catching this issue. Shall we fix this by following steps:
cachefiles_ondemand_fd_release()
xas_for_each(req)
if req is not CACHEFILES_OP_READ
flush
cachefiles_ondemand_restore()
xas_for_each(req)
if req is not CACHEFILES_REQ_NEW && op is (OPEN or CLOSE)
reset req to CACHEFILES_REQ_NEW
By implementing these steps:
1. In real daemon failover case: only pending read reqs will be
reserved. cachefiles_ondemand_select_req will reopen the object by
processing READ req.
2. In daemon alive case: Only read reqs will be reset to
CACHEFILES_REQ_NEW.
>
> Note that since there is only one reference count for waiting, we need to
> avoid the same request being completed multiple times, so we can only
> complete the request if it is successfully removed from the xarray.
>
> Fixes: e73fa11a356c ("cachefiles: add restore command to recover inflight ondemand read requests")
> Suggested-by: Hou Tao <[email protected]>
> Signed-off-by: Baokun Li <[email protected]>
> ---
> fs/cachefiles/internal.h | 1 +
> fs/cachefiles/ondemand.c | 44 ++++++++++++++++++++++------------------
> 2 files changed, 25 insertions(+), 20 deletions(-)
>
> diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
> index d33169f0018b..7745b8abc3aa 100644
> --- a/fs/cachefiles/internal.h
> +++ b/fs/cachefiles/internal.h
> @@ -138,6 +138,7 @@ static inline bool cachefiles_in_ondemand_mode(struct cachefiles_cache *cache)
> struct cachefiles_req {
> struct cachefiles_object *object;
> struct completion done;
> + refcount_t ref;
> int error;
> struct cachefiles_msg msg;
> };
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index fd49728d8bae..56d12fe4bf73 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -4,6 +4,12 @@
> #include <linux/uio.h>
> #include "internal.h"
>
> +static inline void cachefiles_req_put(struct cachefiles_req *req)
> +{
> + if (refcount_dec_and_test(&req->ref))
> + kfree(req);
> +}
> +
> static int cachefiles_ondemand_fd_release(struct inode *inode,
> struct file *file)
> {
> @@ -299,7 +305,6 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
> {
> struct cachefiles_req *req;
> struct cachefiles_msg *msg;
> - unsigned long id = 0;
> size_t n;
> int ret = 0;
> XA_STATE(xas, &cache->reqs, cache->req_id_next);
> @@ -330,41 +335,39 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>
> xas_clear_mark(&xas, CACHEFILES_REQ_NEW);
> cache->req_id_next = xas.xa_index + 1;
> + refcount_inc(&req->ref);
> xa_unlock(&cache->reqs);
>
> - id = xas.xa_index;
> -
> if (msg->opcode == CACHEFILES_OP_OPEN) {
> ret = cachefiles_ondemand_get_fd(req);
> if (ret) {
> cachefiles_ondemand_set_object_close(req->object);
> - goto error;
> + goto out;
> }
> }
>
> - msg->msg_id = id;
> + msg->msg_id = xas.xa_index;
> msg->object_id = req->object->ondemand->ondemand_id;
>
> if (copy_to_user(_buffer, msg, n) != 0) {
> ret = -EFAULT;
> if (msg->opcode == CACHEFILES_OP_OPEN)
> close_fd(((struct cachefiles_open *)msg->data)->fd);
> - goto error;
> }
> -
> - /* CLOSE request has no reply */
> - if (msg->opcode == CACHEFILES_OP_CLOSE) {
> - xa_erase(&cache->reqs, id);
> - complete(&req->done);
> +out:
> + /* Remove error request and CLOSE request has no reply */
> + if (ret || msg->opcode == CACHEFILES_OP_CLOSE) {
> + xas_reset(&xas);
> + xas_lock(&xas);
> + if (xas_load(&xas) == req) {
> + req->error = ret;
> + complete(&req->done);
> + xas_store(&xas, NULL);
> + }
> + xas_unlock(&xas);
> }
> -
> - return n;
> -
> -error:
> - xa_erase(&cache->reqs, id);
> - req->error = ret;
> - complete(&req->done);
> - return ret;
> + cachefiles_req_put(req);
> + return ret ? ret : n;
> }
>
> typedef int (*init_req_fn)(struct cachefiles_req *req, void *private);
> @@ -394,6 +397,7 @@ static int cachefiles_ondemand_send_req(struct cachefiles_object *object,
> goto out;
> }
>
> + refcount_set(&req->ref, 1);
> req->object = object;
> init_completion(&req->done);
> req->msg.opcode = opcode;
> @@ -455,7 +459,7 @@ static int cachefiles_ondemand_send_req(struct cachefiles_object *object,
> wake_up_all(&cache->daemon_pollwq);
> wait_for_completion(&req->done);
> ret = req->error;
> - kfree(req);
> + cachefiles_req_put(req);
> return ret;
> out:
> /* Reset the object to close state in error handling path.
Hi Jia,
On 2024/4/24 22:55, Jia Zhu wrote:
>
>
> 在 2024/4/24 11:39, [email protected] 写道:
>> From: Baokun Li <[email protected]>
>>
>> We got the following issue in a fuzz test of randomly issuing the
>> restore
>> command:
>>
>> ==================================================================
>> BUG: KASAN: slab-use-after-free in
>> cachefiles_ondemand_daemon_read+0x609/0xab0
>> Write of size 4 at addr ffff888109164a80 by task ondemand-04-dae/4962
>>
>> CPU: 11 PID: 4962 Comm: ondemand-04-dae Not tainted 6.8.0-rc7-dirty #542
>> Call Trace:
>> kasan_report+0x94/0xc0
>> cachefiles_ondemand_daemon_read+0x609/0xab0
>> vfs_read+0x169/0xb50
>> ksys_read+0xf5/0x1e0
>>
>> Allocated by task 626:
>> __kmalloc+0x1df/0x4b0
>> cachefiles_ondemand_send_req+0x24d/0x690
>> cachefiles_create_tmpfile+0x249/0xb30
>> cachefiles_create_file+0x6f/0x140
>> cachefiles_look_up_object+0x29c/0xa60
>> cachefiles_lookup_cookie+0x37d/0xca0
>> fscache_cookie_state_machine+0x43c/0x1230
>> [...]
>>
>> Freed by task 626:
>> kfree+0xf1/0x2c0
>> cachefiles_ondemand_send_req+0x568/0x690
>> cachefiles_create_tmpfile+0x249/0xb30
>> cachefiles_create_file+0x6f/0x140
>> cachefiles_look_up_object+0x29c/0xa60
>> cachefiles_lookup_cookie+0x37d/0xca0
>> fscache_cookie_state_machine+0x43c/0x1230
>> [...]
>> ==================================================================
>>
>> Following is the process that triggers the issue:
>>
>> mount | daemon_thread1 | daemon_thread2
>> ------------------------------------------------------------
>> cachefiles_ondemand_init_object
>> cachefiles_ondemand_send_req
>> REQ_A = kzalloc(sizeof(*req) + data_len)
>> wait_for_completion(&REQ_A->done)
>>
>> cachefiles_daemon_read
>> cachefiles_ondemand_daemon_read
>> REQ_A = cachefiles_ondemand_select_req
>> cachefiles_ondemand_get_fd
>> copy_to_user(_buffer, msg, n)
>> process_open_req(REQ_A)
>> ------ restore ------
>> cachefiles_ondemand_restore
>> xas_for_each(&xas, req, ULONG_MAX)
>> xas_set_mark(&xas,
>> CACHEFILES_REQ_NEW);
>>
>> cachefiles_daemon_read
>> cachefiles_ondemand_daemon_read
>> REQ_A =
>> cachefiles_ondemand_select_req
>>
>> write(devfd, ("copen %u,%llu", msg->msg_id, size));
>> cachefiles_ondemand_copen
>> xa_erase(&cache->reqs, id)
>> complete(&REQ_A->done)
>> kfree(REQ_A)
>> cachefiles_ondemand_get_fd(REQ_A)
>> fd = get_unused_fd_flags
>> file = anon_inode_getfile
>> fd_install(fd, file)
>> load = (void *)REQ_A->msg.data;
>> load->fd = fd;
>> // load UAF !!!
>>
>> This issue is caused by issuing a restore command when the daemon is
>> still
>> alive, which results in a request being processed multiple times thus
>> triggering a UAF. So to avoid this problem, add an additional reference
>> count to cachefiles_req, which is held while waiting and reading, and
>> then
>> released when the waiting and reading is over.
>
> Hi Baokun,
> Thank you for catching this issue. Shall we fix this by following steps:
> cachefiles_ondemand_fd_release()
> xas_for_each(req)
> if req is not CACHEFILES_OP_READ
> flush
>
> cachefiles_ondemand_restore()
> xas_for_each(req)
> if req is not CACHEFILES_REQ_NEW && op is (OPEN or CLOSE)
> reset req to CACHEFILES_REQ_NEW
>
> By implementing these steps:
> 1. In real daemon failover case: only pending read reqs will be
> reserved. cachefiles_ondemand_select_req will reopen the object by
> processing READ req.
> 2. In daemon alive case: Only read reqs will be reset to
> CACHEFILES_REQ_NEW.
>
This way, in the fialover case, some processes that are being mounted
will fail, which is contrary to our intention of making the user senseless.
In my opinion, it's better to keep making users senseless.
Thanks,
Baokun
>
>>
>> Note that since there is only one reference count for waiting, we
>> need to
>> avoid the same request being completed multiple times, so we can only
>> complete the request if it is successfully removed from the xarray.
>>
>> Fixes: e73fa11a356c ("cachefiles: add restore command to recover
>> inflight ondemand read requests")
>> Suggested-by: Hou Tao <[email protected]>
>> Signed-off-by: Baokun Li <[email protected]>
>> ---
>> fs/cachefiles/internal.h | 1 +
>> fs/cachefiles/ondemand.c | 44 ++++++++++++++++++++++------------------
>> 2 files changed, 25 insertions(+), 20 deletions(-)
>>
>> diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
>> index d33169f0018b..7745b8abc3aa 100644
>> --- a/fs/cachefiles/internal.h
>> +++ b/fs/cachefiles/internal.h
>> @@ -138,6 +138,7 @@ static inline bool
>> cachefiles_in_ondemand_mode(struct cachefiles_cache *cache)
>> struct cachefiles_req {
>> struct cachefiles_object *object;
>> struct completion done;
>> + refcount_t ref;
>> int error;
>> struct cachefiles_msg msg;
>> };
>> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
>> index fd49728d8bae..56d12fe4bf73 100644
>> --- a/fs/cachefiles/ondemand.c
>> +++ b/fs/cachefiles/ondemand.c
>> @@ -4,6 +4,12 @@
>> #include <linux/uio.h>
>> #include "internal.h"
>> +static inline void cachefiles_req_put(struct cachefiles_req *req)
>> +{
>> + if (refcount_dec_and_test(&req->ref))
>> + kfree(req);
>> +}
>> +
>> static int cachefiles_ondemand_fd_release(struct inode *inode,
>> struct file *file)
>> {
>> @@ -299,7 +305,6 @@ ssize_t cachefiles_ondemand_daemon_read(struct
>> cachefiles_cache *cache,
>> {
>> struct cachefiles_req *req;
>> struct cachefiles_msg *msg;
>> - unsigned long id = 0;
>> size_t n;
>> int ret = 0;
>> XA_STATE(xas, &cache->reqs, cache->req_id_next);
>> @@ -330,41 +335,39 @@ ssize_t cachefiles_ondemand_daemon_read(struct
>> cachefiles_cache *cache,
>> xas_clear_mark(&xas, CACHEFILES_REQ_NEW);
>> cache->req_id_next = xas.xa_index + 1;
>> + refcount_inc(&req->ref);
>> xa_unlock(&cache->reqs);
>> - id = xas.xa_index;
>> -
>> if (msg->opcode == CACHEFILES_OP_OPEN) {
>> ret = cachefiles_ondemand_get_fd(req);
>> if (ret) {
>> cachefiles_ondemand_set_object_close(req->object);
>> - goto error;
>> + goto out;
>> }
>> }
>> - msg->msg_id = id;
>> + msg->msg_id = xas.xa_index;
>> msg->object_id = req->object->ondemand->ondemand_id;
>> if (copy_to_user(_buffer, msg, n) != 0) {
>> ret = -EFAULT;
>> if (msg->opcode == CACHEFILES_OP_OPEN)
>> close_fd(((struct cachefiles_open *)msg->data)->fd);
>> - goto error;
>> }
>> -
>> - /* CLOSE request has no reply */
>> - if (msg->opcode == CACHEFILES_OP_CLOSE) {
>> - xa_erase(&cache->reqs, id);
>> - complete(&req->done);
>> +out:
>> + /* Remove error request and CLOSE request has no reply */
>> + if (ret || msg->opcode == CACHEFILES_OP_CLOSE) {
>> + xas_reset(&xas);
>> + xas_lock(&xas);
>> + if (xas_load(&xas) == req) {
>> + req->error = ret;
>> + complete(&req->done);
>> + xas_store(&xas, NULL);
>> + }
>> + xas_unlock(&xas);
>> }
>> -
>> - return n;
>> -
>> -error:
>> - xa_erase(&cache->reqs, id);
>> - req->error = ret;
>> - complete(&req->done);
>> - return ret;
>> + cachefiles_req_put(req);
>> + return ret ? ret : n;
>> }
>> typedef int (*init_req_fn)(struct cachefiles_req *req, void
>> *private);
>> @@ -394,6 +397,7 @@ static int cachefiles_ondemand_send_req(struct
>> cachefiles_object *object,
>> goto out;
>> }
>> + refcount_set(&req->ref, 1);
>> req->object = object;
>> init_completion(&req->done);
>> req->msg.opcode = opcode;
>> @@ -455,7 +459,7 @@ static int cachefiles_ondemand_send_req(struct
>> cachefiles_object *object,
>> wake_up_all(&cache->daemon_pollwq);
>> wait_for_completion(&req->done);
>> ret = req->error;
>> - kfree(req);
>> + cachefiles_req_put(req);
>> return ret;
>> out:
>> /* Reset the object to close state in error handling path.
在 2024/4/24 11:39, [email protected] 写道:
> From: Baokun Li <[email protected]>
>
> This prevents concurrency from causing access to a freed req.
>
> Signed-off-by: Baokun Li <[email protected]>
Reviewed-by: Jia Zhu <[email protected]>
> ---
> fs/cachefiles/daemon.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c
> index 6465e2574230..ccb7b707ea4b 100644
> --- a/fs/cachefiles/daemon.c
> +++ b/fs/cachefiles/daemon.c
> @@ -159,6 +159,7 @@ static void cachefiles_flush_reqs(struct cachefiles_cache *cache)
> xa_for_each(xa, index, req) {
> req->error = -EIO;
> complete(&req->done);
> + __xa_erase(xa, index);
> }
> xa_unlock(xa);
>
在 2024/4/24 11:39, [email protected] 写道:
> From: Baokun Li <[email protected]>
>
> The err_put_fd tag is only used once, so remove it to make the code more
> readable.
>
> Signed-off-by: Baokun Li <[email protected]>
Reviewed-by: Jia Zhu <[email protected]>
> ---
> fs/cachefiles/ondemand.c | 7 +++----
> 1 file changed, 3 insertions(+), 4 deletions(-)
>
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index 4ba42f1fa3b4..fd49728d8bae 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -347,7 +347,9 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>
> if (copy_to_user(_buffer, msg, n) != 0) {
> ret = -EFAULT;
> - goto err_put_fd;
> + if (msg->opcode == CACHEFILES_OP_OPEN)
> + close_fd(((struct cachefiles_open *)msg->data)->fd);
> + goto error;
> }
>
> /* CLOSE request has no reply */
> @@ -358,9 +360,6 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>
> return n;
>
> -err_put_fd:
> - if (msg->opcode == CACHEFILES_OP_OPEN)
> - close_fd(((struct cachefiles_open *)msg->data)->fd);
> error:
> xa_erase(&cache->reqs, id);
> req->error = ret;
在 2024/4/24 11:39, [email protected] 写道:
> From: Baokun Li <[email protected]>
>
> We got the following issue in a fuzz test of randomly issuing the restore
> command:
>
> ==================================================================
> BUG: KASAN: slab-use-after-free in cachefiles_ondemand_daemon_read+0x609/0xab0
> Write of size 4 at addr ffff888109164a80 by task ondemand-04-dae/4962
>
> CPU: 11 PID: 4962 Comm: ondemand-04-dae Not tainted 6.8.0-rc7-dirty #542
> Call Trace:
> kasan_report+0x94/0xc0
> cachefiles_ondemand_daemon_read+0x609/0xab0
> vfs_read+0x169/0xb50
> ksys_read+0xf5/0x1e0
>
> Allocated by task 626:
> __kmalloc+0x1df/0x4b0
> cachefiles_ondemand_send_req+0x24d/0x690
> cachefiles_create_tmpfile+0x249/0xb30
> cachefiles_create_file+0x6f/0x140
> cachefiles_look_up_object+0x29c/0xa60
> cachefiles_lookup_cookie+0x37d/0xca0
> fscache_cookie_state_machine+0x43c/0x1230
> [...]
>
> Freed by task 626:
> kfree+0xf1/0x2c0
> cachefiles_ondemand_send_req+0x568/0x690
> cachefiles_create_tmpfile+0x249/0xb30
> cachefiles_create_file+0x6f/0x140
> cachefiles_look_up_object+0x29c/0xa60
> cachefiles_lookup_cookie+0x37d/0xca0
> fscache_cookie_state_machine+0x43c/0x1230
> [...]
> ==================================================================
>
> Following is the process that triggers the issue:
>
> mount | daemon_thread1 | daemon_thread2
> ------------------------------------------------------------
> cachefiles_ondemand_init_object
> cachefiles_ondemand_send_req
> REQ_A = kzalloc(sizeof(*req) + data_len)
> wait_for_completion(&REQ_A->done)
>
> cachefiles_daemon_read
> cachefiles_ondemand_daemon_read
> REQ_A = cachefiles_ondemand_select_req
> cachefiles_ondemand_get_fd
> copy_to_user(_buffer, msg, n)
> process_open_req(REQ_A)
> ------ restore ------
> cachefiles_ondemand_restore
> xas_for_each(&xas, req, ULONG_MAX)
> xas_set_mark(&xas, CACHEFILES_REQ_NEW);
>
> cachefiles_daemon_read
> cachefiles_ondemand_daemon_read
> REQ_A = cachefiles_ondemand_select_req
>
> write(devfd, ("copen %u,%llu", msg->msg_id, size));
> cachefiles_ondemand_copen
> xa_erase(&cache->reqs, id)
> complete(&REQ_A->done)
> kfree(REQ_A)
> cachefiles_ondemand_get_fd(REQ_A)
> fd = get_unused_fd_flags
> file = anon_inode_getfile
> fd_install(fd, file)
> load = (void *)REQ_A->msg.data;
> load->fd = fd;
> // load UAF !!!
>
> This issue is caused by issuing a restore command when the daemon is still
> alive, which results in a request being processed multiple times thus
> triggering a UAF. So to avoid this problem, add an additional reference
> count to cachefiles_req, which is held while waiting and reading, and then
> released when the waiting and reading is over.
>
> Note that since there is only one reference count for waiting, we need to
> avoid the same request being completed multiple times, so we can only
> complete the request if it is successfully removed from the xarray.
>
> Fixes: e73fa11a356c ("cachefiles: add restore command to recover inflight ondemand read requests")
> Suggested-by: Hou Tao <[email protected]>
> Signed-off-by: Baokun Li <[email protected]>
Reviewed-by: Jia Zhu <[email protected]>
> ---
> fs/cachefiles/internal.h | 1 +
> fs/cachefiles/ondemand.c | 44 ++++++++++++++++++++++------------------
> 2 files changed, 25 insertions(+), 20 deletions(-)
>
> diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
> index d33169f0018b..7745b8abc3aa 100644
> --- a/fs/cachefiles/internal.h
> +++ b/fs/cachefiles/internal.h
> @@ -138,6 +138,7 @@ static inline bool cachefiles_in_ondemand_mode(struct cachefiles_cache *cache)
> struct cachefiles_req {
> struct cachefiles_object *object;
> struct completion done;
> + refcount_t ref;
> int error;
> struct cachefiles_msg msg;
> };
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index fd49728d8bae..56d12fe4bf73 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -4,6 +4,12 @@
> #include <linux/uio.h>
> #include "internal.h"
>
> +static inline void cachefiles_req_put(struct cachefiles_req *req)
> +{
> + if (refcount_dec_and_test(&req->ref))
> + kfree(req);
> +}
> +
> static int cachefiles_ondemand_fd_release(struct inode *inode,
> struct file *file)
> {
> @@ -299,7 +305,6 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
> {
> struct cachefiles_req *req;
> struct cachefiles_msg *msg;
> - unsigned long id = 0;
> size_t n;
> int ret = 0;
> XA_STATE(xas, &cache->reqs, cache->req_id_next);
> @@ -330,41 +335,39 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>
> xas_clear_mark(&xas, CACHEFILES_REQ_NEW);
> cache->req_id_next = xas.xa_index + 1;
> + refcount_inc(&req->ref);
> xa_unlock(&cache->reqs);
>
> - id = xas.xa_index;
> -
> if (msg->opcode == CACHEFILES_OP_OPEN) {
> ret = cachefiles_ondemand_get_fd(req);
> if (ret) {
> cachefiles_ondemand_set_object_close(req->object);
> - goto error;
> + goto out;
> }
> }
>
> - msg->msg_id = id;
> + msg->msg_id = xas.xa_index;
> msg->object_id = req->object->ondemand->ondemand_id;
>
> if (copy_to_user(_buffer, msg, n) != 0) {
> ret = -EFAULT;
> if (msg->opcode == CACHEFILES_OP_OPEN)
> close_fd(((struct cachefiles_open *)msg->data)->fd);
> - goto error;
> }
> -
> - /* CLOSE request has no reply */
> - if (msg->opcode == CACHEFILES_OP_CLOSE) {
> - xa_erase(&cache->reqs, id);
> - complete(&req->done);
> +out:
> + /* Remove error request and CLOSE request has no reply */
> + if (ret || msg->opcode == CACHEFILES_OP_CLOSE) {
> + xas_reset(&xas);
> + xas_lock(&xas);
> + if (xas_load(&xas) == req) {
> + req->error = ret;
> + complete(&req->done);
> + xas_store(&xas, NULL);
> + }
> + xas_unlock(&xas);
> }
> -
> - return n;
> -
> -error:
> - xa_erase(&cache->reqs, id);
> - req->error = ret;
> - complete(&req->done);
> - return ret;
> + cachefiles_req_put(req);
> + return ret ? ret : n;
> }
>
> typedef int (*init_req_fn)(struct cachefiles_req *req, void *private);
> @@ -394,6 +397,7 @@ static int cachefiles_ondemand_send_req(struct cachefiles_object *object,
> goto out;
> }
>
> + refcount_set(&req->ref, 1);
> req->object = object;
> init_completion(&req->done);
> req->msg.opcode = opcode;
> @@ -455,7 +459,7 @@ static int cachefiles_ondemand_send_req(struct cachefiles_object *object,
> wake_up_all(&cache->daemon_pollwq);
> wait_for_completion(&req->done);
> ret = req->error;
> - kfree(req);
> + cachefiles_req_put(req);
> return ret;
> out:
> /* Reset the object to close state in error handling path.
在 2024/4/24 11:39, [email protected] 写道:
> From: Baokun Li <[email protected]>
>
> We got the following issue in a fuzz test of randomly issuing the restore
> command:
>
> ==================================================================
> BUG: KASAN: slab-use-after-free in cachefiles_ondemand_daemon_read+0xb41/0xb60
> Read of size 8 at addr ffff888122e84088 by task ondemand-04-dae/963
>
> CPU: 13 PID: 963 Comm: ondemand-04-dae Not tainted 6.8.0-dirty #564
> Call Trace:
> kasan_report+0x93/0xc0
> cachefiles_ondemand_daemon_read+0xb41/0xb60
> vfs_read+0x169/0xb50
> ksys_read+0xf5/0x1e0
>
> Allocated by task 116:
> kmem_cache_alloc+0x140/0x3a0
> cachefiles_lookup_cookie+0x140/0xcd0
> fscache_cookie_state_machine+0x43c/0x1230
> [...]
>
> Freed by task 792:
> kmem_cache_free+0xfe/0x390
> cachefiles_put_object+0x241/0x480
> fscache_cookie_state_machine+0x5c8/0x1230
> [...]
> ==================================================================
>
> Following is the process that triggers the issue:
>
> mount | daemon_thread1 | daemon_thread2
> ------------------------------------------------------------
> cachefiles_withdraw_cookie
> cachefiles_ondemand_clean_object(object)
> cachefiles_ondemand_send_req
> REQ_A = kzalloc(sizeof(*req) + data_len)
> wait_for_completion(&REQ_A->done)
>
> cachefiles_daemon_read
> cachefiles_ondemand_daemon_read
> REQ_A = cachefiles_ondemand_select_req
> msg->object_id = req->object->ondemand->ondemand_id
> ------ restore ------
> cachefiles_ondemand_restore
> xas_for_each(&xas, req, ULONG_MAX)
> xas_set_mark(&xas, CACHEFILES_REQ_NEW)
>
> cachefiles_daemon_read
> cachefiles_ondemand_daemon_read
> REQ_A = cachefiles_ondemand_select_req
> copy_to_user(_buffer, msg, n)
> xa_erase(&cache->reqs, id)
> complete(&REQ_A->done)
> ------ close(fd) ------
> cachefiles_ondemand_fd_release
> cachefiles_put_object
> cachefiles_put_object
> kmem_cache_free(cachefiles_object_jar, object)
> REQ_A->object->ondemand->ondemand_id
> // object UAF !!!
>
> When we see the request within xa_lock, req->object must not have been
> freed yet, so grab the reference count of object before xa_unlock to
> avoid the above issue.
>
> Fixes: 0a7e54c1959c ("cachefiles: resend an open request if the read request's object is closed")
> Signed-off-by: Baokun Li <[email protected]>
Reviewed-by: Jia Zhu <[email protected]>
> ---
> fs/cachefiles/ondemand.c | 2 ++
> include/trace/events/cachefiles.h | 6 +++++-
> 2 files changed, 7 insertions(+), 1 deletion(-)
>
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index 56d12fe4bf73..bb94ef6a6f61 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -336,6 +336,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
> xas_clear_mark(&xas, CACHEFILES_REQ_NEW);
> cache->req_id_next = xas.xa_index + 1;
> refcount_inc(&req->ref);
> + cachefiles_grab_object(req->object, cachefiles_obj_get_read_req);
> xa_unlock(&cache->reqs);
>
> if (msg->opcode == CACHEFILES_OP_OPEN) {
> @@ -355,6 +356,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
> close_fd(((struct cachefiles_open *)msg->data)->fd);
> }
> out:
> + cachefiles_put_object(req->object, cachefiles_obj_put_read_req);
> /* Remove error request and CLOSE request has no reply */
> if (ret || msg->opcode == CACHEFILES_OP_CLOSE) {
> xas_reset(&xas);
> diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cachefiles.h
> index cf4b98b9a9ed..119a823fb5a0 100644
> --- a/include/trace/events/cachefiles.h
> +++ b/include/trace/events/cachefiles.h
> @@ -33,6 +33,8 @@ enum cachefiles_obj_ref_trace {
> cachefiles_obj_see_withdrawal,
> cachefiles_obj_get_ondemand_fd,
> cachefiles_obj_put_ondemand_fd,
> + cachefiles_obj_get_read_req,
> + cachefiles_obj_put_read_req,
> };
>
> enum fscache_why_object_killed {
> @@ -127,7 +129,9 @@ enum cachefiles_error_trace {
> EM(cachefiles_obj_see_lookup_cookie, "SEE lookup_cookie") \
> EM(cachefiles_obj_see_lookup_failed, "SEE lookup_failed") \
> EM(cachefiles_obj_see_withdraw_cookie, "SEE withdraw_cookie") \
> - E_(cachefiles_obj_see_withdrawal, "SEE withdrawal")
> + EM(cachefiles_obj_see_withdrawal, "SEE withdrawal") \
> + EM(cachefiles_obj_get_read_req, "GET read_req") \
> + E_(cachefiles_obj_put_read_req, "PUT read_req")
>
> #define cachefiles_coherency_traces \
> EM(cachefiles_coherency_check_aux, "BAD aux ") \
在 2024/4/24 11:39, [email protected] 写道:
> From: Zizhi Wo <[email protected]>
>
> If copen is maliciously called in the user mode, it may delete the request
> corresponding to the random id. And the request may have not been read yet.
>
> Note that when the object is set to reopen, the open request will be done
> with the still reopen state in above case. As a result, the request
> corresponding to this object is always skipped in select_req function, so
> the read request is never completed and blocks other process.
>
> Fix this issue by simply set object to close if its id < 0 in copen.
>
> Signed-off-by: Zizhi Wo <[email protected]>
> Signed-off-by: Baokun Li <[email protected]>
Reviewed-by: Jia Zhu <[email protected]>
> ---
> fs/cachefiles/ondemand.c | 7 ++++++-
> 1 file changed, 6 insertions(+), 1 deletion(-)
>
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index 7c2d43104120..673e7ad52041 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -182,6 +182,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
> xas_store(&xas, NULL);
> xa_unlock(&cache->reqs);
>
> + info = req->object->ondemand;
> /* fail OPEN request if copen format is invalid */
> ret = kstrtol(psize, 0, &size);
> if (ret) {
> @@ -201,7 +202,6 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
> goto out;
> }
>
> - info = req->object->ondemand;
> spin_lock(&info->lock);
> /* The anonymous fd was closed before copen ? */
> if (info->ondemand_id == CACHEFILES_ONDEMAND_ID_CLOSED) {
> @@ -222,6 +222,11 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
> wake_up_all(&cache->daemon_pollwq);
>
> out:
> + spin_lock(&info->lock);
> + /* Need to set object close to avoid reopen status continuing */
> + if (info->ondemand_id == CACHEFILES_ONDEMAND_ID_CLOSED)
> + cachefiles_ondemand_set_object_close(req->object);
> + spin_unlock(&info->lock);
> complete(&req->done);
> return ret;
> }
在 2024/4/24 11:39, [email protected] 写道:
> From: Baokun Li <[email protected]>
>
> Replacing wait_for_completion() with wait_for_completion_killable() in
> cachefiles_ondemand_send_req() allows us to kill processes that might
> trigger a hunk_task if the daemon is abnormal.
>
> But now only CACHEFILES_OP_READ is killable, because OP_CLOSE and OP_OPEN
> is initiated from kworker context and the signal is prohibited in these
> kworker.
>
> Note that when the req in xas changes, i.e. xas_load(&xas) != req, it
> means that a process will complete the current request soon, so wait
> again for the request to be completed.
>
> Suggested-by: Hou Tao <[email protected]>
> Signed-off-by: Baokun Li <[email protected]>
Reviewed-by: Jia Zhu <[email protected]>
> ---
> fs/cachefiles/ondemand.c | 21 +++++++++++++++++++--
> 1 file changed, 19 insertions(+), 2 deletions(-)
>
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index 673e7ad52041..b766430f4abf 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -525,8 +525,25 @@ static int cachefiles_ondemand_send_req(struct cachefiles_object *object,
> goto out;
>
> wake_up_all(&cache->daemon_pollwq);
> - wait_for_completion(&req->done);
> - ret = req->error;
> +wait:
> + ret = wait_for_completion_killable(&req->done);
> + if (!ret) {
> + ret = req->error;
> + } else {
> + xas_reset(&xas);
> + xas_lock(&xas);
> + if (xas_load(&xas) == req) {
> + xas_store(&xas, NULL);
> + ret = -EINTR;
> + }
> + xas_unlock(&xas);
> +
> + /* Someone will complete it soon. */
> + if (ret != -EINTR) {
> + cpu_relax();
> + goto wait;
> + }
> + }
> cachefiles_req_put(req);
> return ret;
> out:
Hi Baokun,
Thanks for improving on this!
On 4/24/24 11:39 AM, [email protected] wrote:
> From: Baokun Li <[email protected]>
>
> This prevents malicious processes from completing random copen/cread
> requests and crashing the system. Added checks are listed below:
>
> * Generic, copen can only complete open requests, and cread can only
> complete read requests.
> * For copen, ondemand_id must not be 0, because this indicates that the
> request has not been read by the daemon.
> * For cread, the object corresponding to fd and req should be the same.
>
> Signed-off-by: Baokun Li <[email protected]>
> ---
> fs/cachefiles/ondemand.c | 27 ++++++++++++++++++++-------
> 1 file changed, 20 insertions(+), 7 deletions(-)
>
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index bb94ef6a6f61..898fab68332b 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -82,12 +82,12 @@ static loff_t cachefiles_ondemand_fd_llseek(struct file *filp, loff_t pos,
> }
>
> static long cachefiles_ondemand_fd_ioctl(struct file *filp, unsigned int ioctl,
> - unsigned long arg)
> + unsigned long id)
> {
> struct cachefiles_object *object = filp->private_data;
> struct cachefiles_cache *cache = object->volume->cache;
> struct cachefiles_req *req;
> - unsigned long id;
> + XA_STATE(xas, &cache->reqs, id);
>
> if (ioctl != CACHEFILES_IOC_READ_COMPLETE)
> return -EINVAL;
> @@ -95,10 +95,15 @@ static long cachefiles_ondemand_fd_ioctl(struct file *filp, unsigned int ioctl,
> if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags))
> return -EOPNOTSUPP;
>
> - id = arg;
> - req = xa_erase(&cache->reqs, id);
> - if (!req)
> + xa_lock(&cache->reqs);
> + req = xas_load(&xas);
> + if (!req || req->msg.opcode != CACHEFILES_OP_READ ||
> + req->object != object) {
> + xa_unlock(&cache->reqs);
> return -EINVAL;
> + }
> + xas_store(&xas, NULL);
> + xa_unlock(&cache->reqs);
>
> trace_cachefiles_ondemand_cread(object, id);
> complete(&req->done);
> @@ -126,6 +131,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
> unsigned long id;
> long size;
> int ret;
> + XA_STATE(xas, &cache->reqs, 0);
>
> if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags))
> return -EOPNOTSUPP;
> @@ -149,9 +155,16 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
> if (ret)
> return ret;
>
> - req = xa_erase(&cache->reqs, id);
> - if (!req)
> + xa_lock(&cache->reqs);
> + xas.xa_index = id;
> + req = xas_load(&xas);
> + if (!req || req->msg.opcode != CACHEFILES_OP_OPEN ||
> + !req->object->ondemand->ondemand_id) {
> + xa_unlock(&cache->reqs);
> return -EINVAL;
> + }
> + xas_store(&xas, NULL);
> + xa_unlock(&cache->reqs);
>
> /* fail OPEN request if copen format is invalid */
> ret = kstrtol(psize, 0, &size);
The code looks good to me, but I still have some questions.
First, what's the worst consequence if the daemon misbehaves like
completing random copen/cread requests? I mean, does that affect other
processes on the system besides the direct users of the ondemand mode,
e.g. will the misbehavior cause system crash?
Besides, it seems that the above security improvement is only "best
effort". It can not completely prevent a malicious misbehaved daemon
from completing random copen/cread requests, right?
--
Thanks,
Jingbo
On 4/24/24 11:39 AM, [email protected] wrote:
> From: Baokun Li <[email protected]>
>
> The following concurrency may cause a read request to fail to be completed
> and result in a hung:
>
> t1 | t2
> ---------------------------------------------------------
> cachefiles_ondemand_copen
> req = xa_erase(&cache->reqs, id)
> // Anon fd is maliciously closed.
> cachefiles_ondemand_fd_release
> xa_lock(&cache->reqs)
> cachefiles_ondemand_set_object_close(object)
> xa_unlock(&cache->reqs)
> cachefiles_ondemand_set_object_open
> // No one will ever close it again.
> cachefiles_ondemand_daemon_read
> cachefiles_ondemand_select_req
> // Get a read req but its fd is already closed.
> // The daemon can't issue a cread ioctl with an closed fd, then hung.
>
> So add spin_lock for cachefiles_ondemand_info to protect ondemand_id and
> state, thus we can avoid the above problem in cachefiles_ondemand_copen()
> by using ondemand_id to determine if fd has been released.
>
> Signed-off-by: Baokun Li <[email protected]>
This indeed looks like a reasonable scenario where the kernel side
should fix, as a non-malicious daemon could also run into this.
How about reusing &cache->reqs spinlock rather than introducing a new
spinlock, as &cache->reqs spinlock is already held when setting object
to close state in cachefiles_ondemand_fd_release()?
> ---
> fs/cachefiles/internal.h | 1 +
> fs/cachefiles/ondemand.c | 16 +++++++++++++++-
> 2 files changed, 16 insertions(+), 1 deletion(-)
>
> diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
> index 7745b8abc3aa..45c8bed60538 100644
> --- a/fs/cachefiles/internal.h
> +++ b/fs/cachefiles/internal.h
> @@ -55,6 +55,7 @@ struct cachefiles_ondemand_info {
> int ondemand_id;
> enum cachefiles_object_state state;
> struct cachefiles_object *object;
> + spinlock_t lock;
> };
>
> /*
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index 898fab68332b..b5e6a851ef04 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -16,13 +16,16 @@ static int cachefiles_ondemand_fd_release(struct inode *inode,
> struct cachefiles_object *object = file->private_data;
> struct cachefiles_cache *cache = object->volume->cache;
> struct cachefiles_ondemand_info *info = object->ondemand;
> - int object_id = info->ondemand_id;
> + int object_id;
> struct cachefiles_req *req;
> XA_STATE(xas, &cache->reqs, 0);
>
> xa_lock(&cache->reqs);
> + spin_lock(&info->lock);
> + object_id = info->ondemand_id;
> info->ondemand_id = CACHEFILES_ONDEMAND_ID_CLOSED;
> cachefiles_ondemand_set_object_close(object);
> + spin_unlock(&info->lock);
>
> /* Only flush CACHEFILES_REQ_NEW marked req to avoid race with daemon_read */
> xas_for_each_marked(&xas, req, ULONG_MAX, CACHEFILES_REQ_NEW) {
> @@ -127,6 +130,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
> {
> struct cachefiles_req *req;
> struct fscache_cookie *cookie;
> + struct cachefiles_ondemand_info *info;
> char *pid, *psize;
> unsigned long id;
> long size;
> @@ -185,6 +189,14 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
> goto out;
> }
>
> + info = req->object->ondemand;
> + spin_lock(&info->lock);
> + /* The anonymous fd was closed before copen ? */
I would like describe more details in the comment, e.g. put the time
sequence described in the commit message here.
> + if (info->ondemand_id == CACHEFILES_ONDEMAND_ID_CLOSED) {
> + spin_unlock(&info->lock);
> + req->error = -EBADFD;
> + goto out;
> + }
> cookie = req->object->cookie;
> cookie->object_size = size;
> if (size)
> @@ -194,6 +206,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
> trace_cachefiles_ondemand_copen(req->object, id, size);
>
> cachefiles_ondemand_set_object_open(req->object);
> + spin_unlock(&info->lock);
> wake_up_all(&cache->daemon_pollwq);
>
> out:
> @@ -596,6 +609,7 @@ int cachefiles_ondemand_init_obj_info(struct cachefiles_object *object,
> return -ENOMEM;
>
> object->ondemand->object = object;
> + spin_lock_init(&object->ondemand->lock);
> INIT_WORK(&object->ondemand->ondemand_work, ondemand_object_worker);
> return 0;
> }
--
Thanks,
Jingbo
On 4/24/24 11:39 AM, [email protected] wrote:
> From: Baokun Li <[email protected]>
>
> Now every time the daemon reads an open request, it requests a new anon fd
> and ondemand_id. With the introduction of "restore", it is possible to read
> the same open request more than once, and therefore have multiple anon fd's
> for the same object.
>
> To avoid this, allocate a new anon fd only if no anon fd has been allocated
> (ondemand_id == 0) or if the previously allocated anon fd has been closed
> (ondemand_id == -1). Returns an error if ondemand_id is valid, letting the
> daemon know that the current userland restore logic is abnormal and needs
> to be checked.
I have no obvious preference on strengthening this on kernel side or
not. Could you explain more about what will happen if the daemon gets
several distinct anon fd corresponding to one same object? IMHO the
daemon should expect the side effect if it issues a 'restore' command
when the daemon doesn't crash. IOW, it's something that shall be fixed
or managed either on the kernel side, or on the daemon side.
> ---
> fs/cachefiles/ondemand.c | 34 ++++++++++++++++++++++++++++------
> 1 file changed, 28 insertions(+), 6 deletions(-)
>
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index b5e6a851ef04..0cf63bfedc9e 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -14,11 +14,18 @@ static int cachefiles_ondemand_fd_release(struct inode *inode,
> struct file *file)
> {
> struct cachefiles_object *object = file->private_data;
> - struct cachefiles_cache *cache = object->volume->cache;
> - struct cachefiles_ondemand_info *info = object->ondemand;
> + struct cachefiles_cache *cache;
> + struct cachefiles_ondemand_info *info;
> int object_id;
> struct cachefiles_req *req;
> - XA_STATE(xas, &cache->reqs, 0);
> + XA_STATE(xas, NULL, 0);
> +
> + if (!object)
> + return 0;
> +
> + info = object->ondemand;
> + cache = object->volume->cache;
> + xas.xa = &cache->reqs;
>
> xa_lock(&cache->reqs);
> spin_lock(&info->lock);
> @@ -269,22 +276,39 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
> goto err_put_fd;
> }
>
> + spin_lock(&object->ondemand->lock);
> + if (object->ondemand->ondemand_id > 0) {
> + spin_unlock(&object->ondemand->lock);
> + ret = -EEXIST;
> + /* Avoid performing cachefiles_ondemand_fd_release(). */
> + file->private_data = NULL;
> + goto err_put_file;
> + }
> +
> file->f_mode |= FMODE_PWRITE | FMODE_LSEEK;
> fd_install(fd, file);
>
> load = (void *)req->msg.data;
> load->fd = fd;
> object->ondemand->ondemand_id = object_id;
> + spin_unlock(&object->ondemand->lock);
>
> cachefiles_get_unbind_pincount(cache);
> trace_cachefiles_ondemand_open(object, &req->msg, load);
> return 0;
>
> +err_put_file:
> + fput(file);
> err_put_fd:
> put_unused_fd(fd);
> err_free_id:
> xa_erase(&cache->ondemand_ids, object_id);
> err:
> + spin_lock(&object->ondemand->lock);
> + /* Avoid marking an opened object as closed. */
> + if (object->ondemand->ondemand_id <= 0)
> + cachefiles_ondemand_set_object_close(object);
> + spin_unlock(&object->ondemand->lock);
> cachefiles_put_object(object, cachefiles_obj_put_ondemand_fd);
> return ret;
> }
> @@ -367,10 +391,8 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>
> if (msg->opcode == CACHEFILES_OP_OPEN) {
> ret = cachefiles_ondemand_get_fd(req);
> - if (ret) {
> - cachefiles_ondemand_set_object_close(req->object);
> + if (ret)
> goto out;
> - }
> }
>
> msg->msg_id = xas.xa_index;
--
Thanks,
Jingbo
Hi Jingbo,
Thank you very much for the review!
On 2024/5/6 10:31, Jingbo Xu wrote:
> Hi Baokun,
>
> Thanks for improving on this!
>
> On 4/24/24 11:39 AM, [email protected] wrote:
>> From: Baokun Li <[email protected]>
>>
>> This prevents malicious processes from completing random copen/cread
>> requests and crashing the system. Added checks are listed below:
>>
>> * Generic, copen can only complete open requests, and cread can only
>> complete read requests.
>> * For copen, ondemand_id must not be 0, because this indicates that the
>> request has not been read by the daemon.
>> * For cread, the object corresponding to fd and req should be the same.
>>
>> Signed-off-by: Baokun Li <[email protected]>
>> ---
>> fs/cachefiles/ondemand.c | 27 ++++++++++++++++++++-------
>> 1 file changed, 20 insertions(+), 7 deletions(-)
>>
>> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
>> index bb94ef6a6f61..898fab68332b 100644
>> --- a/fs/cachefiles/ondemand.c
>> +++ b/fs/cachefiles/ondemand.c
>> @@ -82,12 +82,12 @@ static loff_t cachefiles_ondemand_fd_llseek(struct file *filp, loff_t pos,
>> }
>>
>> static long cachefiles_ondemand_fd_ioctl(struct file *filp, unsigned int ioctl,
>> - unsigned long arg)
>> + unsigned long id)
>> {
>> struct cachefiles_object *object = filp->private_data;
>> struct cachefiles_cache *cache = object->volume->cache;
>> struct cachefiles_req *req;
>> - unsigned long id;
>> + XA_STATE(xas, &cache->reqs, id);
>>
>> if (ioctl != CACHEFILES_IOC_READ_COMPLETE)
>> return -EINVAL;
>> @@ -95,10 +95,15 @@ static long cachefiles_ondemand_fd_ioctl(struct file *filp, unsigned int ioctl,
>> if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags))
>> return -EOPNOTSUPP;
>>
>> - id = arg;
>> - req = xa_erase(&cache->reqs, id);
>> - if (!req)
>> + xa_lock(&cache->reqs);
>> + req = xas_load(&xas);
>> + if (!req || req->msg.opcode != CACHEFILES_OP_READ ||
>> + req->object != object) {
>> + xa_unlock(&cache->reqs);
>> return -EINVAL;
>> + }
>> + xas_store(&xas, NULL);
>> + xa_unlock(&cache->reqs);
>>
>> trace_cachefiles_ondemand_cread(object, id);
>> complete(&req->done);
>> @@ -126,6 +131,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
>> unsigned long id;
>> long size;
>> int ret;
>> + XA_STATE(xas, &cache->reqs, 0);
>>
>> if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags))
>> return -EOPNOTSUPP;
>> @@ -149,9 +155,16 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
>> if (ret)
>> return ret;
>>
>> - req = xa_erase(&cache->reqs, id);
>> - if (!req)
>> + xa_lock(&cache->reqs);
>> + xas.xa_index = id;
>> + req = xas_load(&xas);
>> + if (!req || req->msg.opcode != CACHEFILES_OP_OPEN ||
>> + !req->object->ondemand->ondemand_id) {
>> + xa_unlock(&cache->reqs);
>> return -EINVAL;
>> + }
>> + xas_store(&xas, NULL);
>> + xa_unlock(&cache->reqs);
>>
>> /* fail OPEN request if copen format is invalid */
>> ret = kstrtol(psize, 0, &size);
> The code looks good to me, but I still have some questions.
>
> First, what's the worst consequence if the daemon misbehaves like
> completing random copen/cread requests? I mean, does that affect other
> processes on the system besides the direct users of the ondemand mode,
> e.g. will the misbehavior cause system crash?
This can lead to system crashes, which can lead to a lot of problems.
For example, on reopen, to finish the read request, we might UAF in
ondemand_object_worker();
Or we might UAF in cachefiles_ondemand_daemon_read() when we
haven't added reference counts to the req yet.
Even though these issues are completely resolved in other ways,
I think some basic consistency checks are still necessary.
>
> Besides, it seems that the above security improvement is only "best
> effort". It can not completely prevent a malicious misbehaved daemon
> from completing random copen/cread requests, right?
>
Yes, this doesn't solve the problem completely, we still can't
distinguish between the following cases:
1) different read reqs of the same object reusing the req id.
2) open reqs of different objects.
Ideally, we would calculate a checksum from
timestamps + struct cachefiles_msg to check if the requests
are consistent, but this breaks the uapi.
Thanks,
Baokun
On 2024/5/6 10:55, Jingbo Xu wrote:
>
> On 4/24/24 11:39 AM, [email protected] wrote:
>> From: Baokun Li <[email protected]>
>>
>> The following concurrency may cause a read request to fail to be completed
>> and result in a hung:
>>
>> t1 | t2
>> ---------------------------------------------------------
>> cachefiles_ondemand_copen
>> req = xa_erase(&cache->reqs, id)
>> // Anon fd is maliciously closed.
>> cachefiles_ondemand_fd_release
>> xa_lock(&cache->reqs)
>> cachefiles_ondemand_set_object_close(object)
>> xa_unlock(&cache->reqs)
>> cachefiles_ondemand_set_object_open
>> // No one will ever close it again.
>> cachefiles_ondemand_daemon_read
>> cachefiles_ondemand_select_req
>> // Get a read req but its fd is already closed.
>> // The daemon can't issue a cread ioctl with an closed fd, then hung.
>>
>> So add spin_lock for cachefiles_ondemand_info to protect ondemand_id and
>> state, thus we can avoid the above problem in cachefiles_ondemand_copen()
>> by using ondemand_id to determine if fd has been released.
>>
>> Signed-off-by: Baokun Li <[email protected]>
> This indeed looks like a reasonable scenario where the kernel side
> should fix, as a non-malicious daemon could also run into this.
>
> How about reusing &cache->reqs spinlock rather than introducing a new
> spinlock, as &cache->reqs spinlock is already held when setting object
> to close state in cachefiles_ondemand_fd_release()?
We've considered reusing &cache->reqs spinlock before, but their
uses don't exactly overlap, and there are patches coming that will
use the new spin_lock,. In addition, this reduces competition for
&cache->reqs spinlock.
>> ---
>> fs/cachefiles/internal.h | 1 +
>> fs/cachefiles/ondemand.c | 16 +++++++++++++++-
>> 2 files changed, 16 insertions(+), 1 deletion(-)
>>
>> diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
>> index 7745b8abc3aa..45c8bed60538 100644
>> --- a/fs/cachefiles/internal.h
>> +++ b/fs/cachefiles/internal.h
>> @@ -55,6 +55,7 @@ struct cachefiles_ondemand_info {
>> int ondemand_id;
>> enum cachefiles_object_state state;
>> struct cachefiles_object *object;
>> + spinlock_t lock;
>> };
>>
>> /*
>> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
>> index 898fab68332b..b5e6a851ef04 100644
>> --- a/fs/cachefiles/ondemand.c
>> +++ b/fs/cachefiles/ondemand.c
>> @@ -16,13 +16,16 @@ static int cachefiles_ondemand_fd_release(struct inode *inode,
>> struct cachefiles_object *object = file->private_data;
>> struct cachefiles_cache *cache = object->volume->cache;
>> struct cachefiles_ondemand_info *info = object->ondemand;
>> - int object_id = info->ondemand_id;
>> + int object_id;
>> struct cachefiles_req *req;
>> XA_STATE(xas, &cache->reqs, 0);
>>
>> xa_lock(&cache->reqs);
>> + spin_lock(&info->lock);
>> + object_id = info->ondemand_id;
>> info->ondemand_id = CACHEFILES_ONDEMAND_ID_CLOSED;
>> cachefiles_ondemand_set_object_close(object);
>> + spin_unlock(&info->lock);
>>
>> /* Only flush CACHEFILES_REQ_NEW marked req to avoid race with daemon_read */
>> xas_for_each_marked(&xas, req, ULONG_MAX, CACHEFILES_REQ_NEW) {
>> @@ -127,6 +130,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
>> {
>> struct cachefiles_req *req;
>> struct fscache_cookie *cookie;
>> + struct cachefiles_ondemand_info *info;
>> char *pid, *psize;
>> unsigned long id;
>> long size;
>> @@ -185,6 +189,14 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
>> goto out;
>> }
>>
>> + info = req->object->ondemand;
>> + spin_lock(&info->lock);
>> + /* The anonymous fd was closed before copen ? */
> I would like describe more details in the comment, e.g. put the time
> sequence described in the commit message here.
OK, thanks for your suggestion, I will describe it in more detail
in the next revision.
Thanks,
Baokun
>
>> + if (info->ondemand_id == CACHEFILES_ONDEMAND_ID_CLOSED) {
>> + spin_unlock(&info->lock);
>> + req->error = -EBADFD;
>> + goto out;
>> + }
>> cookie = req->object->cookie;
>> cookie->object_size = size;
>> if (size)
>> @@ -194,6 +206,7 @@ int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args)
>> trace_cachefiles_ondemand_copen(req->object, id, size);
>>
>> cachefiles_ondemand_set_object_open(req->object);
>> + spin_unlock(&info->lock);
>> wake_up_all(&cache->daemon_pollwq);
>>
>> out:
>> @@ -596,6 +609,7 @@ int cachefiles_ondemand_init_obj_info(struct cachefiles_object *object,
>> return -ENOMEM;
>>
>> object->ondemand->object = object;
>> + spin_lock_init(&object->ondemand->lock);
>> INIT_WORK(&object->ondemand->ondemand_work, ondemand_object_worker);
>> return 0;
>> }
On 4/24/24 11:39 AM, [email protected] wrote:
> From: Baokun Li <[email protected]>
>
> After installing the anonymous fd, we can now see it in userland and close
> it. However, at this point we may not have gotten the reference count of
> the cache, but we will put it during colse fd, so this may cause a cache
> UAF.
Good catch!
>
> To avoid this, we will make the anonymous fd accessible to the userland by
> executing fd_install() after copy_to_user() has succeeded, and by this
> point we must have already grabbed the reference count of the cache.
Why we must execute fd_install() after copy_to_user() has succeeded?
Why not grab a reference to the cache before fd_install()?
>
> Suggested-by: Hou Tao <[email protected]>
> Signed-off-by: Baokun Li <[email protected]>
> ---
> fs/cachefiles/ondemand.c | 53 +++++++++++++++++++++++++---------------
> 1 file changed, 33 insertions(+), 20 deletions(-)
>
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index 0cf63bfedc9e..7c2d43104120 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -4,6 +4,11 @@
> #include <linux/uio.h>
> #include "internal.h"
>
> +struct anon_file {
> + struct file *file;
> + int fd;
> +};
> +
> static inline void cachefiles_req_put(struct cachefiles_req *req)
> {
> if (refcount_dec_and_test(&req->ref))
> @@ -244,14 +249,14 @@ int cachefiles_ondemand_restore(struct cachefiles_cache *cache, char *args)
> return 0;
> }
>
> -static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
> +static int cachefiles_ondemand_get_fd(struct cachefiles_req *req,
> + struct anon_file *anon_file)
> {
> struct cachefiles_object *object;
> struct cachefiles_cache *cache;
> struct cachefiles_open *load;
> - struct file *file;
> u32 object_id;
> - int ret, fd;
> + int ret;
>
> object = cachefiles_grab_object(req->object,
> cachefiles_obj_get_ondemand_fd);
> @@ -263,16 +268,16 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
> if (ret < 0)
> goto err;
>
> - fd = get_unused_fd_flags(O_WRONLY);
> - if (fd < 0) {
> - ret = fd;
> + anon_file->fd = get_unused_fd_flags(O_WRONLY);
> + if (anon_file->fd < 0) {
> + ret = anon_file->fd;
> goto err_free_id;
> }
>
> - file = anon_inode_getfile("[cachefiles]", &cachefiles_ondemand_fd_fops,
> - object, O_WRONLY);
> - if (IS_ERR(file)) {
> - ret = PTR_ERR(file);
> + anon_file->file = anon_inode_getfile("[cachefiles]",
> + &cachefiles_ondemand_fd_fops, object, O_WRONLY);
> + if (IS_ERR(anon_file->file)) {
> + ret = PTR_ERR(anon_file->file);
> goto err_put_fd;
> }
>
> @@ -281,15 +286,14 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
> spin_unlock(&object->ondemand->lock);
> ret = -EEXIST;
> /* Avoid performing cachefiles_ondemand_fd_release(). */
> - file->private_data = NULL;
> + anon_file->file->private_data = NULL;
> goto err_put_file;
> }
>
> - file->f_mode |= FMODE_PWRITE | FMODE_LSEEK;
> - fd_install(fd, file);
> + anon_file->file->f_mode |= FMODE_PWRITE | FMODE_LSEEK;
>
> load = (void *)req->msg.data;
> - load->fd = fd;
> + load->fd = anon_file->fd;
> object->ondemand->ondemand_id = object_id;
> spin_unlock(&object->ondemand->lock);
>
> @@ -298,9 +302,11 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
> return 0;
>
> err_put_file:
> - fput(file);
> + fput(anon_file->file);
> + anon_file->file = NULL;
> err_put_fd:
> - put_unused_fd(fd);
> + put_unused_fd(anon_file->fd);
> + anon_file->fd = ret;
> err_free_id:
> xa_erase(&cache->ondemand_ids, object_id);
> err:
> @@ -357,6 +363,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
> struct cachefiles_msg *msg;
> size_t n;
> int ret = 0;
> + struct anon_file anon_file;
> XA_STATE(xas, &cache->reqs, cache->req_id_next);
>
> xa_lock(&cache->reqs);
> @@ -390,7 +397,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
> xa_unlock(&cache->reqs);
>
> if (msg->opcode == CACHEFILES_OP_OPEN) {
> - ret = cachefiles_ondemand_get_fd(req);
> + ret = cachefiles_ondemand_get_fd(req, &anon_file);
> if (ret)
> goto out;
> }
> @@ -398,10 +405,16 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
> msg->msg_id = xas.xa_index;
> msg->object_id = req->object->ondemand->ondemand_id;
>
> - if (copy_to_user(_buffer, msg, n) != 0) {
> + if (copy_to_user(_buffer, msg, n) != 0)
> ret = -EFAULT;
> - if (msg->opcode == CACHEFILES_OP_OPEN)
> - close_fd(((struct cachefiles_open *)msg->data)->fd);
> +
> + if (msg->opcode == CACHEFILES_OP_OPEN) {
> + if (ret < 0) {
> + fput(anon_file.file);
> + put_unused_fd(anon_file.fd);
> + goto out;
> + }
> + fd_install(anon_file.fd, anon_file.file);
> }
> out:
> cachefiles_put_object(req->object, cachefiles_obj_put_read_req);
--
Thanks,
Jingbo
On 2024/5/6 11:24, Jingbo Xu wrote:
>
> On 4/24/24 11:39 AM, [email protected] wrote:
>> From: Baokun Li <[email protected]>
>>
>> After installing the anonymous fd, we can now see it in userland and close
>> it. However, at this point we may not have gotten the reference count of
>> the cache, but we will put it during colse fd, so this may cause a cache
>> UAF.
> Good catch!
>
>> To avoid this, we will make the anonymous fd accessible to the userland by
>> executing fd_install() after copy_to_user() has succeeded, and by this
>> point we must have already grabbed the reference count of the cache.
> Why we must execute fd_install() after copy_to_user() has succeeded?
> Why not grab a reference to the cache before fd_install()?
Two things are actually done here:
1) Grab a reference to the cache before fd_install()
2) By kernel convention, fd is taken over by the user land after
fd_install() is executed, and the kernel does not call close_fd() after
this, so fd_install() is called after everything is ready.
Thanks,
Baokun
>
>> Suggested-by: Hou Tao <[email protected]>
>> Signed-off-by: Baokun Li <[email protected]>
>> ---
>> fs/cachefiles/ondemand.c | 53 +++++++++++++++++++++++++---------------
>> 1 file changed, 33 insertions(+), 20 deletions(-)
>>
>> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
>> index 0cf63bfedc9e..7c2d43104120 100644
>> --- a/fs/cachefiles/ondemand.c
>> +++ b/fs/cachefiles/ondemand.c
>> @@ -4,6 +4,11 @@
>> #include <linux/uio.h>
>> #include "internal.h"
>>
>> +struct anon_file {
>> + struct file *file;
>> + int fd;
>> +};
>> +
>> static inline void cachefiles_req_put(struct cachefiles_req *req)
>> {
>> if (refcount_dec_and_test(&req->ref))
>> @@ -244,14 +249,14 @@ int cachefiles_ondemand_restore(struct cachefiles_cache *cache, char *args)
>> return 0;
>> }
>>
>> -static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
>> +static int cachefiles_ondemand_get_fd(struct cachefiles_req *req,
>> + struct anon_file *anon_file)
>> {
>> struct cachefiles_object *object;
>> struct cachefiles_cache *cache;
>> struct cachefiles_open *load;
>> - struct file *file;
>> u32 object_id;
>> - int ret, fd;
>> + int ret;
>>
>> object = cachefiles_grab_object(req->object,
>> cachefiles_obj_get_ondemand_fd);
>> @@ -263,16 +268,16 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
>> if (ret < 0)
>> goto err;
>>
>> - fd = get_unused_fd_flags(O_WRONLY);
>> - if (fd < 0) {
>> - ret = fd;
>> + anon_file->fd = get_unused_fd_flags(O_WRONLY);
>> + if (anon_file->fd < 0) {
>> + ret = anon_file->fd;
>> goto err_free_id;
>> }
>>
>> - file = anon_inode_getfile("[cachefiles]", &cachefiles_ondemand_fd_fops,
>> - object, O_WRONLY);
>> - if (IS_ERR(file)) {
>> - ret = PTR_ERR(file);
>> + anon_file->file = anon_inode_getfile("[cachefiles]",
>> + &cachefiles_ondemand_fd_fops, object, O_WRONLY);
>> + if (IS_ERR(anon_file->file)) {
>> + ret = PTR_ERR(anon_file->file);
>> goto err_put_fd;
>> }
>>
>> @@ -281,15 +286,14 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
>> spin_unlock(&object->ondemand->lock);
>> ret = -EEXIST;
>> /* Avoid performing cachefiles_ondemand_fd_release(). */
>> - file->private_data = NULL;
>> + anon_file->file->private_data = NULL;
>> goto err_put_file;
>> }
>>
>> - file->f_mode |= FMODE_PWRITE | FMODE_LSEEK;
>> - fd_install(fd, file);
>> + anon_file->file->f_mode |= FMODE_PWRITE | FMODE_LSEEK;
>>
>> load = (void *)req->msg.data;
>> - load->fd = fd;
>> + load->fd = anon_file->fd;
>> object->ondemand->ondemand_id = object_id;
>> spin_unlock(&object->ondemand->lock);
>>
>> @@ -298,9 +302,11 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
>> return 0;
>>
>> err_put_file:
>> - fput(file);
>> + fput(anon_file->file);
>> + anon_file->file = NULL;
>> err_put_fd:
>> - put_unused_fd(fd);
>> + put_unused_fd(anon_file->fd);
>> + anon_file->fd = ret;
>> err_free_id:
>> xa_erase(&cache->ondemand_ids, object_id);
>> err:
>> @@ -357,6 +363,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>> struct cachefiles_msg *msg;
>> size_t n;
>> int ret = 0;
>> + struct anon_file anon_file;
>> XA_STATE(xas, &cache->reqs, cache->req_id_next);
>>
>> xa_lock(&cache->reqs);
>> @@ -390,7 +397,7 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>> xa_unlock(&cache->reqs);
>>
>> if (msg->opcode == CACHEFILES_OP_OPEN) {
>> - ret = cachefiles_ondemand_get_fd(req);
>> + ret = cachefiles_ondemand_get_fd(req, &anon_file);
>> if (ret)
>> goto out;
>> }
>> @@ -398,10 +405,16 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>> msg->msg_id = xas.xa_index;
>> msg->object_id = req->object->ondemand->ondemand_id;
>>
>> - if (copy_to_user(_buffer, msg, n) != 0) {
>> + if (copy_to_user(_buffer, msg, n) != 0)
>> ret = -EFAULT;
>> - if (msg->opcode == CACHEFILES_OP_OPEN)
>> - close_fd(((struct cachefiles_open *)msg->data)->fd);
>> +
>> + if (msg->opcode == CACHEFILES_OP_OPEN) {
>> + if (ret < 0) {
>> + fput(anon_file.file);
>> + put_unused_fd(anon_file.fd);
>> + goto out;
>> + }
>> + fd_install(anon_file.fd, anon_file.file);
>> }
>> out:
>> cachefiles_put_object(req->object, cachefiles_obj_put_read_req);
On 4/24/24 11:39 AM, [email protected] wrote:
> From: Baokun Li <[email protected]>
>
> This prevents concurrency from causing access to a freed req.
Could you give more details on how the concurrent access will happen?
How could another process access the &cache->reqs xarray after it has
been flushed?
> ---
> fs/cachefiles/daemon.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c
> index 6465e2574230..ccb7b707ea4b 100644
> --- a/fs/cachefiles/daemon.c
> +++ b/fs/cachefiles/daemon.c
> @@ -159,6 +159,7 @@ static void cachefiles_flush_reqs(struct cachefiles_cache *cache)
> xa_for_each(xa, index, req) {
> req->error = -EIO;
> complete(&req->done);
> + __xa_erase(xa, index);
> }
> xa_unlock(xa);
>
--
Thanks,
Jingbo
On 4/24/24 11:39 AM, [email protected] wrote:
> From: Baokun Li <[email protected]>
>
> The err_put_fd tag is only used once, so remove it to make the code more
> readable.
I think it's a conventional style to put error handling in the bottom of
the function so that it could be reused. Indeed currently err_put_fd
has only one caller but IMHO it's only styling issues.
By the way it seems that this is not needed anymore if patch 9 is applied.
> ---
> fs/cachefiles/ondemand.c | 7 +++----
> 1 file changed, 3 insertions(+), 4 deletions(-)
>
> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
> index 4ba42f1fa3b4..fd49728d8bae 100644
> --- a/fs/cachefiles/ondemand.c
> +++ b/fs/cachefiles/ondemand.c
> @@ -347,7 +347,9 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>
> if (copy_to_user(_buffer, msg, n) != 0) {
> ret = -EFAULT;
> - goto err_put_fd;
> + if (msg->opcode == CACHEFILES_OP_OPEN)
> + close_fd(((struct cachefiles_open *)msg->data)->fd);
> + goto error;
> }
>
> /* CLOSE request has no reply */
> @@ -358,9 +360,6 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>
> return n;
>
> -err_put_fd:
> - if (msg->opcode == CACHEFILES_OP_OPEN)
> - close_fd(((struct cachefiles_open *)msg->data)->fd);
> error:
> xa_erase(&cache->reqs, id);
> req->error = ret;
--
Thanks,
Jingbo
On 2024/5/6 11:48, Jingbo Xu wrote:
>
> On 4/24/24 11:39 AM, [email protected] wrote:
>> From: Baokun Li <[email protected]>
>>
>> This prevents concurrency from causing access to a freed req.
> Could you give more details on how the concurrent access will happen?
> How could another process access the &cache->reqs xarray after it has
> been flushed?
Similar logic to restore leading to UAF:
mount | daemon_thread1 | daemon_thread2
------------------------------------------------------------
cachefiles_ondemand_init_object
cachefiles_ondemand_send_req
REQ_A = kzalloc(sizeof(*req) + data_len)
wait_for_completion(&REQ_A->done)
cachefiles_daemon_read
cachefiles_ondemand_daemon_read
REQ_A = cachefiles_ondemand_select_req
cachefiles_ondemand_get_fd
copy_to_user(_buffer, msg, n)
process_open_req(REQ_A)
// close dev fd
cachefiles_flush_reqs
complete(&REQ_A->done)
kfree(REQ_A)
cachefiles_ondemand_get_fd(REQ_A)
fd = get_unused_fd_flags
file = anon_inode_getfile
fd_install(fd, file)
load = (void *)REQ_A->msg.data;
load->fd = fd;
// load UAF !!!
>> ---
>> fs/cachefiles/daemon.c | 1 +
>> 1 file changed, 1 insertion(+)
>>
>> diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c
>> index 6465e2574230..ccb7b707ea4b 100644
>> --- a/fs/cachefiles/daemon.c
>> +++ b/fs/cachefiles/daemon.c
>> @@ -159,6 +159,7 @@ static void cachefiles_flush_reqs(struct cachefiles_cache *cache)
>> xa_for_each(xa, index, req) {
>> req->error = -EIO;
>> complete(&req->done);
>> + __xa_erase(xa, index);
>> }
>> xa_unlock(xa);
>>
On 2024/5/6 11:55, Jingbo Xu wrote:
>
> On 4/24/24 11:39 AM, [email protected] wrote:
>> From: Baokun Li <[email protected]>
>>
>> The err_put_fd tag is only used once, so remove it to make the code more
>> readable.
> I think it's a conventional style to put error handling in the bottom of
> the function so that it could be reused. Indeed currently err_put_fd
> has only one caller but IMHO it's only styling issues.
>
> By the way it seems that this is not needed anymore if patch 9 is applied.
This is just to make patch 3 look clearer, if you insist on dropping it
I will drop it in the next revision.
Cheers,
Baokun
>> ---
>> fs/cachefiles/ondemand.c | 7 +++----
>> 1 file changed, 3 insertions(+), 4 deletions(-)
>>
>> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
>> index 4ba42f1fa3b4..fd49728d8bae 100644
>> --- a/fs/cachefiles/ondemand.c
>> +++ b/fs/cachefiles/ondemand.c
>> @@ -347,7 +347,9 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>>
>> if (copy_to_user(_buffer, msg, n) != 0) {
>> ret = -EFAULT;
>> - goto err_put_fd;
>> + if (msg->opcode == CACHEFILES_OP_OPEN)
>> + close_fd(((struct cachefiles_open *)msg->data)->fd);
>> + goto error;
>> }
>>
>> /* CLOSE request has no reply */
>> @@ -358,9 +360,6 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>>
>> return n;
>>
>> -err_put_fd:
>> - if (msg->opcode == CACHEFILES_OP_OPEN)
>> - close_fd(((struct cachefiles_open *)msg->data)->fd);
>> error:
>> xa_erase(&cache->reqs, id);
>> req->error = ret;
On 5/6/24 11:57 AM, Baokun Li wrote:
> On 2024/5/6 11:48, Jingbo Xu wrote:
>>
>> On 4/24/24 11:39 AM, [email protected] wrote:
>>> From: Baokun Li <[email protected]>
>>>
>>> This prevents concurrency from causing access to a freed req.
>> Could you give more details on how the concurrent access will happen?
>> How could another process access the &cache->reqs xarray after it has
>> been flushed?
>
> Similar logic to restore leading to UAF:
>
> mount | daemon_thread1 | daemon_thread2
> ------------------------------------------------------------
> cachefiles_ondemand_init_object
> cachefiles_ondemand_send_req
> REQ_A = kzalloc(sizeof(*req) + data_len)
> wait_for_completion(&REQ_A->done)
>
> cachefiles_daemon_read
> cachefiles_ondemand_daemon_read
> REQ_A = cachefiles_ondemand_select_req
> cachefiles_ondemand_get_fd
> copy_to_user(_buffer, msg, n)
> process_open_req(REQ_A)
> // close dev fd
> cachefiles_flush_reqs
> complete(&REQ_A->done)
> kfree(REQ_A)
> cachefiles_ondemand_get_fd(REQ_A)
> fd = get_unused_fd_flags
> file = anon_inode_getfile
> fd_install(fd, file)
> load = (void *)REQ_A->msg.data;
> load->fd = fd;
> // load UAF !!!
How could the second cachefiles_ondemand_get_fd() get called here, given
the cache has been flushed and flagged as DEAD?
>
>>> ---
>>> fs/cachefiles/daemon.c | 1 +
>>> 1 file changed, 1 insertion(+)
>>>
>>> diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c
>>> index 6465e2574230..ccb7b707ea4b 100644
>>> --- a/fs/cachefiles/daemon.c
>>> +++ b/fs/cachefiles/daemon.c
>>> @@ -159,6 +159,7 @@ static void cachefiles_flush_reqs(struct
>>> cachefiles_cache *cache)
>>> xa_for_each(xa, index, req) {
>>> req->error = -EIO;
>>> complete(&req->done);
>>> + __xa_erase(xa, index);
>>> }
>>> xa_unlock(xa);
>>>
>
--
Thanks,
Jingbo
Hi Jingbo,
Sorry for the late reply.
On 2024/5/6 13:50, Jingbo Xu wrote:
>
> On 5/6/24 11:57 AM, Baokun Li wrote:
>> On 2024/5/6 11:48, Jingbo Xu wrote:
>>> On 4/24/24 11:39 AM, [email protected] wrote:
>>>> From: Baokun Li <[email protected]>
>>>>
>>>> This prevents concurrency from causing access to a freed req.
>>> Could you give more details on how the concurrent access will happen?
>>> How could another process access the &cache->reqs xarray after it has
>>> been flushed?
>> Similar logic to restore leading to UAF:
>>
>> mount | daemon_thread1 | daemon_thread2
>> ------------------------------------------------------------
>> cachefiles_ondemand_init_object
>> cachefiles_ondemand_send_req
>> REQ_A = kzalloc(sizeof(*req) + data_len)
>> wait_for_completion(&REQ_A->done)
>>
>> cachefiles_daemon_read
>> cachefiles_ondemand_daemon_read
>> REQ_A = cachefiles_ondemand_select_req
>> cachefiles_ondemand_get_fd
>> copy_to_user(_buffer, msg, n)
>> process_open_req(REQ_A)
>> // close dev fd
>> cachefiles_flush_reqs
>> complete(&REQ_A->done)
>> kfree(REQ_A)
>
>> cachefiles_ondemand_get_fd(REQ_A)
>> fd = get_unused_fd_flags
>> file = anon_inode_getfile
>> fd_install(fd, file)
>> load = (void *)REQ_A->msg.data;
>> load->fd = fd;
>> // load UAF !!!
> How could the second cachefiles_ondemand_get_fd() get called here, given
> the cache has been flushed and flagged as DEAD?
>
I was in a bit of a rush to reply earlier, and that graph above is
wrong. Please see the one below:
mount | daemon_thread1 | daemon_thread2
------------------------------------------------------------
cachefiles_ondemand_init_object
cachefiles_ondemand_send_req
REQ_A = kzalloc(sizeof(*req) + data_len)
wait_for_completion(&REQ_A->done)
cachefiles_daemon_read
cachefiles_ondemand_daemon_read
// close dev fd
cachefiles_flush_reqs
complete(&REQ_A->done)
kfree(REQ_A)
xa_lock(&cache->reqs);
cachefiles_ondemand_select_req
req->msg.opcode != CACHEFILES_OP_READ
// req use-after-free !!!
xa_unlock(&cache->reqs);
xa_destroy(&cache->reqs)
Even with CACHEFILES_DEAD set, we can still read the requests, so
accessing it after the request has been freed will trigger use-after-free.
Thanks,
Baokun
>>>> ---
>>>> fs/cachefiles/daemon.c | 1 +
>>>> 1 file changed, 1 insertion(+)
>>>>
>>>> diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c
>>>> index 6465e2574230..ccb7b707ea4b 100644
>>>> --- a/fs/cachefiles/daemon.c
>>>> +++ b/fs/cachefiles/daemon.c
>>>> @@ -159,6 +159,7 @@ static void cachefiles_flush_reqs(struct
>>>> cachefiles_cache *cache)
>>>> xa_for_each(xa, index, req) {
>>>> req->error = -EIO;
>>>> complete(&req->done);
>>>> + __xa_erase(xa, index);
>>>> }
>>>> xa_unlock(xa);
>>>>
Hi Jingbo,
On 2024/5/6 11:09, Jingbo Xu wrote:
>
> On 4/24/24 11:39 AM, [email protected] wrote:
>> From: Baokun Li <[email protected]>
>>
>> Now every time the daemon reads an open request, it requests a new anon fd
>> and ondemand_id. With the introduction of "restore", it is possible to read
>> the same open request more than once, and therefore have multiple anon fd's
>> for the same object.
>>
>> To avoid this, allocate a new anon fd only if no anon fd has been allocated
>> (ondemand_id == 0) or if the previously allocated anon fd has been closed
>> (ondemand_id == -1). Returns an error if ondemand_id is valid, letting the
>> daemon know that the current userland restore logic is abnormal and needs
>> to be checked.
> I have no obvious preference on strengthening this on kernel side or
> not. Could you explain more about what will happen if the daemon gets
> several distinct anon fd corresponding to one same object? IMHO the
> daemon should expect the side effect if it issues a 'restore' command
> when the daemon doesn't crash. IOW, it's something that shall be fixed
> or managed either on the kernel side, or on the daemon side.
If the anon_fd is not unique, the daemon will only close the anon_fd
corresponding to the newest object_id during drop_object, and the
other anon_fds will not be closed until the daemon exits.
However, the anon_fd holds the reference count of the object, so the
object will not be freed, and the cookie will also not be freed. So
mounting a same-named image at this point will cause a hung task
in fscache_hash_cookie() by waiting for the cookie to unhash.
The object_id and anon_fd of an object are supposed to be unique
under normal circumstances, this patch just provides that guarantee
even in the case of an exception.
Thank you very much for the review!
Regards,
Baokun
>> ---
>> fs/cachefiles/ondemand.c | 34 ++++++++++++++++++++++++++++------
>> 1 file changed, 28 insertions(+), 6 deletions(-)
>>
>> diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
>> index b5e6a851ef04..0cf63bfedc9e 100644
>> --- a/fs/cachefiles/ondemand.c
>> +++ b/fs/cachefiles/ondemand.c
>> @@ -14,11 +14,18 @@ static int cachefiles_ondemand_fd_release(struct inode *inode,
>> struct file *file)
>> {
>> struct cachefiles_object *object = file->private_data;
>> - struct cachefiles_cache *cache = object->volume->cache;
>> - struct cachefiles_ondemand_info *info = object->ondemand;
>> + struct cachefiles_cache *cache;
>> + struct cachefiles_ondemand_info *info;
>> int object_id;
>> struct cachefiles_req *req;
>> - XA_STATE(xas, &cache->reqs, 0);
>> + XA_STATE(xas, NULL, 0);
>> +
>> + if (!object)
>> + return 0;
>> +
>> + info = object->ondemand;
>> + cache = object->volume->cache;
>> + xas.xa = &cache->reqs;
>>
>> xa_lock(&cache->reqs);
>> spin_lock(&info->lock);
>> @@ -269,22 +276,39 @@ static int cachefiles_ondemand_get_fd(struct cachefiles_req *req)
>> goto err_put_fd;
>> }
>>
>> + spin_lock(&object->ondemand->lock);
>> + if (object->ondemand->ondemand_id > 0) {
>> + spin_unlock(&object->ondemand->lock);
>> + ret = -EEXIST;
>> + /* Avoid performing cachefiles_ondemand_fd_release(). */
>> + file->private_data = NULL;
>> + goto err_put_file;
>> + }
>> +
>> file->f_mode |= FMODE_PWRITE | FMODE_LSEEK;
>> fd_install(fd, file);
>>
>> load = (void *)req->msg.data;
>> load->fd = fd;
>> object->ondemand->ondemand_id = object_id;
>> + spin_unlock(&object->ondemand->lock);
>>
>> cachefiles_get_unbind_pincount(cache);
>> trace_cachefiles_ondemand_open(object, &req->msg, load);
>> return 0;
>>
>> +err_put_file:
>> + fput(file);
>> err_put_fd:
>> put_unused_fd(fd);
>> err_free_id:
>> xa_erase(&cache->ondemand_ids, object_id);
>> err:
>> + spin_lock(&object->ondemand->lock);
>> + /* Avoid marking an opened object as closed. */
>> + if (object->ondemand->ondemand_id <= 0)
>> + cachefiles_ondemand_set_object_close(object);
>> + spin_unlock(&object->ondemand->lock);
>> cachefiles_put_object(object, cachefiles_obj_put_ondemand_fd);
>> return ret;
>> }
>> @@ -367,10 +391,8 @@ ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache,
>>
>> if (msg->opcode == CACHEFILES_OP_OPEN) {
>> ret = cachefiles_ondemand_get_fd(req);
>> - if (ret) {
>> - cachefiles_ondemand_set_object_close(req->object);
>> + if (ret)
>> goto out;
>> - }
>> }
>>
>> msg->msg_id = xas.xa_index;