summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhiyu Zhang <zhiyuzhang999@gmail.com>2026-10-29 01:41:36 -0400
committerChristian Brauner <brauner@kernel.org>2026-10-30 15:53:44 +0100
commit53d72818f9e2aeb03a6053544ee6efa4eb2a3988 (patch)
treed1ed0a7f2b4b33ec05aea5b06f194c15cff55a6a
parent87d02f3e98a86abf70441b0a0418e01cb1d64b44 (diff)
downloadlinux-53d72818f9e2aeb03a6053544ee6efa4eb2a3988.tar.gz
linux-53d72818f9e2aeb03a6053544ee6efa4eb2a3988.zip
watch_queue: keep info_id separate from rcu_head
struct watch overlays info_id with rcu_head. A notification poster reads info_id under rcu_read_lock(), but a concurrent final put can enqueue the watch with call_rcu(). Enqueueing the callback writes rcu_head before the grace period has elapsed, so it can corrupt info_id while the object remains legally visible to the reader. __post_watch_notification() then merges that value into the notification info word without masking it. Pointer bits can corrupt WATCH_INFO_LENGTH and make post_one_notification() copy beyond the posting task's stack record. Give info_id storage separate from rcu_head so RCU enqueueing cannot modify it. Place it after the kref, which uses existing tail padding on x86-64. Mask info_id with WATCH_INFO_ID at the point of use as an additional invariant check. An unprivileged shell and keyctl reproducer was run for 30 seconds in QEMU. The vulnerable baseline produced malformed notification lengths, while this change completed with 288631 key notifications and no malformed record. Fixes: c73be61cede5 ("pipe: Add general notification queue support") Reported-by: Tong Liu <lyutoon@gmail.com> Reported-by: Qi Tang <tpluszz77@gmail.com> Reported-by: Zhiyu Zhang <zhiyuzhang999@gmail.com> Closes: https://lore.kernel.org/lkml/CALf2hKveij_sHgANY9Aktm1dPa4wfKjKNkHyp9yPbjORWFXagA@mail.gmail.com/ Link: https://lore.kernel.org/netdev/CALf2hKvp0NPVLwr=a3+jUHtPohxbra8gox+aYAfWathJnn-J+Q@mail.gmail.com/ Signed-off-by: Zhiyu Zhang <zhiyuzhang999@gmail.com> Link: https://patch.msgid.link/20261029054136.1174216-1-zhiyuzhang999@gmail.com Reviewed-by: Bradley Morgan <brads@mainlining.org> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
-rw-r--r--include/linux/watch_queue.h6
-rw-r--r--kernel/watch_queue.c2
2 files changed, 3 insertions, 5 deletions
diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h
index 429c7b6afead..b87e1babb5f0 100644
--- a/include/linux/watch_queue.h
+++ b/include/linux/watch_queue.h
@@ -52,10 +52,7 @@ struct watch_queue {
* Representation of a watch on an object.
*/
struct watch {
- union {
- struct rcu_head rcu;
- u32 info_id; /* ID to be OR'd in to info field */
- };
+ struct rcu_head rcu;
struct watch_queue __rcu *queue; /* Queue to post events to */
struct hlist_node queue_node; /* Link in queue->watches */
struct watch_list __rcu *watch_list;
@@ -64,6 +61,7 @@ struct watch {
void *private; /* Private data for the watched object */
u64 id; /* Internal identifier */
struct kref usage; /* Object usage count */
+ u32 info_id; /* ID to be OR'd in to info field */
};
/*
diff --git a/kernel/watch_queue.c b/kernel/watch_queue.c
index 932a88ac1f39..453dcf613205 100644
--- a/kernel/watch_queue.c
+++ b/kernel/watch_queue.c
@@ -212,7 +212,7 @@ void __post_watch_notification(struct watch_list *wlist,
if (watch->id != id)
continue;
n->info &= ~WATCH_INFO_ID;
- n->info |= watch->info_id;
+ n->info |= watch->info_id & WATCH_INFO_ID;
wqueue = rcu_dereference(watch->queue);
wf = rcu_dereference(wqueue->filter);