* [PATCH v5 1/7] eventfs: Remove "is_freed" union with rcu head
[not found] <20231031223326.794680978@goodmis.org>
@ 2023-10-31 22:33 ` Steven Rostedt
2023-11-01 12:19 ` Masami Hiramatsu
2023-10-31 22:33 ` [PATCH v5 6/7] eventfs: Delete eventfs_inode when the last dentry is freed Steven Rostedt
1 sibling, 1 reply; 3+ messages in thread
From: Steven Rostedt @ 2023-10-31 22:33 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel
Cc: Masami Hiramatsu, Mark Rutland, Ajay Kaher, Andrew Morton, stable
From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
The eventfs_inode->is_freed was a union with the rcu_head with the
assumption that when it was on the srcu list the head would contain a
pointer which would make "is_freed" true. But that was a wrong assumption
as the rcu head is a single link list where the last element is NULL.
Instead, split the nr_entries integer so that "is_freed" is one bit and
the nr_entries is the next 31 bits. As there shouldn't be more than 10
(currently there's at most 5 to 7 depending on the config), this should
not be a problem.
Cc: stable@vger.kernel.org
Fixes: 63940449555e7 ("eventfs: Implement eventfs lookup, read, open functions")
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
---
fs/tracefs/event_inode.c | 2 ++
fs/tracefs/internal.h | 6 +++---
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c
index 754885dfe71c..2c2c75b2ad73 100644
--- a/fs/tracefs/event_inode.c
+++ b/fs/tracefs/event_inode.c
@@ -824,6 +824,8 @@ static void eventfs_remove_rec(struct eventfs_inode *ei, struct list_head *head,
eventfs_remove_rec(ei_child, head, level + 1);
}
+ ei->is_freed = 1;
+
list_del_rcu(&ei->list);
list_add_tail(&ei->del_list, head);
}
diff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h
index 64fde9490f52..c7d88aaa949f 100644
--- a/fs/tracefs/internal.h
+++ b/fs/tracefs/internal.h
@@ -23,6 +23,7 @@ struct tracefs_inode {
* @d_parent: pointer to the parent's dentry
* @d_children: The array of dentries to represent the files when created
* @data: The private data to pass to the callbacks
+ * @is_freed: Flag set if the eventfs is on its way to be freed
* @nr_entries: The number of items in @entries
*/
struct eventfs_inode {
@@ -38,14 +39,13 @@ struct eventfs_inode {
* Union - used for deletion
* @del_list: list of eventfs_inode to delete
* @rcu: eventfs_inode to delete in RCU
- * @is_freed: node is freed if one of the above is set
*/
union {
struct list_head del_list;
struct rcu_head rcu;
- unsigned long is_freed;
};
- int nr_entries;
+ unsigned int is_freed:1;
+ unsigned int nr_entries:31;
};
static inline struct tracefs_inode *get_tracefs(const struct inode *inode)
--
2.42.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH v5 6/7] eventfs: Delete eventfs_inode when the last dentry is freed
[not found] <20231031223326.794680978@goodmis.org>
2023-10-31 22:33 ` [PATCH v5 1/7] eventfs: Remove "is_freed" union with rcu head Steven Rostedt
@ 2023-10-31 22:33 ` Steven Rostedt
1 sibling, 0 replies; 3+ messages in thread
From: Steven Rostedt @ 2023-10-31 22:33 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel
Cc: Masami Hiramatsu, Mark Rutland, Ajay Kaher, Andrew Morton, stable
From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
There exists a race between holding a reference of an eventfs_inode dentry
and the freeing of the eventfs_inode. If user space has a dentry held long
enough, it may still be able to access the dentry's eventfs_inode after it
has been freed.
To prevent this, have he eventfs_inode freed via the last dput() (or via
RCU if the eventfs_inode does not have a dentry).
This means reintroducing the eventfs_inode del_list field at a temporary
place to put the eventfs_inode. It needs to mark it as freed (via the
list) but also must invalidate the dentry immediately as the return from
eventfs_remove_dir() expects that they are. But the dentry invalidation
must not be called under the eventfs_mutex, so it must be done after the
eventfs_inode is marked as free (put on a deletion list).
Cc: stable@vger.kernel.org
Fixes: 5bdcd5f5331a2 ("eventfs: Implement removal of meta data from eventfs")
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
---
fs/tracefs/event_inode.c | 184 +++++++++++++++++----------------------
fs/tracefs/internal.h | 2 +
2 files changed, 84 insertions(+), 102 deletions(-)
diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c
index 87a8aaeda231..827ca152cfbe 100644
--- a/fs/tracefs/event_inode.c
+++ b/fs/tracefs/event_inode.c
@@ -85,8 +85,7 @@ static int eventfs_set_attr(struct mnt_idmap *idmap, struct dentry *dentry,
mutex_lock(&eventfs_mutex);
ei = dentry->d_fsdata;
- /* The LSB is set when the eventfs_inode is being freed */
- if (((unsigned long)ei & 1UL) || ei->is_freed) {
+ if (ei->is_freed) {
/* Do not allow changes if the event is about to be removed. */
mutex_unlock(&eventfs_mutex);
return -ENODEV;
@@ -276,35 +275,17 @@ static void free_ei(struct eventfs_inode *ei)
void eventfs_set_ei_status_free(struct tracefs_inode *ti, struct dentry *dentry)
{
struct tracefs_inode *ti_parent;
- struct eventfs_inode *ei_child, *tmp;
struct eventfs_inode *ei;
int i;
/* The top level events directory may be freed by this */
if (unlikely(ti->flags & TRACEFS_EVENT_TOP_INODE)) {
- LIST_HEAD(ef_del_list);
-
mutex_lock(&eventfs_mutex);
-
ei = ti->private;
-
- /* Record all the top level files */
- list_for_each_entry_srcu(ei_child, &ei->children, list,
- lockdep_is_held(&eventfs_mutex)) {
- list_add_tail(&ei_child->del_list, &ef_del_list);
- }
-
/* Nothing should access this, but just in case! */
ti->private = NULL;
-
mutex_unlock(&eventfs_mutex);
- /* Now safely free the top level files and their children */
- list_for_each_entry_safe(ei_child, tmp, &ef_del_list, del_list) {
- list_del(&ei_child->del_list);
- eventfs_remove_dir(ei_child);
- }
-
free_ei(ei);
return;
}
@@ -319,14 +300,6 @@ void eventfs_set_ei_status_free(struct tracefs_inode *ti, struct dentry *dentry)
if (!ei)
goto out;
- /*
- * If ei was freed, then the LSB bit is set for d_fsdata.
- * But this should not happen, as it should still have a
- * ref count that prevents it. Warn in case it does.
- */
- if (WARN_ON_ONCE((unsigned long)ei & 1))
- goto out;
-
/* This could belong to one of the files of the ei */
if (ei->dentry != dentry) {
for (i = 0; i < ei->nr_entries; i++) {
@@ -336,6 +309,8 @@ void eventfs_set_ei_status_free(struct tracefs_inode *ti, struct dentry *dentry)
if (WARN_ON_ONCE(i == ei->nr_entries))
goto out;
ei->d_children[i] = NULL;
+ } else if (ei->is_freed) {
+ free_ei(ei);
} else {
ei->dentry = NULL;
}
@@ -962,13 +937,79 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry
return ERR_PTR(-ENOMEM);
}
+static LLIST_HEAD(free_list);
+
+static void eventfs_workfn(struct work_struct *work)
+{
+ struct eventfs_inode *ei, *tmp;
+ struct llist_node *llnode;
+
+ llnode = llist_del_all(&free_list);
+ llist_for_each_entry_safe(ei, tmp, llnode, llist) {
+ /* This dput() matches the dget() from unhook_dentry() */
+ for (int i = 0; i < ei->nr_entries; i++) {
+ if (ei->d_children[i])
+ dput(ei->d_children[i]);
+ }
+ /* This should only get here if it had a dentry */
+ if (!WARN_ON_ONCE(!ei->dentry))
+ dput(ei->dentry);
+ }
+}
+
+static DECLARE_WORK(eventfs_work, eventfs_workfn);
+
static void free_rcu_ei(struct rcu_head *head)
{
struct eventfs_inode *ei = container_of(head, struct eventfs_inode, rcu);
+ if (ei->dentry) {
+ /* Do not free the ei until all references of dentry are gone */
+ if (llist_add(&ei->llist, &free_list))
+ queue_work(system_unbound_wq, &eventfs_work);
+ return;
+ }
+
+ /* If the ei doesn't have a dentry, neither should its children */
+ for (int i = 0; i < ei->nr_entries; i++) {
+ WARN_ON_ONCE(ei->d_children[i]);
+ }
+
free_ei(ei);
}
+static void unhook_dentry(struct dentry *dentry)
+{
+ struct inode *inode;
+
+ if (!dentry)
+ return;
+
+ /* Keep the dentry from being freed yet (see eventfs_workfn()) */
+ dget(dentry);
+
+ inode = dentry->d_inode;
+ inode_lock(inode);
+ if (d_is_dir(dentry))
+ inode->i_flags |= S_DEAD;
+ clear_nlink(inode);
+ inode_unlock(inode);
+
+ inode = dentry->d_parent->d_inode;
+ inode_lock(inode);
+
+ /* Remove its visibility */
+ d_invalidate(dentry);
+ if (d_is_dir(dentry))
+ fsnotify_rmdir(inode, dentry);
+ else
+ fsnotify_unlink(inode, dentry);
+
+ if (d_is_dir(dentry))
+ drop_nlink(inode);
+ inode_unlock(inode);
+}
+
/**
* eventfs_remove_rec - remove eventfs dir or file from list
* @ei: eventfs_inode to be removed.
@@ -1006,34 +1047,6 @@ static void eventfs_remove_rec(struct eventfs_inode *ei, struct list_head *head,
list_add_tail(&ei->del_list, head);
}
-static void unhook_dentry(struct dentry **dentry, struct dentry **list)
-{
- if (*dentry) {
- unsigned long ptr = (unsigned long)*list;
-
- /* Keep the dentry from being freed yet */
- dget(*dentry);
-
- /*
- * Paranoid: The dget() above should prevent the dentry
- * from being freed and calling eventfs_set_ei_status_free().
- * But just in case, set the link list LSB pointer to 1
- * and have eventfs_set_ei_status_free() check that to
- * make sure that if it does happen, it will not think
- * the d_fsdata is an eventfs_inode.
- *
- * For this to work, no eventfs_inode should be allocated
- * on a odd space, as the ef should always be allocated
- * to be at least word aligned. Check for that too.
- */
- WARN_ON_ONCE(ptr & 1);
-
- (*dentry)->d_fsdata = (void *)(ptr | 1);
- *list = *dentry;
- *dentry = NULL;
- }
-}
-
/**
* eventfs_remove_dir - remove eventfs dir or file from list
* @ei: eventfs_inode to be removed.
@@ -1044,61 +1057,28 @@ void eventfs_remove_dir(struct eventfs_inode *ei)
{
struct eventfs_inode *tmp;
LIST_HEAD(ei_del_list);
- struct dentry *dentry_list = NULL;
- struct dentry *dentry;
- struct inode *inode;
- int i;
if (!ei)
return;
+ /*
+ * Move the deleted eventfs_inodes onto the ei_del_list
+ * which will also set the is_freed value. Note, this has to be
+ * done under the eventfs_mutex, but the deletions of
+ * the dentries must be done outside the eventfs_mutex.
+ * Hence moving them to this temporary list.
+ */
mutex_lock(&eventfs_mutex);
eventfs_remove_rec(ei, &ei_del_list, 0);
-
- list_for_each_entry_safe(ei, tmp, &ei_del_list, del_list) {
- for (i = 0; i < ei->nr_entries; i++)
- unhook_dentry(&ei->d_children[i], &dentry_list);
- unhook_dentry(&ei->dentry, &dentry_list);
- call_srcu(&eventfs_srcu, &ei->rcu, free_rcu_ei);
- }
mutex_unlock(&eventfs_mutex);
- while (dentry_list) {
- unsigned long ptr;
-
- dentry = dentry_list;
- ptr = (unsigned long)dentry->d_fsdata & ~1UL;
- dentry_list = (struct dentry *)ptr;
- dentry->d_fsdata = NULL;
-
- inode = dentry->d_inode;
- inode_lock(inode);
- if (d_is_dir(dentry))
- inode->i_flags |= S_DEAD;
- clear_nlink(inode);
- inode_unlock(inode);
-
- inode = dentry->d_parent->d_inode;
- inode_lock(inode);
-
- /* Remove its visibility */
- d_invalidate(dentry);
- if (d_is_dir(dentry))
- fsnotify_rmdir(inode, dentry);
- else
- fsnotify_unlink(inode, dentry);
-
- if (d_is_dir(dentry))
- drop_nlink(inode);
- inode_unlock(inode);
+ list_for_each_entry_safe(ei, tmp, &ei_del_list, del_list) {
- mutex_lock(&eventfs_mutex);
- /* dentry should now have at least a single reference */
- WARN_ONCE((int)d_count(dentry) < 1,
- "dentry %px (%s) less than one reference (%d) after invalidate\n",
- dentry, dentry->d_name.name, d_count(dentry));
- mutex_unlock(&eventfs_mutex);
- dput(dentry);
+ for (int i = 0; i < ei->nr_entries; i++)
+ unhook_dentry(ei->d_children[i]);
+ unhook_dentry(ei->dentry);
+ list_del(&ei->del_list);
+ call_srcu(&eventfs_srcu, &ei->rcu, free_rcu_ei);
}
}
diff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h
index 5f60bcd69289..06a1f220b901 100644
--- a/fs/tracefs/internal.h
+++ b/fs/tracefs/internal.h
@@ -54,10 +54,12 @@ struct eventfs_inode {
void *data;
/*
* Union - used for deletion
+ * @llist: for calling dput() if needed after RCU
* @del_list: list of eventfs_inode to delete
* @rcu: eventfs_inode to delete in RCU
*/
union {
+ struct llist_node llist;
struct list_head del_list;
struct rcu_head rcu;
};
--
2.42.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH v5 1/7] eventfs: Remove "is_freed" union with rcu head
2023-10-31 22:33 ` [PATCH v5 1/7] eventfs: Remove "is_freed" union with rcu head Steven Rostedt
@ 2023-11-01 12:19 ` Masami Hiramatsu
0 siblings, 0 replies; 3+ messages in thread
From: Masami Hiramatsu @ 2023-11-01 12:19 UTC (permalink / raw)
To: Steven Rostedt
Cc: linux-kernel, linux-trace-kernel, Masami Hiramatsu, Mark Rutland,
Ajay Kaher, Andrew Morton, stable
On Tue, 31 Oct 2023 18:33:27 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
> From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
>
> The eventfs_inode->is_freed was a union with the rcu_head with the
> assumption that when it was on the srcu list the head would contain a
> pointer which would make "is_freed" true. But that was a wrong assumption
> as the rcu head is a single link list where the last element is NULL.
>
> Instead, split the nr_entries integer so that "is_freed" is one bit and
> the nr_entries is the next 31 bits. As there shouldn't be more than 10
> (currently there's at most 5 to 7 depending on the config), this should
> not be a problem.
Yeah, even 16 bit nr_entries is enough.. (maybe the biggest user is
syscall event group)
Looks good to me.
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Thank you,
> Cc: stable@vger.kernel.org
> Fixes: 63940449555e7 ("eventfs: Implement eventfs lookup, read, open functions")
> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
> ---
> fs/tracefs/event_inode.c | 2 ++
> fs/tracefs/internal.h | 6 +++---
> 2 files changed, 5 insertions(+), 3 deletions(-)
>
> diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c
> index 754885dfe71c..2c2c75b2ad73 100644
> --- a/fs/tracefs/event_inode.c
> +++ b/fs/tracefs/event_inode.c
> @@ -824,6 +824,8 @@ static void eventfs_remove_rec(struct eventfs_inode *ei, struct list_head *head,
> eventfs_remove_rec(ei_child, head, level + 1);
> }
>
> + ei->is_freed = 1;
> +
> list_del_rcu(&ei->list);
> list_add_tail(&ei->del_list, head);
> }
> diff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h
> index 64fde9490f52..c7d88aaa949f 100644
> --- a/fs/tracefs/internal.h
> +++ b/fs/tracefs/internal.h
> @@ -23,6 +23,7 @@ struct tracefs_inode {
> * @d_parent: pointer to the parent's dentry
> * @d_children: The array of dentries to represent the files when created
> * @data: The private data to pass to the callbacks
> + * @is_freed: Flag set if the eventfs is on its way to be freed
> * @nr_entries: The number of items in @entries
> */
> struct eventfs_inode {
> @@ -38,14 +39,13 @@ struct eventfs_inode {
> * Union - used for deletion
> * @del_list: list of eventfs_inode to delete
> * @rcu: eventfs_inode to delete in RCU
> - * @is_freed: node is freed if one of the above is set
> */
> union {
> struct list_head del_list;
> struct rcu_head rcu;
> - unsigned long is_freed;
> };
> - int nr_entries;
> + unsigned int is_freed:1;
> + unsigned int nr_entries:31;
> };
>
> static inline struct tracefs_inode *get_tracefs(const struct inode *inode)
> --
> 2.42.0
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2023-11-01 12:20 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20231031223326.794680978@goodmis.org>
2023-10-31 22:33 ` [PATCH v5 1/7] eventfs: Remove "is_freed" union with rcu head Steven Rostedt
2023-11-01 12:19 ` Masami Hiramatsu
2023-10-31 22:33 ` [PATCH v5 6/7] eventfs: Delete eventfs_inode when the last dentry is freed Steven Rostedt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox