Re: [PATCH] watch_queue: keep info_id separate from rcu_head

From: Bradley Morgan

Date: Thu Oct 29 2026 - 07:54:24 EST


On 29 October 2026 05:41:36 GMT, Zhiyu Zhang <zhiyuzhang999@xxxxxxxxx>
wrote:
>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@xxxxxxxxx>
>Reported-by: Qi Tang <tpluszz77@xxxxxxxxx>

Reviewed-by: Bradley Morgan <brads@xxxxxxxxxxxxxx>

>Reported-by: Zhiyu Zhang <zhiyuzhang999@xxxxxxxxx>
>Closes: https://lore.kernel.org/lkml/CALf2hKveij_sHgANY9Aktm1dPa4wfKjKNkHyp9yPbjORWFXagA@xxxxxxxxxxxxxx/
>Link: https://lore.kernel.org/netdev/CALf2hKvp0NPVLwr=a3+jUHtPohxbra8gox+aYAfWathJnn-J+Q@xxxxxxxxxxxxxx/
>Signed-off-by: Zhiyu Zhang <zhiyuzhang999@xxxxxxxxx>
>---
> include/linux/watch_queue.h | 6 ++----
> kernel/watch_queue.c | 2 +-
> 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);
>


--- Thanks!