diff options
| author | Zhiyu Zhang <zhiyuzhang999@gmail.com> | 2026-10-29 01:41:36 -0400 |
|---|---|---|
| committer | Christian Brauner <brauner@kernel.org> | 2026-10-30 15:53:44 +0100 |
| commit | 53d72818f9e2aeb03a6053544ee6efa4eb2a3988 (patch) | |
| tree | d1ed0a7f2b4b33ec05aea5b06f194c15cff55a6a | |
| parent | 87d02f3e98a86abf70441b0a0418e01cb1d64b44 (diff) | |
| download | linux-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.h | 6 | ||||
| -rw-r--r-- | 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); |
