--- /dev/null
+From e730558adffb88a52e562db089e969ee9510184a Mon Sep 17 00:00:00 2001
+From: Amir Goldstein <amir73il@gmail.com>
+Date: Wed, 11 May 2022 22:02:13 +0300
+Subject: fsnotify: consistent behavior for parent not watching children
+
+From: Amir Goldstein <amir73il@gmail.com>
+
+commit e730558adffb88a52e562db089e969ee9510184a upstream.
+
+The logic for handling events on child in groups that have a mark on
+the parent inode, but without FS_EVENT_ON_CHILD flag in the mask is
+duplicated in several places and inconsistent.
+
+Move the logic into the preparation of mark type iterator, so that the
+parent mark type will be excluded from all mark type iterations in that
+case.
+
+This results in several subtle changes of behavior, hopefully all
+desired changes of behavior, for example:
+
+- Group A has a mount mark with FS_MODIFY in mask
+- Group A has a mark with ignore mask that does not survive FS_MODIFY
+ and does not watch children on directory D.
+- Group B has a mark with FS_MODIFY in mask that does watch children
+ on directory D.
+- FS_MODIFY event on file D/foo should not clear the ignore mask of
+ group A, but before this change it does
+
+And if group A ignore mask was set to survive FS_MODIFY:
+- FS_MODIFY event on file D/foo should be reported to group A on account
+ of the mount mark, but before this change it is wrongly ignored
+
+Fixes: 2f02fd3fa13e ("fanotify: fix ignore mask logic for events on child and on dir")
+Reported-by: Jan Kara <jack@suse.com>
+Link: https://lore.kernel.org/linux-fsdevel/20220314113337.j7slrb5srxukztje@quack3.lan/
+Signed-off-by: Amir Goldstein <amir73il@gmail.com>
+Signed-off-by: Jan Kara <jack@suse.cz>
+Link: https://lore.kernel.org/r/20220511190213.831646-3-amir73il@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/notify/fanotify/fanotify.c | 10 +---------
+ fs/notify/fsnotify.c | 34 +++++++++++++++++++---------------
+ 2 files changed, 20 insertions(+), 24 deletions(-)
+
+--- a/fs/notify/fanotify/fanotify.c
++++ b/fs/notify/fanotify/fanotify.c
+@@ -320,7 +320,7 @@ static u32 fanotify_group_event_mask(str
+ }
+
+ fsnotify_foreach_iter_mark_type(iter_info, mark, type) {
+- /* Apply ignore mask regardless of ISDIR and ON_CHILD flags */
++ /* Apply ignore mask regardless of mark's ISDIR flag */
+ marks_ignored_mask |= mark->ignored_mask;
+
+ /*
+@@ -330,14 +330,6 @@ static u32 fanotify_group_event_mask(str
+ if (event_mask & FS_ISDIR && !(mark->mask & FS_ISDIR))
+ continue;
+
+- /*
+- * If the event is on a child and this mark is on a parent not
+- * watching children, don't send it!
+- */
+- if (type == FSNOTIFY_ITER_TYPE_PARENT &&
+- !(mark->mask & FS_EVENT_ON_CHILD))
+- continue;
+-
+ marks_mask |= mark->mask;
+
+ /* Record the mark types of this group that matched the event */
+--- a/fs/notify/fsnotify.c
++++ b/fs/notify/fsnotify.c
+@@ -290,22 +290,15 @@ static int fsnotify_handle_event(struct
+ }
+
+ if (parent_mark) {
+- /*
+- * parent_mark indicates that the parent inode is watching
+- * children and interested in this event, which is an event
+- * possible on child. But is *this mark* watching children and
+- * interested in this event?
+- */
+- if (parent_mark->mask & FS_EVENT_ON_CHILD) {
+- ret = fsnotify_handle_inode_event(group, parent_mark, mask,
+- data, data_type, dir, name, 0);
+- if (ret)
+- return ret;
+- }
+- if (!inode_mark)
+- return 0;
++ ret = fsnotify_handle_inode_event(group, parent_mark, mask,
++ data, data_type, dir, name, 0);
++ if (ret)
++ return ret;
+ }
+
++ if (!inode_mark)
++ return 0;
++
+ if (mask & FS_EVENT_ON_CHILD) {
+ /*
+ * Some events can be sent on both parent dir and child marks
+@@ -422,8 +415,19 @@ static bool fsnotify_iter_select_report_
+ iter_info->report_mask = 0;
+ fsnotify_foreach_iter_type(type) {
+ mark = iter_info->marks[type];
+- if (mark && mark->group == iter_info->current_group)
++ if (mark && mark->group == iter_info->current_group) {
++ /*
++ * FSNOTIFY_ITER_TYPE_PARENT indicates that this inode
++ * is watching children and interested in this event,
++ * which is an event possible on child.
++ * But is *this mark* watching children?
++ */
++ if (type == FSNOTIFY_ITER_TYPE_PARENT &&
++ !(mark->mask & FS_EVENT_ON_CHILD))
++ continue;
++
+ fsnotify_iter_set_report_type(iter_info, type);
++ }
+ }
+
+ return true;
--- /dev/null
+From 14362a2541797cf9df0e86fb12dcd7950baf566e Mon Sep 17 00:00:00 2001
+From: Amir Goldstein <amir73il@gmail.com>
+Date: Wed, 11 May 2022 22:02:12 +0300
+Subject: fsnotify: introduce mark type iterator
+
+From: Amir Goldstein <amir73il@gmail.com>
+
+commit 14362a2541797cf9df0e86fb12dcd7950baf566e upstream.
+
+fsnotify_foreach_iter_mark_type() is used to reduce boilerplate code
+of iterating all marks of a specific group interested in an event
+by consulting the iterator report_mask.
+
+Use an open coded version of that iterator in fsnotify_iter_next()
+that collects all marks of the current iteration group without
+consulting the iterator report_mask.
+
+At the moment, the two iterator variants are the same, but this
+decoupling will allow us to exclude some of the group's marks from
+reporting the event, for example for event on child and inode marks
+on parent did not request to watch events on children.
+
+Fixes: 2f02fd3fa13e ("fanotify: fix ignore mask logic for events on child and on dir")
+Reported-by: Jan Kara <jack@suse.com>
+Signed-off-by: Amir Goldstein <amir73il@gmail.com>
+Signed-off-by: Jan Kara <jack@suse.cz>
+Link: https://lore.kernel.org/r/20220511190213.831646-2-amir73il@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/notify/fanotify/fanotify.c | 14 ++--------
+ fs/notify/fsnotify.c | 53 +++++++++++++++++++--------------------
+ include/linux/fsnotify_backend.h | 31 +++++++++++++++++-----
+ 3 files changed, 54 insertions(+), 44 deletions(-)
+
+--- a/fs/notify/fanotify/fanotify.c
++++ b/fs/notify/fanotify/fanotify.c
+@@ -319,11 +319,7 @@ static u32 fanotify_group_event_mask(str
+ return 0;
+ }
+
+- fsnotify_foreach_iter_type(type) {
+- if (!fsnotify_iter_should_report_type(iter_info, type))
+- continue;
+- mark = iter_info->marks[type];
+-
++ fsnotify_foreach_iter_mark_type(iter_info, mark, type) {
+ /* Apply ignore mask regardless of ISDIR and ON_CHILD flags */
+ marks_ignored_mask |= mark->ignored_mask;
+
+@@ -849,16 +845,14 @@ out:
+ */
+ static __kernel_fsid_t fanotify_get_fsid(struct fsnotify_iter_info *iter_info)
+ {
++ struct fsnotify_mark *mark;
+ int type;
+ __kernel_fsid_t fsid = {};
+
+- fsnotify_foreach_iter_type(type) {
++ fsnotify_foreach_iter_mark_type(iter_info, mark, type) {
+ struct fsnotify_mark_connector *conn;
+
+- if (!fsnotify_iter_should_report_type(iter_info, type))
+- continue;
+-
+- conn = READ_ONCE(iter_info->marks[type]->connector);
++ conn = READ_ONCE(mark->connector);
+ /* Mark is just getting destroyed or created? */
+ if (!conn)
+ continue;
+--- a/fs/notify/fsnotify.c
++++ b/fs/notify/fsnotify.c
+@@ -335,31 +335,23 @@ static int send_to_group(__u32 mask, con
+ struct fsnotify_mark *mark;
+ int type;
+
+- if (WARN_ON(!iter_info->report_mask))
++ if (!iter_info->report_mask)
+ return 0;
+
+ /* clear ignored on inode modification */
+ if (mask & FS_MODIFY) {
+- fsnotify_foreach_iter_type(type) {
+- if (!fsnotify_iter_should_report_type(iter_info, type))
+- continue;
+- mark = iter_info->marks[type];
+- if (mark &&
+- !(mark->flags & FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY))
++ fsnotify_foreach_iter_mark_type(iter_info, mark, type) {
++ if (!(mark->flags &
++ FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY))
+ mark->ignored_mask = 0;
+ }
+ }
+
+- fsnotify_foreach_iter_type(type) {
+- if (!fsnotify_iter_should_report_type(iter_info, type))
+- continue;
+- mark = iter_info->marks[type];
+- /* does the object mark tell us to do something? */
+- if (mark) {
+- group = mark->group;
+- marks_mask |= mark->mask;
+- marks_ignored_mask |= mark->ignored_mask;
+- }
++ /* Are any of the group marks interested in this event? */
++ fsnotify_foreach_iter_mark_type(iter_info, mark, type) {
++ group = mark->group;
++ marks_mask |= mark->mask;
++ marks_ignored_mask |= mark->ignored_mask;
+ }
+
+ pr_debug("%s: group=%p mask=%x marks_mask=%x marks_ignored_mask=%x data=%p data_type=%d dir=%p cookie=%d\n",
+@@ -403,11 +395,11 @@ static struct fsnotify_mark *fsnotify_ne
+
+ /*
+ * iter_info is a multi head priority queue of marks.
+- * Pick a subset of marks from queue heads, all with the
+- * same group and set the report_mask for selected subset.
+- * Returns the report_mask of the selected subset.
++ * Pick a subset of marks from queue heads, all with the same group
++ * and set the report_mask to a subset of the selected marks.
++ * Returns false if there are no more groups to iterate.
+ */
+-static unsigned int fsnotify_iter_select_report_types(
++static bool fsnotify_iter_select_report_types(
+ struct fsnotify_iter_info *iter_info)
+ {
+ struct fsnotify_group *max_prio_group = NULL;
+@@ -423,30 +415,37 @@ static unsigned int fsnotify_iter_select
+ }
+
+ if (!max_prio_group)
+- return 0;
++ return false;
+
+ /* Set the report mask for marks from same group as max prio group */
++ iter_info->current_group = max_prio_group;
+ iter_info->report_mask = 0;
+ fsnotify_foreach_iter_type(type) {
+ mark = iter_info->marks[type];
+- if (mark &&
+- fsnotify_compare_groups(max_prio_group, mark->group) == 0)
++ if (mark && mark->group == iter_info->current_group)
+ fsnotify_iter_set_report_type(iter_info, type);
+ }
+
+- return iter_info->report_mask;
++ return true;
+ }
+
+ /*
+- * Pop from iter_info multi head queue, the marks that were iterated in the
++ * Pop from iter_info multi head queue, the marks that belong to the group of
+ * current iteration step.
+ */
+ static void fsnotify_iter_next(struct fsnotify_iter_info *iter_info)
+ {
++ struct fsnotify_mark *mark;
+ int type;
+
++ /*
++ * We cannot use fsnotify_foreach_iter_mark_type() here because we
++ * may need to advance a mark of type X that belongs to current_group
++ * but was not selected for reporting.
++ */
+ fsnotify_foreach_iter_type(type) {
+- if (fsnotify_iter_should_report_type(iter_info, type))
++ mark = iter_info->marks[type];
++ if (mark && mark->group == iter_info->current_group)
+ iter_info->marks[type] =
+ fsnotify_next_mark(iter_info->marks[type]);
+ }
+--- a/include/linux/fsnotify_backend.h
++++ b/include/linux/fsnotify_backend.h
+@@ -370,6 +370,7 @@ static inline bool fsnotify_valid_obj_ty
+
+ struct fsnotify_iter_info {
+ struct fsnotify_mark *marks[FSNOTIFY_ITER_TYPE_COUNT];
++ struct fsnotify_group *current_group;
+ unsigned int report_mask;
+ int srcu_idx;
+ };
+@@ -386,20 +387,31 @@ static inline void fsnotify_iter_set_rep
+ iter_info->report_mask |= (1U << iter_type);
+ }
+
+-static inline void fsnotify_iter_set_report_type_mark(
+- struct fsnotify_iter_info *iter_info, int iter_type,
+- struct fsnotify_mark *mark)
++static inline struct fsnotify_mark *fsnotify_iter_mark(
++ struct fsnotify_iter_info *iter_info, int iter_type)
+ {
+- iter_info->marks[iter_type] = mark;
+- iter_info->report_mask |= (1U << iter_type);
++ if (fsnotify_iter_should_report_type(iter_info, iter_type))
++ return iter_info->marks[iter_type];
++ return NULL;
++}
++
++static inline int fsnotify_iter_step(struct fsnotify_iter_info *iter, int type,
++ struct fsnotify_mark **markp)
++{
++ while (type < FSNOTIFY_ITER_TYPE_COUNT) {
++ *markp = fsnotify_iter_mark(iter, type);
++ if (*markp)
++ break;
++ type++;
++ }
++ return type;
+ }
+
+ #define FSNOTIFY_ITER_FUNCS(name, NAME) \
+ static inline struct fsnotify_mark *fsnotify_iter_##name##_mark( \
+ struct fsnotify_iter_info *iter_info) \
+ { \
+- return (iter_info->report_mask & (1U << FSNOTIFY_ITER_TYPE_##NAME)) ? \
+- iter_info->marks[FSNOTIFY_ITER_TYPE_##NAME] : NULL; \
++ return fsnotify_iter_mark(iter_info, FSNOTIFY_ITER_TYPE_##NAME); \
+ }
+
+ FSNOTIFY_ITER_FUNCS(inode, INODE)
+@@ -409,6 +421,11 @@ FSNOTIFY_ITER_FUNCS(sb, SB)
+
+ #define fsnotify_foreach_iter_type(type) \
+ for (type = 0; type < FSNOTIFY_ITER_TYPE_COUNT; type++)
++#define fsnotify_foreach_iter_mark_type(iter, mark, type) \
++ for (type = 0; \
++ type = fsnotify_iter_step(iter, type, &mark), \
++ type < FSNOTIFY_ITER_TYPE_COUNT; \
++ type++)
+
+ /*
+ * fsnotify_connp_t is what we embed in objects which connector can be attached