From: odion@efficios.com
To: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Peter Zijlstra <peterz@infradead.org>,
"Paul E. McKenney" <paulmck@kernel.org>,
Boqun Feng <boqun@kernel.org>,
LKML <linux-kernel@vger.kernel.org>,
Thomas Gleixner <tglx@kernel.org>,
Dmitry Vyukov <dvyukov@google.com>,
David Matlack <dmatlack@google.com>,
Marco Elver <elver@google.com>,
Sean Christopherson <seanjc@google.com>,
Wei Liu <wei.liu@kernel.org>, Florian Weimer <fweimer@redhat.com>,
Mathias Stearn <mathias@mongodb.com>,
Chris Kennelly <ckennelly@google.com>,
Blake Oler <blake.oler@mongodb.com>,
Rich Felker <dalias@libc.org>,
Matthew Wilcox <willy@infradead.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Carlos O'Donell <codonell@redhat.com>,
Olivier Dion <odion@efficios.com>
Subject: [RFC PATCH 3/5] rseq: apply operations on exit to user space
Date: Fri, 28 Aug 2026 11:33:41 -0400 [thread overview]
Message-ID: <20260828153349.8061-4-odion@efficios.com> (raw)
In-Reply-To: <20260828153349.8061-1-odion@efficios.com>
From: Olivier Dion <odion@efficios.com>
Add rseq_apply_ops(), which walks the task's circular operation list on
return to user space and applies each operation.
Wire the walk into rseq_exit_user_update() for both the ids-unchanged
and ids-changed paths, gated on rseq_event::rseq_op. Since operation
processing is a persistent per-task state rather than a one-shot event,
introduce rseq_clear_one_shot_events() which preserves the rseq_op bit
when clearing events on the way out.
len, src and dst are re-read from user memory and re-validated on every
application, since user space may change them after registration. A
broken ABI contract will fault the process.
Signed-off-by: Olivier Dion <odion@efficios.com>
---
include/linux/rseq_entry.h | 157 +++++++++++++++++++++++++++++++++++--
1 file changed, 152 insertions(+), 5 deletions(-)
diff --git a/include/linux/rseq_entry.h b/include/linux/rseq_entry.h
index ed9da6e41a2a..df083b0ee14a 100644
--- a/include/linux/rseq_entry.h
+++ b/include/linux/rseq_entry.h
@@ -247,6 +247,137 @@ static __always_inline bool rseq_grant_slice_extension(unsigned long ti_work, un
#define rseq_slice_clear_user(rseq, efault) do { } while (0)
#endif /* !CONFIG_RSEQ_SLICE_EXTENSION */
+/*
+ * Validate and perform a single reset word operation: reset @dst to *@src,
+ * or to zero when @src is 0. @len, @src and @dst come from user memory and
+ * are re-validated on every application, since user space may have changed
+ * them since registration. A broken ABI contract returns false, which faults
+ * the process.
+ */
+static __always_inline bool rseq_op_reset_word(u64 src, u64 dst, u32 len,
+ void __user *node)
+{
+ if (unlikely(!IS_ALIGNED(dst, len) || (src && !IS_ALIGNED(src, len)))) {
+ pr_info_ratelimited("rseq: bad operation alignment len=%u src=%llx dst=%llx from %p\n",
+ len, src, dst, node);
+ return false;
+ }
+
+ switch (len) {
+ case 4: {
+ u32 __user *udst = (u32 __user *)dst;
+ u32 v = 0;
+
+ if (src) {
+ u32 __user *usrc = (u32 __user *)src;
+
+ scoped_user_read_access(usrc, efault)
+ unsafe_get_user(v, usrc, efault);
+ }
+ scoped_user_write_access(udst, efault)
+ unsafe_put_user(v, udst, efault);
+ return true;
+ }
+ case 8: {
+ u64 __user *udst = (u64 __user *)dst;
+ u64 v = 0;
+
+ if (src) {
+ u64 __user *usrc = (u64 __user *)src;
+
+ scoped_user_read_access(usrc, efault)
+ unsafe_get_user(v, usrc, efault);
+ }
+ scoped_user_write_access(udst, efault)
+ unsafe_put_user(v, udst, efault);
+ return true;
+ }
+ default:
+ pr_info("rseq: bad operation length=%u from %p\n", len, node);
+ return false;
+ }
+efault:
+ pr_info("rseq: fault while applying operation from %p\n", node);
+ return false;
+}
+
+static __always_inline bool rseq_apply_ops(struct task_struct *t)
+{
+ struct rseq __user *rseq = t->rseq.usrptr;
+ struct rseq_op_node __user *sentinel = &rseq->rseq_op_list;
+ struct rseq_op_node __user *node;
+ unsigned int limit = RSEQ_OP_LIST_LIMIT;
+ u64 next;
+
+ WARN_ONCE(!t->rseq.event.rseq_op, "rseq operations not enabled");
+
+ scoped_user_read_access(sentinel, efault)
+ unsafe_get_user(next, &sentinel->next, efault);
+
+ node = (struct rseq_op_node __user *)next;
+
+ while (node != sentinel && limit--) {
+ u8 type;
+
+ scoped_user_read_access(node, efault) {
+ unsafe_get_user(type, &node->type, efault);
+ unsafe_get_user(next, &node->next, efault);
+ }
+
+ switch (type) {
+ case RSEQ_OP_RESET: {
+ struct rseq_op_reset __user *op =
+ (struct rseq_op_reset __user *)node;
+ u64 src, dst;
+ u32 len;
+
+ scoped_user_read_access(op, efault) {
+ unsafe_get_user(src, &op->src, efault);
+ unsafe_get_user(dst, &op->dst, efault);
+ unsafe_get_user(len, &op->len, efault);
+ }
+
+ if (!rseq_op_reset_word(src, dst, len, node))
+ return false;
+ break;
+ }
+ case RSEQ_OP_RESET_WITH_STRIDE_CPUID: /* fall through */
+ case RSEQ_OP_RESET_WITH_STRIDE_MMCID: {
+ struct rseq_op_reset_with_stride __user *op =
+ (struct rseq_op_reset_with_stride __user *)node;
+ u64 src, dst, dst_stride;
+ u32 len, index;
+
+ scoped_user_read_access(op, efault) {
+ unsafe_get_user(src, &op->src, efault);
+ unsafe_get_user(dst, &op->dst, efault);
+ unsafe_get_user(dst_stride, &op->dst_stride, efault);
+ unsafe_get_user(len, &op->len, efault);
+ }
+ index = (type == RSEQ_OP_RESET_WITH_STRIDE_CPUID) ?
+ t->rseq.ids.cpu_id : t->rseq.ids.mm_cid;
+ dst += dst_stride * index;
+
+ if (!rseq_op_reset_word(src, dst, len, node))
+ return false;
+
+ break;
+ }
+ default:
+ pr_info("rseq: bad operation type=%u from %p\n",
+ type, node);
+ return false;
+ }
+
+ node = (struct rseq_op_node __user *)next;
+ }
+
+ return node == sentinel;
+efault:
+ pr_info("rseq: fault while walking operation list\n");
+ return false;
+}
+
bool rseq_debug_update_user_cs(struct task_struct *t, struct pt_regs *regs, unsigned long csaddr);
static __always_inline void rseq_note_user_irq_entry(void)
@@ -632,6 +763,10 @@ static __always_inline bool rseq_exit_user_update(struct pt_regs *regs, struct t
if (unlikely(!rseq_update_user_cs(t, regs, csaddr)))
return false;
}
+
+ if (t->rseq.event.rseq_op && !rseq_apply_ops(t))
+ return false;
+
return true;
}
@@ -642,11 +777,22 @@ static __always_inline bool rseq_exit_user_update(struct pt_regs *regs, struct t
.node_id = cpu_to_node(cpu),
};
- return rseq_update_usr(t, regs, &ids);
+ if (!rseq_update_usr(t, regs, &ids))
+ return false;
+
+ if (t->rseq.event.rseq_op && !rseq_apply_ops(t))
+ return false;
+
+ return true;
efault:
return false;
}
+static __always_inline void rseq_clear_one_shot_events(struct rseq_event *ev)
+{
+ ev->events &= (struct rseq_event){ .rseq_op = true }.events;
+}
+
static __always_inline bool __rseq_exit_to_user_mode_restart(struct pt_regs *regs)
{
struct task_struct *t = current;
@@ -674,8 +820,9 @@ static __always_inline bool __rseq_exit_to_user_mode_restart(struct pt_regs *reg
if (unlikely(!rseq_exit_user_update(regs, t)))
return true;
}
- /* Clear state so next entry starts from a clean slate */
- t->rseq.event.events = 0;
+ /* Clear one-shot events so next entry starts from a clean slate */
+ rseq_clear_one_shot_events(&t->rseq.event);
+
return false;
}
@@ -730,7 +877,7 @@ static __always_inline void rseq_syscall_exit_to_user_mode(void)
/* Needed to remove the store for the !lockdep case */
if (IS_ENABLED(CONFIG_LOCKDEP)) {
WARN_ON_ONCE(ev->sched_switch);
- ev->events = 0;
+ rseq_clear_one_shot_events(ev);
}
}
@@ -747,7 +894,7 @@ static __always_inline void rseq_irqentry_exit_to_user_mode(void)
* interrupt did not result in a schedule and therefore the
* rseq processing could not clear it.
*/
- ev->events = 0;
+ rseq_clear_one_shot_events(ev);
}
void __rseq_debug_syscall_return(struct pt_regs *regs);
--
2.54.0
next prev parent reply other threads:[~2026-08-28 15:41 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-28 15:33 [RFC PATCH 0/5] rseq: add support for RSEQ operations odion
2026-08-28 15:33 ` [RFC PATCH 1/5] rseq: uapi: add rseq operation definitions odion
2026-08-29 22:34 ` Dmitry Vyukov
2026-08-29 22:47 ` Dmitry Vyukov
2026-08-31 8:37 ` Florian Weimer
2026-09-08 16:01 ` Olivier Dion
2026-09-08 17:31 ` Florian Weimer
2026-09-08 22:15 ` Thomas Gleixner
2026-08-28 15:33 ` [RFC PATCH 2/5] rseq: add per-task rseq operation state odion
2026-08-29 22:37 ` Dmitry Vyukov
2026-08-28 15:33 ` odion [this message]
2026-08-29 22:42 ` [RFC PATCH 3/5] rseq: apply operations on exit to user space Dmitry Vyukov
2026-08-28 15:33 ` [RFC PATCH 4/5] rseq: register and unregister operations via prctl odion
2026-08-29 22:50 ` Dmitry Vyukov
2026-08-28 15:33 ` [RFC PATCH 5/5] selftests/rseq: add coverage for rseq operations odion
2026-09-08 16:56 ` [RFC PATCH 0/5] rseq: add support for RSEQ operations Thomas Gleixner
2026-09-08 17:38 ` Olivier Dion
2026-09-08 21:29 ` Thomas Gleixner
2026-09-09 14:54 ` Dmitry Vyukov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260828153349.8061-4-odion@efficios.com \
--to=odion@efficios.com \
--cc=blake.oler@mongodb.com \
--cc=boqun@kernel.org \
--cc=ckennelly@google.com \
--cc=codonell@redhat.com \
--cc=dalias@libc.org \
--cc=dmatlack@google.com \
--cc=dvyukov@google.com \
--cc=elver@google.com \
--cc=fweimer@redhat.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mathias@mongodb.com \
--cc=mathieu.desnoyers@efficios.com \
--cc=paulmck@kernel.org \
--cc=peterz@infradead.org \
--cc=seanjc@google.com \
--cc=tglx@kernel.org \
--cc=wei.liu@kernel.org \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.