restore lost packages from 0.2.3 + fix overwritten 0.2.4 files
- Restore 29 recipe symlinks (libdrm, qtbase, dbus, sddm, pipewire, etc.) - Restore 33 patches (KDE, libdrm, mesa, pipewire, sddm, wireplumber) - Restore 20+ local/scripts (audit, lint, test, build helpers) - Restore src/cook/scheduler.rs, status.rs, gnu-config/ - Restore scripts/patch-inclusion-gate.sh, run_mini1.sh, validate-collision-log.sh - Recover TLC source from HEAD (was overwritten by 0.2.3 checkout) - Recover 11 local/docs plans from HEAD (were overwritten) - Recover qt6-wayland-smoke symlink from HEAD - Fix MOTD: remove garbled ASCII art, use clean text - Update version: 0.2.0 -> 0.2.4 in os-release, motd, config - Reduce filesystem_size: 1536 -> 512 MiB - Add ABSOLUTE RULE to AGENTS.md: never delete/ignore packages - Reduce pcid scheme log verbosity: info -> debug
This commit is contained in:
@@ -0,0 +1,771 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _LINUX_RSEQ_ENTRY_H
|
||||
#define _LINUX_RSEQ_ENTRY_H
|
||||
|
||||
/* Must be outside the CONFIG_RSEQ guard to resolve the stubs */
|
||||
#ifdef CONFIG_RSEQ_STATS
|
||||
#include <linux/percpu.h>
|
||||
|
||||
struct rseq_stats {
|
||||
unsigned long exit;
|
||||
unsigned long signal;
|
||||
unsigned long slowpath;
|
||||
unsigned long fastpath;
|
||||
unsigned long ids;
|
||||
unsigned long cs;
|
||||
unsigned long clear;
|
||||
unsigned long fixup;
|
||||
unsigned long s_granted;
|
||||
unsigned long s_expired;
|
||||
unsigned long s_revoked;
|
||||
unsigned long s_yielded;
|
||||
unsigned long s_aborted;
|
||||
};
|
||||
|
||||
DECLARE_PER_CPU(struct rseq_stats, rseq_stats);
|
||||
|
||||
/*
|
||||
* Slow path has interrupts and preemption enabled, but the fast path
|
||||
* runs with interrupts disabled so there is no point in having the
|
||||
* preemption checks implied in __this_cpu_inc() for every operation.
|
||||
*/
|
||||
#ifdef RSEQ_BUILD_SLOW_PATH
|
||||
#define rseq_stat_inc(which) this_cpu_inc((which))
|
||||
#else
|
||||
#define rseq_stat_inc(which) raw_cpu_inc((which))
|
||||
#endif
|
||||
|
||||
#else /* CONFIG_RSEQ_STATS */
|
||||
#define rseq_stat_inc(x) do { } while (0)
|
||||
#endif /* !CONFIG_RSEQ_STATS */
|
||||
|
||||
#ifdef CONFIG_RSEQ
|
||||
#include <linux/hrtimer_rearm.h>
|
||||
#include <linux/jump_label.h>
|
||||
#include <linux/rseq.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <linux/tracepoint-defs.h>
|
||||
|
||||
#ifdef CONFIG_TRACEPOINTS
|
||||
DECLARE_TRACEPOINT(rseq_update);
|
||||
DECLARE_TRACEPOINT(rseq_ip_fixup);
|
||||
void __rseq_trace_update(struct task_struct *t);
|
||||
void __rseq_trace_ip_fixup(unsigned long ip, unsigned long start_ip,
|
||||
unsigned long offset, unsigned long abort_ip);
|
||||
|
||||
static inline void rseq_trace_update(struct task_struct *t, struct rseq_ids *ids)
|
||||
{
|
||||
if (tracepoint_enabled(rseq_update) && ids)
|
||||
__rseq_trace_update(t);
|
||||
}
|
||||
|
||||
static inline void rseq_trace_ip_fixup(unsigned long ip, unsigned long start_ip,
|
||||
unsigned long offset, unsigned long abort_ip)
|
||||
{
|
||||
if (tracepoint_enabled(rseq_ip_fixup))
|
||||
__rseq_trace_ip_fixup(ip, start_ip, offset, abort_ip);
|
||||
}
|
||||
|
||||
#else /* CONFIG_TRACEPOINT */
|
||||
static inline void rseq_trace_update(struct task_struct *t, struct rseq_ids *ids) { }
|
||||
static inline void rseq_trace_ip_fixup(unsigned long ip, unsigned long start_ip,
|
||||
unsigned long offset, unsigned long abort_ip) { }
|
||||
#endif /* !CONFIG_TRACEPOINT */
|
||||
|
||||
DECLARE_STATIC_KEY_MAYBE(CONFIG_RSEQ_DEBUG_DEFAULT_ENABLE, rseq_debug_enabled);
|
||||
|
||||
#ifdef RSEQ_BUILD_SLOW_PATH
|
||||
#define rseq_inline
|
||||
#else
|
||||
#define rseq_inline __always_inline
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_RSEQ_SLICE_EXTENSION
|
||||
DECLARE_STATIC_KEY_TRUE(rseq_slice_extension_key);
|
||||
|
||||
static __always_inline bool rseq_slice_extension_enabled(void)
|
||||
{
|
||||
return static_branch_likely(&rseq_slice_extension_key);
|
||||
}
|
||||
|
||||
extern unsigned int rseq_slice_ext_nsecs;
|
||||
bool __rseq_arm_slice_extension_timer(void);
|
||||
|
||||
static __always_inline bool rseq_arm_slice_extension_timer(void)
|
||||
{
|
||||
if (!rseq_slice_extension_enabled())
|
||||
return false;
|
||||
|
||||
if (likely(!current->rseq.slice.state.granted))
|
||||
return false;
|
||||
|
||||
return __rseq_arm_slice_extension_timer();
|
||||
}
|
||||
|
||||
static __always_inline void rseq_slice_clear_grant(struct task_struct *t)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_RSEQ_STATS) && t->rseq.slice.state.granted)
|
||||
rseq_stat_inc(rseq_stats.s_revoked);
|
||||
t->rseq.slice.state.granted = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open coded, so it can be invoked within a user access region.
|
||||
*
|
||||
* This clears the user space state of the time slice extensions field only when
|
||||
* the task has registered the optimized RSEQ_ABI V2. Some legacy registrations,
|
||||
* e.g. TCMalloc, have conflicting non-ABI fields in struct RSEQ, which would be
|
||||
* overwritten by an unconditional write.
|
||||
*/
|
||||
#define rseq_slice_clear_user(rseq, efault) \
|
||||
do { \
|
||||
if (rseq_slice_extension_enabled()) \
|
||||
unsafe_put_user(0U, &rseq->slice_ctrl.all, efault); \
|
||||
} while (0)
|
||||
|
||||
static __always_inline bool __rseq_grant_slice_extension(bool work_pending)
|
||||
{
|
||||
struct task_struct *curr = current;
|
||||
struct rseq_slice_ctrl usr_ctrl;
|
||||
union rseq_slice_state state;
|
||||
struct rseq __user *rseq;
|
||||
|
||||
if (!rseq_slice_extension_enabled())
|
||||
return false;
|
||||
|
||||
/* If not enabled or not a return from interrupt, nothing to do. */
|
||||
state = curr->rseq.slice.state;
|
||||
state.enabled &= curr->rseq.event.user_irq;
|
||||
if (likely(!state.state))
|
||||
return false;
|
||||
|
||||
rseq = curr->rseq.usrptr;
|
||||
scoped_user_rw_access(rseq, efault) {
|
||||
|
||||
/*
|
||||
* Quick check conditions where a grant is not possible or
|
||||
* needs to be revoked.
|
||||
*
|
||||
* 1) Any TIF bit which needs to do extra work aside of
|
||||
* rescheduling prevents a grant.
|
||||
*
|
||||
* 2) A previous rescheduling request resulted in a slice
|
||||
* extension grant.
|
||||
*/
|
||||
if (unlikely(work_pending || state.granted)) {
|
||||
/* Clear user control unconditionally. No point for checking */
|
||||
unsafe_put_user(0U, &rseq->slice_ctrl.all, efault);
|
||||
rseq_slice_clear_grant(curr);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsafe_get_user(usr_ctrl.all, &rseq->slice_ctrl.all, efault);
|
||||
if (likely(!(usr_ctrl.request)))
|
||||
return false;
|
||||
|
||||
/* Grant the slice extention */
|
||||
usr_ctrl.request = 0;
|
||||
usr_ctrl.granted = 1;
|
||||
unsafe_put_user(usr_ctrl.all, &rseq->slice_ctrl.all, efault);
|
||||
}
|
||||
|
||||
rseq_stat_inc(rseq_stats.s_granted);
|
||||
|
||||
curr->rseq.slice.state.granted = true;
|
||||
/* Store expiry time for arming the timer on the way out */
|
||||
curr->rseq.slice.expires = data_race(rseq_slice_ext_nsecs) + ktime_get_mono_fast_ns();
|
||||
/*
|
||||
* This is racy against a remote CPU setting TIF_NEED_RESCHED in
|
||||
* several ways:
|
||||
*
|
||||
* 1)
|
||||
* CPU0 CPU1
|
||||
* clear_tsk()
|
||||
* set_tsk()
|
||||
* clear_preempt()
|
||||
* Raise scheduler IPI on CPU0
|
||||
* --> IPI
|
||||
* fold_need_resched() -> Folds correctly
|
||||
* 2)
|
||||
* CPU0 CPU1
|
||||
* set_tsk()
|
||||
* clear_tsk()
|
||||
* clear_preempt()
|
||||
* Raise scheduler IPI on CPU0
|
||||
* --> IPI
|
||||
* fold_need_resched() <- NOOP as TIF_NEED_RESCHED is false
|
||||
*
|
||||
* #1 is not any different from a regular remote reschedule as it
|
||||
* sets the previously not set bit and then raises the IPI which
|
||||
* folds it into the preempt counter
|
||||
*
|
||||
* #2 is obviously incorrect from a scheduler POV, but it's not
|
||||
* differently incorrect than the code below clearing the
|
||||
* reschedule request with the safety net of the timer.
|
||||
*
|
||||
* The important part is that the clearing is protected against the
|
||||
* scheduler IPI and also against any other interrupt which might
|
||||
* end up waking up a task and setting the bits in the middle of
|
||||
* the operation:
|
||||
*
|
||||
* clear_tsk()
|
||||
* ---> Interrupt
|
||||
* wakeup_on_this_cpu()
|
||||
* set_tsk()
|
||||
* set_preempt()
|
||||
* clear_preempt()
|
||||
*
|
||||
* which would be inconsistent state.
|
||||
*/
|
||||
scoped_guard(irq) {
|
||||
clear_tsk_need_resched(curr);
|
||||
clear_preempt_need_resched();
|
||||
}
|
||||
return true;
|
||||
|
||||
efault:
|
||||
force_sig(SIGSEGV);
|
||||
return false;
|
||||
}
|
||||
|
||||
static __always_inline bool rseq_grant_slice_extension(unsigned long ti_work, unsigned long mask)
|
||||
{
|
||||
if (unlikely(__rseq_grant_slice_extension(ti_work & mask))) {
|
||||
hrtimer_rearm_deferred_tif(ti_work);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#else /* CONFIG_RSEQ_SLICE_EXTENSION */
|
||||
static __always_inline bool rseq_slice_extension_enabled(void) { return false; }
|
||||
static __always_inline bool rseq_arm_slice_extension_timer(void) { return false; }
|
||||
static __always_inline void rseq_slice_clear_grant(struct task_struct *t) { }
|
||||
static __always_inline bool rseq_grant_slice_extension(unsigned long ti_work, unsigned long mask) { return false; }
|
||||
#define rseq_slice_clear_user(rseq, efault) do { } while (0)
|
||||
#endif /* !CONFIG_RSEQ_SLICE_EXTENSION */
|
||||
|
||||
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)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_GENERIC_IRQ_ENTRY))
|
||||
current->rseq.event.user_irq = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether there is a valid critical section and whether the
|
||||
* instruction pointer in @regs is inside the critical section.
|
||||
*
|
||||
* - If the critical section is invalid, terminate the task.
|
||||
*
|
||||
* - If valid and the instruction pointer is inside, set it to the abort IP.
|
||||
*
|
||||
* - If valid and the instruction pointer is outside, clear the critical
|
||||
* section address.
|
||||
*
|
||||
* Returns true, if the section was valid and either fixup or clear was
|
||||
* done, false otherwise.
|
||||
*
|
||||
* In the failure case task::rseq_event::fatal is set when a invalid
|
||||
* section was found. It's clear when the failure was an unresolved page
|
||||
* fault.
|
||||
*
|
||||
* If inlined into the exit to user path with interrupts disabled, the
|
||||
* caller has to protect against page faults with pagefault_disable().
|
||||
*
|
||||
* In preemptible task context this would be counterproductive as the page
|
||||
* faults could not be fully resolved. As a consequence unresolved page
|
||||
* faults in task context are fatal too.
|
||||
*/
|
||||
|
||||
#ifdef RSEQ_BUILD_SLOW_PATH
|
||||
/*
|
||||
* The debug version is put out of line, but kept here so the code stays
|
||||
* together.
|
||||
*
|
||||
* @csaddr has already been checked by the caller to be in user space
|
||||
*/
|
||||
bool rseq_debug_update_user_cs(struct task_struct *t, struct pt_regs *regs,
|
||||
unsigned long csaddr)
|
||||
{
|
||||
struct rseq_cs __user *ucs = (struct rseq_cs __user *)(unsigned long)csaddr;
|
||||
u64 start_ip, abort_ip, offset, cs_end, head, tasksize = TASK_SIZE;
|
||||
unsigned long ip = instruction_pointer(regs);
|
||||
u64 __user *uc_head = (u64 __user *) ucs;
|
||||
u32 usig, __user *uc_sig;
|
||||
|
||||
scoped_user_rw_access(ucs, efault) {
|
||||
/*
|
||||
* Evaluate the user pile and exit if one of the conditions
|
||||
* is not fulfilled.
|
||||
*/
|
||||
unsafe_get_user(start_ip, &ucs->start_ip, efault);
|
||||
if (unlikely(start_ip >= tasksize))
|
||||
goto die;
|
||||
/* If outside, just clear the critical section. */
|
||||
if (ip < start_ip)
|
||||
goto clear;
|
||||
|
||||
unsafe_get_user(offset, &ucs->post_commit_offset, efault);
|
||||
cs_end = start_ip + offset;
|
||||
/* Check for overflow and wraparound */
|
||||
if (unlikely(cs_end >= tasksize || cs_end < start_ip))
|
||||
goto die;
|
||||
|
||||
/* If not inside, clear it. */
|
||||
if (ip >= cs_end)
|
||||
goto clear;
|
||||
|
||||
unsafe_get_user(abort_ip, &ucs->abort_ip, efault);
|
||||
/* Ensure it's "valid" */
|
||||
if (unlikely(abort_ip >= tasksize || abort_ip < sizeof(*uc_sig)))
|
||||
goto die;
|
||||
/* Validate that the abort IP is not in the critical section */
|
||||
if (unlikely(abort_ip - start_ip < offset))
|
||||
goto die;
|
||||
|
||||
/*
|
||||
* Check version and flags for 0. No point in emitting
|
||||
* deprecated warnings before dying. That could be done in
|
||||
* the slow path eventually, but *shrug*.
|
||||
*/
|
||||
unsafe_get_user(head, uc_head, efault);
|
||||
if (unlikely(head))
|
||||
goto die;
|
||||
|
||||
/* abort_ip - 4 is >= 0. See abort_ip check above */
|
||||
uc_sig = (u32 __user *)(unsigned long)(abort_ip - sizeof(*uc_sig));
|
||||
unsafe_get_user(usig, uc_sig, efault);
|
||||
if (unlikely(usig != t->rseq.sig))
|
||||
goto die;
|
||||
|
||||
/* rseq_event.user_irq is only valid if CONFIG_GENERIC_IRQ_ENTRY=y */
|
||||
if (IS_ENABLED(CONFIG_GENERIC_IRQ_ENTRY)) {
|
||||
/* If not in interrupt from user context, let it die */
|
||||
if (unlikely(!t->rseq.event.user_irq))
|
||||
goto die;
|
||||
}
|
||||
unsafe_put_user(0ULL, &t->rseq.usrptr->rseq_cs, efault);
|
||||
instruction_pointer_set(regs, (unsigned long)abort_ip);
|
||||
rseq_stat_inc(rseq_stats.fixup);
|
||||
break;
|
||||
clear:
|
||||
unsafe_put_user(0ULL, &t->rseq.usrptr->rseq_cs, efault);
|
||||
rseq_stat_inc(rseq_stats.clear);
|
||||
abort_ip = 0ULL;
|
||||
}
|
||||
|
||||
if (unlikely(abort_ip))
|
||||
rseq_trace_ip_fixup(ip, start_ip, offset, abort_ip);
|
||||
return true;
|
||||
die:
|
||||
t->rseq.event.fatal = true;
|
||||
efault:
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif /* RSEQ_BUILD_SLOW_PATH */
|
||||
|
||||
/*
|
||||
* This only ensures that abort_ip is in the user address space and
|
||||
* validates that it is preceded by the signature.
|
||||
*
|
||||
* No other sanity checks are done here, that's what the debug code is for.
|
||||
*/
|
||||
static rseq_inline bool
|
||||
rseq_update_user_cs(struct task_struct *t, struct pt_regs *regs, unsigned long csaddr)
|
||||
{
|
||||
struct rseq_cs __user *ucs = (struct rseq_cs __user *)(unsigned long)csaddr;
|
||||
unsigned long ip = instruction_pointer(regs);
|
||||
unsigned long tasksize = TASK_SIZE;
|
||||
u64 start_ip, abort_ip, offset;
|
||||
u32 usig, __user *uc_sig;
|
||||
|
||||
rseq_stat_inc(rseq_stats.cs);
|
||||
|
||||
if (unlikely(csaddr >= tasksize)) {
|
||||
t->rseq.event.fatal = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static_branch_unlikely(&rseq_debug_enabled))
|
||||
return rseq_debug_update_user_cs(t, regs, csaddr);
|
||||
|
||||
scoped_user_rw_access(ucs, efault) {
|
||||
unsafe_get_user(start_ip, &ucs->start_ip, efault);
|
||||
unsafe_get_user(offset, &ucs->post_commit_offset, efault);
|
||||
unsafe_get_user(abort_ip, &ucs->abort_ip, efault);
|
||||
|
||||
/*
|
||||
* No sanity checks. If user space screwed it up, it can
|
||||
* keep the pieces. That's what debug code is for.
|
||||
*
|
||||
* If outside, just clear the critical section.
|
||||
*/
|
||||
if (ip - start_ip >= offset)
|
||||
goto clear;
|
||||
|
||||
/*
|
||||
* Two requirements for @abort_ip:
|
||||
* - Must be in user space as x86 IRET would happily return to
|
||||
* the kernel.
|
||||
* - The four bytes preceding the instruction at @abort_ip must
|
||||
* contain the signature.
|
||||
*
|
||||
* The latter protects against the following attack vector:
|
||||
*
|
||||
* An attacker with limited abilities to write, creates a critical
|
||||
* section descriptor, sets the abort IP to a library function or
|
||||
* some other ROP gadget and stores the address of the descriptor
|
||||
* in TLS::rseq::rseq_cs. An RSEQ abort would then evade ROP
|
||||
* protection.
|
||||
*/
|
||||
if (unlikely(abort_ip >= tasksize || abort_ip < sizeof(*uc_sig)))
|
||||
goto die;
|
||||
|
||||
/* The address is guaranteed to be >= 0 and < TASK_SIZE */
|
||||
uc_sig = (u32 __user *)(unsigned long)(abort_ip - sizeof(*uc_sig));
|
||||
unsafe_get_user(usig, uc_sig, efault);
|
||||
if (unlikely(usig != t->rseq.sig))
|
||||
goto die;
|
||||
|
||||
/* Invalidate the critical section */
|
||||
unsafe_put_user(0ULL, &t->rseq.usrptr->rseq_cs, efault);
|
||||
/* Update the instruction pointer */
|
||||
instruction_pointer_set(regs, (unsigned long)abort_ip);
|
||||
rseq_stat_inc(rseq_stats.fixup);
|
||||
break;
|
||||
clear:
|
||||
unsafe_put_user(0ULL, &t->rseq.usrptr->rseq_cs, efault);
|
||||
rseq_stat_inc(rseq_stats.clear);
|
||||
abort_ip = 0ULL;
|
||||
}
|
||||
|
||||
if (unlikely(abort_ip))
|
||||
rseq_trace_ip_fixup(ip, start_ip, offset, abort_ip);
|
||||
return true;
|
||||
die:
|
||||
t->rseq.event.fatal = true;
|
||||
efault:
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates CPU ID, Node ID and MM CID and reads the critical section
|
||||
* address, when @csaddr != NULL. This allows to put the ID update and the
|
||||
* read under the same uaccess region to spare a separate begin/end.
|
||||
*
|
||||
* As this is either invoked from a C wrapper with @csaddr = NULL or from
|
||||
* the fast path code with a valid pointer, a clever compiler should be
|
||||
* able to optimize the read out. Spares a duplicate implementation.
|
||||
*
|
||||
* Returns true, if the operation was successful, false otherwise.
|
||||
*
|
||||
* In the failure case task::rseq_event::fatal is set when invalid data
|
||||
* was found on debug kernels. It's clear when the failure was an unresolved page
|
||||
* fault.
|
||||
*
|
||||
* If inlined into the exit to user path with interrupts disabled, the
|
||||
* caller has to protect against page faults with pagefault_disable().
|
||||
*
|
||||
* In preemptible task context this would be counterproductive as the page
|
||||
* faults could not be fully resolved. As a consequence unresolved page
|
||||
* faults in task context are fatal too.
|
||||
*/
|
||||
static rseq_inline
|
||||
bool rseq_set_ids_get_csaddr(struct task_struct *t, struct rseq_ids *ids, u64 *csaddr)
|
||||
{
|
||||
struct rseq __user *rseq = t->rseq.usrptr;
|
||||
|
||||
scoped_user_rw_access(rseq, efault) {
|
||||
/* Validate the R/O fields for debug and optimized mode */
|
||||
if (static_branch_unlikely(&rseq_debug_enabled) || rseq_v2(t)) {
|
||||
u32 cpu_id, uval;
|
||||
|
||||
unsafe_get_user(cpu_id, &rseq->cpu_id_start, efault);
|
||||
if (cpu_id != t->rseq.ids.cpu_id)
|
||||
goto die;
|
||||
unsafe_get_user(uval, &rseq->cpu_id, efault);
|
||||
if (uval != cpu_id)
|
||||
goto die;
|
||||
unsafe_get_user(uval, &rseq->node_id, efault);
|
||||
if (uval != t->rseq.ids.node_id)
|
||||
goto die;
|
||||
unsafe_get_user(uval, &rseq->mm_cid, efault);
|
||||
if (uval != t->rseq.ids.mm_cid)
|
||||
goto die;
|
||||
}
|
||||
|
||||
unsafe_put_user(ids->cpu_id, &rseq->cpu_id_start, efault);
|
||||
unsafe_put_user(ids->cpu_id, &rseq->cpu_id, efault);
|
||||
unsafe_put_user(ids->node_id, &rseq->node_id, efault);
|
||||
unsafe_put_user(ids->mm_cid, &rseq->mm_cid, efault);
|
||||
if (csaddr)
|
||||
unsafe_get_user(*csaddr, &rseq->rseq_cs, efault);
|
||||
|
||||
/* RSEQ ABI V2 only operations */
|
||||
if (rseq_v2(t))
|
||||
rseq_slice_clear_user(rseq, efault);
|
||||
}
|
||||
|
||||
rseq_slice_clear_grant(t);
|
||||
/* Cache the new values */
|
||||
t->rseq.ids = *ids;
|
||||
rseq_stat_inc(rseq_stats.ids);
|
||||
rseq_trace_update(t, ids);
|
||||
return true;
|
||||
|
||||
die:
|
||||
t->rseq.event.fatal = true;
|
||||
efault:
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update user space with new IDs and conditionally check whether the task
|
||||
* is in a critical section.
|
||||
*/
|
||||
static rseq_inline bool rseq_update_usr(struct task_struct *t, struct pt_regs *regs,
|
||||
struct rseq_ids *ids)
|
||||
{
|
||||
u64 csaddr;
|
||||
|
||||
if (!rseq_set_ids_get_csaddr(t, ids, &csaddr))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* On architectures which utilize the generic entry code this
|
||||
* allows to skip the critical section when the entry was not from
|
||||
* a user space interrupt, unless debug mode is enabled.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_GENERIC_IRQ_ENTRY)) {
|
||||
if (!static_branch_unlikely(&rseq_debug_enabled)) {
|
||||
if (likely(!t->rseq.event.user_irq))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (likely(!csaddr))
|
||||
return true;
|
||||
/* Sigh, this really needs to do work */
|
||||
return rseq_update_user_cs(t, regs, csaddr);
|
||||
}
|
||||
|
||||
/*
|
||||
* If you want to use this then convert your architecture to the generic
|
||||
* entry code. I'm tired of building workarounds for people who can't be
|
||||
* bothered to make the maintenance of generic infrastructure less
|
||||
* burdensome. Just sucking everything into the architecture code and
|
||||
* thereby making others chase the horrible hacks and keep them working is
|
||||
* neither acceptable nor sustainable.
|
||||
*/
|
||||
#ifdef CONFIG_GENERIC_ENTRY
|
||||
|
||||
/*
|
||||
* This is inlined into the exit path because:
|
||||
*
|
||||
* 1) It's a one time comparison in the fast path when there is no event to
|
||||
* handle
|
||||
*
|
||||
* 2) The access to the user space rseq memory (TLS) is unlikely to fault
|
||||
* so the straight inline operation is:
|
||||
*
|
||||
* - Four 32-bit stores only if CPU ID/ MM CID need to be updated
|
||||
* - One 64-bit load to retrieve the critical section address
|
||||
*
|
||||
* 3) In the unlikely case that the critical section address is != NULL:
|
||||
*
|
||||
* - One 64-bit load to retrieve the start IP
|
||||
* - One 64-bit load to retrieve the offset for calculating the end
|
||||
* - One 64-bit load to retrieve the abort IP
|
||||
* - One 64-bit load to retrieve the signature
|
||||
* - One store to clear the critical section address
|
||||
*
|
||||
* The non-debug case implements only the minimal required checking. It
|
||||
* provides protection against a rogue abort IP in kernel space, which
|
||||
* would be exploitable at least on x86, and also against a rogue CS
|
||||
* descriptor by checking the signature at the abort IP. Any fallout from
|
||||
* invalid critical section descriptors is a user space problem. The debug
|
||||
* case provides the full set of checks and terminates the task if a
|
||||
* condition is not met.
|
||||
*
|
||||
* In case of a fault or an invalid value, this sets TIF_NOTIFY_RESUME and
|
||||
* tells the caller to loop back into exit_to_user_mode_loop(). The rseq
|
||||
* slow path there will handle the failure.
|
||||
*/
|
||||
static __always_inline bool rseq_exit_user_update(struct pt_regs *regs, struct task_struct *t)
|
||||
{
|
||||
/*
|
||||
* Page faults need to be disabled as this is called with
|
||||
* interrupts disabled
|
||||
*/
|
||||
guard(pagefault)();
|
||||
/*
|
||||
* This optimization is only valid when the task registered for the
|
||||
* optimized RSEQ_ABI_V2 variant. Some legacy users rely on the original
|
||||
* RSEQ implementation behaviour which unconditionally updated the IDs.
|
||||
* rseq_sched_switch_event() ensures that legacy registrations always
|
||||
* have both sched_switch and ids_changed set, which is compatible with
|
||||
* the historical TIF_NOTIFY_RESUME behaviour.
|
||||
*/
|
||||
if (likely(!t->rseq.event.ids_changed)) {
|
||||
struct rseq __user *rseq = t->rseq.usrptr;
|
||||
/*
|
||||
* If IDs have not changed rseq_event::user_irq must be true
|
||||
* See rseq_sched_switch_event().
|
||||
*/
|
||||
u64 csaddr;
|
||||
|
||||
scoped_user_rw_access(rseq, efault) {
|
||||
unsafe_get_user(csaddr, &rseq->rseq_cs, efault);
|
||||
|
||||
/* RSEQ ABI V2 only operations */
|
||||
if (rseq_v2(t))
|
||||
rseq_slice_clear_user(rseq, efault);
|
||||
}
|
||||
|
||||
rseq_slice_clear_grant(t);
|
||||
|
||||
if (static_branch_unlikely(&rseq_debug_enabled) || unlikely(csaddr)) {
|
||||
if (unlikely(!rseq_update_user_cs(t, regs, csaddr)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct rseq_ids ids = {
|
||||
.cpu_id = task_cpu(t),
|
||||
.mm_cid = task_mm_cid(t),
|
||||
.node_id = cpu_to_node(ids.cpu_id),
|
||||
};
|
||||
|
||||
return rseq_update_usr(t, regs, &ids);
|
||||
efault:
|
||||
return false;
|
||||
}
|
||||
|
||||
static __always_inline bool __rseq_exit_to_user_mode_restart(struct pt_regs *regs)
|
||||
{
|
||||
struct task_struct *t = current;
|
||||
|
||||
/*
|
||||
* If the task did not go through schedule or got the flag enforced
|
||||
* by the rseq syscall or execve, then nothing to do here.
|
||||
*
|
||||
* CPU ID and MM CID can only change when going through a context
|
||||
* switch.
|
||||
*
|
||||
* rseq_sched_switch_event() sets the rseq_event::sched_switch bit
|
||||
* only when rseq_event::has_rseq is true. That conditional is
|
||||
* required to avoid setting the TIF bit if RSEQ is not registered
|
||||
* for a task. rseq_event::sched_switch is cleared when RSEQ is
|
||||
* unregistered by a task so it's sufficient to check for the
|
||||
* sched_switch bit alone.
|
||||
*
|
||||
* A sane compiler requires three instructions for the nothing to do
|
||||
* case including clearing the events, but your mileage might vary.
|
||||
*/
|
||||
if (unlikely((t->rseq.event.sched_switch))) {
|
||||
rseq_stat_inc(rseq_stats.fastpath);
|
||||
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Required to allow conversion to GENERIC_ENTRY w/o GENERIC_TIF_BITS */
|
||||
#ifdef CONFIG_HAVE_GENERIC_TIF_BITS
|
||||
static __always_inline bool test_tif_rseq(unsigned long ti_work)
|
||||
{
|
||||
return ti_work & _TIF_RSEQ;
|
||||
}
|
||||
|
||||
static __always_inline void clear_tif_rseq(void)
|
||||
{
|
||||
static_assert(TIF_RSEQ != TIF_NOTIFY_RESUME);
|
||||
clear_thread_flag(TIF_RSEQ);
|
||||
}
|
||||
#else
|
||||
static __always_inline bool test_tif_rseq(unsigned long ti_work) { return true; }
|
||||
static __always_inline void clear_tif_rseq(void) { }
|
||||
#endif
|
||||
|
||||
static __always_inline bool
|
||||
rseq_exit_to_user_mode_restart(struct pt_regs *regs, unsigned long ti_work)
|
||||
{
|
||||
if (unlikely(test_tif_rseq(ti_work))) {
|
||||
if (unlikely(__rseq_exit_to_user_mode_restart(regs))) {
|
||||
current->rseq.event.slowpath = true;
|
||||
set_tsk_thread_flag(current, TIF_NOTIFY_RESUME);
|
||||
return true;
|
||||
}
|
||||
clear_tif_rseq();
|
||||
}
|
||||
/*
|
||||
* Arm the slice extension timer if nothing to do anymore and the
|
||||
* task really goes out to user space.
|
||||
*/
|
||||
return rseq_arm_slice_extension_timer();
|
||||
}
|
||||
|
||||
#else /* CONFIG_GENERIC_ENTRY */
|
||||
static inline bool rseq_exit_to_user_mode_restart(struct pt_regs *regs, unsigned long ti_work)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* !CONFIG_GENERIC_ENTRY */
|
||||
|
||||
static __always_inline void rseq_syscall_exit_to_user_mode(void)
|
||||
{
|
||||
struct rseq_event *ev = ¤t->rseq.event;
|
||||
|
||||
rseq_stat_inc(rseq_stats.exit);
|
||||
|
||||
/* Needed to remove the store for the !lockdep case */
|
||||
if (IS_ENABLED(CONFIG_LOCKDEP)) {
|
||||
WARN_ON_ONCE(ev->sched_switch);
|
||||
ev->events = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static __always_inline void rseq_irqentry_exit_to_user_mode(void)
|
||||
{
|
||||
struct rseq_event *ev = ¤t->rseq.event;
|
||||
|
||||
rseq_stat_inc(rseq_stats.exit);
|
||||
|
||||
lockdep_assert_once(!ev->sched_switch);
|
||||
|
||||
/*
|
||||
* Ensure that event (especially user_irq) is cleared when the
|
||||
* interrupt did not result in a schedule and therefore the
|
||||
* rseq processing could not clear it.
|
||||
*/
|
||||
ev->events = 0;
|
||||
}
|
||||
|
||||
void __rseq_debug_syscall_return(struct pt_regs *regs);
|
||||
|
||||
static __always_inline void rseq_debug_syscall_return(struct pt_regs *regs)
|
||||
{
|
||||
if (static_branch_unlikely(&rseq_debug_enabled))
|
||||
__rseq_debug_syscall_return(regs);
|
||||
}
|
||||
#else /* CONFIG_RSEQ */
|
||||
static inline void rseq_note_user_irq_entry(void) { }
|
||||
static inline bool rseq_exit_to_user_mode_restart(struct pt_regs *regs, unsigned long ti_work)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline void rseq_syscall_exit_to_user_mode(void) { }
|
||||
static inline void rseq_irqentry_exit_to_user_mode(void) { }
|
||||
static inline void rseq_debug_syscall_return(struct pt_regs *regs) { }
|
||||
static inline bool rseq_grant_slice_extension(unsigned long ti_work, unsigned long mask) { return false; }
|
||||
#endif /* !CONFIG_RSEQ */
|
||||
|
||||
#endif /* _LINUX_RSEQ_ENTRY_H */
|
||||
Reference in New Issue
Block a user