uprobes: deuglify xol_get_insn_slot/xol_free_insn_slot paths
Subsystems (3)
Oleg Nesterov
maintainercover uprobes: deuglify xol_get_insn_slot/xol_free_insn_slot paths
Hello,
No functional changes, please review.
I'd like to push these cleanups first, then I'll send some optimizations on top of this series.
Oleg.
---
kernel/events/uprobes.c | 111 ++++++++++++++++-------------------------------- 1 file changed, 37 insertions(+), 74 deletions(-)
Oleg Nesterov
maintainerPATCH 1/7 uprobes: don't abuse get_utask() in pre_ssout() and prepare_uretprobe()
handle_swbp() calls get_utask() before prepare_uretprobe() or pre_ssout() can be called, they can simply use current->utask which can't be NULL.
--- kernel/events/uprobes.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-)
kernel/events/uprobes.c +3 -11
@@ -1908,18 +1908,14 @@ static void cleanup_return_instances(struct uprobe_task *utask, bool chained,static void prepare_uretprobe (struct uprobe *uprobe,struct pt_regs *regs) { -struct return_instance *ri; -struct uprobe_task *utask; +struct uprobe_task *utask = current->utask;unsigned long orig_ret_vaddr, trampoline_vaddr; +struct return_instance *ri;bool chained;if (!get_xol_area ())return ; - utask =get_utask (); -if (!utask) -return ; -if (utask->depth >=MAX_URETPROBE_DEPTH ) { printk_ratelimited(KERN_INFO "uprobe: omit uretprobe due to" " nestedness limit pid/tgid=%d/%d\n",
@@ -1980,14 +1976,10 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs)static int pre_ssout (struct uprobe *uprobe,struct pt_regs *regs,unsigned long bp_vaddr) { -struct uprobe_task *utask; +struct uprobe_task *utask = current->utask;unsigned long xol_vaddr;int err; - utask =get_utask (); -if (!utask) -return -ENOMEM ; -if (!try_get_uprobe (uprobe))return -EINVAL ;
Signature · 1 line
2.25.1.362.g51ebf55
Oleg Nesterov
maintainerPATCH 2/7 uprobes: sanitiize xol_free_insn_slot()
In reply to Oleg Nesterov1. Clear utask->xol_vaddr unconditionally, even if this addr is not valid, xol_free_insn_slot() should never return with utask->xol_vaddr != NULL.
2. Add a comment to explain why do we need to validate slot_addr.
3. Simplify the validation above. We can simply check offset < PAGE_SIZE, unsigned underflows are fine, it should work if slot_addr < area->vaddr.
4. Kill the unnecessary "slot_nr >= UINSNS_PER_PAGE" check, slot_nr must be valid if offset < PAGE_SIZE.
The next patches will cleanup this function even more.
--- kernel/events/uprobes.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-)
kernel/events/uprobes.c +9 -12
@@ -1683,8 +1683,8 @@ static unsigned long xol_get_insn_slot(struct uprobe *uprobe)static void xol_free_insn_slot (struct task_struct *tsk) {struct xol_area *area; -unsigned long vma_end;unsigned long slot_addr; +unsigned long offset;if (!tsk->mm || !tsk->mm->uprobes_state.xol_area || !tsk->utask)return ;
@@ -1693,24 +1693,21 @@ static void xol_free_insn_slot(struct task_struct *tsk)if (unlikely (!slot_addr))return ; + tsk->utask->xol_vaddr =0 ; area = tsk->mm->uprobes_state.xol_area; - vma_end = area->vaddr +PAGE_SIZE ; -if (area->vaddr <= slot_addr && slot_addr < vma_end) { -unsigned long offset; -int slot_nr; - - offset = slot_addr - area->vaddr; - slot_nr = offset /UPROBE_XOL_SLOT_BYTES ; -if (slot_nr >=UINSNS_PER_PAGE ) -return ; + offset = slot_addr - area->vaddr; +/* +* slot_addr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). +* This check can only fail if the "[uprobes]" vma was mremap'ed. +*/ +if (offset <PAGE_SIZE ) { +int slot_nr = offset /UPROBE_XOL_SLOT_BYTES ;clear_bit (slot_nr, area->bitmap);atomic_dec (&area->slot_count);smp_mb__after_atomic ();/* pairs with prepare_to_wait() */ if (waitqueue_active (&area->wq))wake_up (&area->wq); - - tsk->utask->xol_vaddr =0 ; } }
Signature · 1 line
2.25.1.362.g51ebf55
Oleg Nesterov
maintainerPATCH 3/7 uprobes: kill the unnecessary put_uprobe/xol_free_insn_slot in uprobe_free_utask()
In reply to Oleg NesterovIf pre_ssout() succeeds and sets utask->active_uprobe and utask->xol_vaddr the task must not exit until it calls handle_singlestep() which does the necessary put_uprobe() and xol_free_insn_slot().
Remove put_uprobe() and xol_free_insn_slot() from uprobe_free_utask(). With this change xol_free_insn_slot() can't hit xol_area/utask/xol_vaddr == NULL, we can kill the unnecessary checks checks and simplify this function more.
--- kernel/events/uprobes.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-)
kernel/events/uprobes.c +5 -19
@@ -1676,28 +1676,16 @@ static unsigned long xol_get_insn_slot(struct uprobe *uprobe) }/* -* xol_free_insn_slot - If slot was earlier allocated by -* @xol_get_insn_slot(), make the slot available for -* subsequent requests. +* xol_free_insn_slot - free the slot allocated by xol_get_insn_slot() */ static void xol_free_insn_slot (struct task_struct *tsk) { -struct xol_area *area; -unsigned long slot_addr; -unsigned long offset; - -if (!tsk->mm || !tsk->mm->uprobes_state.xol_area || !tsk->utask) -return ; - - slot_addr = tsk->utask->xol_vaddr; -if (unlikely (!slot_addr)) -return ; +struct xol_area *area = tsk->mm->uprobes_state.xol_area; +unsigned long offset = tsk->utask->xol_vaddr - area->vaddr; tsk->utask->xol_vaddr =0 ; - area = tsk->mm->uprobes_state.xol_area; - offset = slot_addr - area->vaddr;/* -* slot_addr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). +* xol_vaddr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). * This check can only fail if the "[uprobes]" vma was mremap'ed. */ if (offset <PAGE_SIZE ) {
@@ -1767,14 +1755,12 @@ void uprobe_free_utask(struct task_struct *t)if (!utask)return ; -if (utask->active_uprobe) -put_uprobe (utask->active_uprobe); +WARN_ON_ONCE (utask->active_uprobe || utask->xol_vaddr); ri = utask->return_instances;while (ri) ri =free_ret_instance (ri); -xol_free_insn_slot (t);kfree (utask); t->utask =NULL ; }
Signature · 1 line
2.25.1.362.g51ebf55
Oleg Nesterov
maintainerPATCH 4/7 uprobes: simplify xol_take_insn_slot() and its caller
In reply to Oleg NesterovThe do / while (slot_nr >= UINSNS_PER_PAGE) loop in xol_take_insn_slot() makes no sense, the checked condition is always true. Change this code to use the "for (;;)" loop, this way we do not need to change slot_nr if test_and_set_bit() fails.
Also, kill the unnecessary xol_vaddr != NULL check in xol_get_insn_slot(), xol_take_insn_slot() never returns NULL.
--- kernel/events/uprobes.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-)
kernel/events/uprobes.c +4 -13
@@ -1631,25 +1631,20 @@ void uprobe_dup_mmap(struct mm_struct *oldmm, struct mm_struct *newmm)*/ static unsigned long xol_take_insn_slot (struct xol_area *area) { -unsigned long slot_addr; -int slot_nr; +unsigned int slot_nr; -do { +for (;;) { slot_nr =find_first_zero_bit (area->bitmap,UINSNS_PER_PAGE );if (slot_nr <UINSNS_PER_PAGE ) {if (!test_and_set_bit (slot_nr, area->bitmap))break ; - - slot_nr =UINSNS_PER_PAGE ;continue ; }wait_event (area->wq, (atomic_read (&area->slot_count) <UINSNS_PER_PAGE )); - }while (slot_nr >=UINSNS_PER_PAGE ); + } - slot_addr = area->vaddr + (slot_nr *UPROBE_XOL_SLOT_BYTES );atomic_inc (&area->slot_count); - -return slot_addr; +return area->vaddr + slot_nr *UPROBE_XOL_SLOT_BYTES ; }/*
@@ -1666,12 +1661,8 @@ static unsigned long xol_get_insn_slot(struct uprobe *uprobe)return 0 ; xol_vaddr =xol_take_insn_slot (area); -if (unlikely (!xol_vaddr)) -return 0 ; -arch_uprobe_copy_ixol (area->page, xol_vaddr, &uprobe->arch.ixol,sizeof (uprobe->arch.ixol)); -return xol_vaddr; }
Signature · 1 line
2.25.1.362.g51ebf55
Oleg Nesterov
maintainerPATCH 5/7 uprobes: move the initialization of utask->xol_vaddr from pre_ssout() to xol_get_insn_slot()
In reply to Oleg NesterovThis simplifies the code and makes xol_get_insn_slot() symmetric with xol_free_insn_slot() which clears utask->xol_vaddr.
--- kernel/events/uprobes.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-)
kernel/events/uprobes.c +8 -14
@@ -1649,21 +1649,19 @@ static unsigned long xol_take_insn_slot(struct xol_area *area)/* * xol_get_insn_slot - allocate a slot for xol. -* Returns the allocated slot address or 0. */ -static unsigned long xol_get_insn_slot (struct uprobe *uprobe) +static bool xol_get_insn_slot (struct uprobe *uprobe) { -struct xol_area *area; -unsigned long xol_vaddr; +struct uprobe_task *utask = current->utask; +struct xol_area *area =get_xol_area (); - area =get_xol_area ();if (!area) -return 0 ; +return false; - xol_vaddr =xol_take_insn_slot (area); -arch_uprobe_copy_ixol (area->page, xol_vaddr, + utask->xol_vaddr =xol_take_insn_slot (area); +arch_uprobe_copy_ixol (area->page, utask->xol_vaddr, &uprobe->arch.ixol,sizeof (uprobe->arch.ixol)); -return xol_vaddr; +return true; }/*
@@ -1951,21 +1949,17 @@ static int pre_ssout(struct uprobe *uprobe, struct pt_regs *regs, unsigned long bp_vaddr) { struct uprobe_task *utask = current->utask; -unsigned long xol_vaddr;int err;if (!try_get_uprobe (uprobe))return -EINVAL ; - xol_vaddr =xol_get_insn_slot (uprobe); -if (!xol_vaddr) { +if (!xol_get_insn_slot (uprobe)) { err = -ENOMEM ; goto err_out; } - utask->xol_vaddr = xol_vaddr; utask->vaddr = bp_vaddr; - err =arch_uprobe_pre_xol (&uprobe->arch, regs);if (unlikely (err)) {xol_free_insn_slot (current);
Signature · 1 line
2.25.1.362.g51ebf55
Oleg Nesterov
maintainerPATCH 6/7 uprobes: pass utask to xol_get_insn_slot() and xol_free_insn_slot()
In reply to Oleg NesterovAdd the "struct uprobe_task *utask" argument to xol_get_insn_slot() and xol_free_insn_slot(), their callers already have it so we can avoid the unnecessary dereference and simplify the code.
Kill the "tsk" argument of xol_free_insn_slot(), it is always current.
--- kernel/events/uprobes.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-)
kernel/events/uprobes.c +8 -9
@@ -1650,9 +1650,8 @@ static unsigned long xol_take_insn_slot(struct xol_area *area)/* * xol_get_insn_slot - allocate a slot for xol. */ -static bool xol_get_insn_slot (struct uprobe *uprobe) +static bool xol_get_insn_slot (struct uprobe *uprobe,struct uprobe_task *utask) { -struct uprobe_task *utask = current->utask;struct xol_area *area =get_xol_area ();if (!area)
@@ -1667,12 +1666,12 @@ static bool xol_get_insn_slot(struct uprobe *uprobe)/* * xol_free_insn_slot - free the slot allocated by xol_get_insn_slot() */ -static void xol_free_insn_slot (struct task_struct *tsk) +static void xol_free_insn_slot (struct uprobe_task *utask) { -struct xol_area *area = tsk->mm->uprobes_state.xol_area; -unsigned long offset = tsk->utask->xol_vaddr - area->vaddr; +struct xol_area *area = current->mm->uprobes_state.xol_area; +unsigned long offset = utask->xol_vaddr - area->vaddr; - tsk->utask->xol_vaddr =0 ; + utask->xol_vaddr =0 ;/* * xol_vaddr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). * This check can only fail if the "[uprobes]" vma was mremap'ed.
@@ -1954,7 +1953,7 @@ pre_ssout(struct uprobe *uprobe, struct pt_regs *regs, unsigned long bp_vaddr)if (!try_get_uprobe (uprobe))return -EINVAL ; -if (!xol_get_insn_slot (uprobe)) { +if (!xol_get_insn_slot (uprobe, utask)) { err = -ENOMEM ; goto err_out; }
@@ -1962,7 +1961,7 @@ pre_ssout(struct uprobe *uprobe, struct pt_regs *regs, unsigned long bp_vaddr) utask->vaddr = bp_vaddr; err =arch_uprobe_pre_xol (&uprobe->arch, regs);if (unlikely (err)) { -xol_free_insn_slot (current); +xol_free_insn_slot (utask); goto err_out; }
@@ -2313,7 +2312,7 @@ static void handle_singlestep(struct uprobe_task *utask, struct pt_regs *regs)put_uprobe (uprobe); utask->active_uprobe =NULL ; utask->state =UTASK_RUNNING ; -xol_free_insn_slot (current); +xol_free_insn_slot (utask);spin_lock_irq (¤t->sighand->siglock);recalc_sigpending ();/* see uprobe_deny_signal() */
Signature · 1 line
2.25.1.362.g51ebf55
Oleg Nesterov
maintainerPATCH 7/7 uprobes: deny mremap(xol_vma)
In reply to Oleg Nesterovkernel/events/uprobes.c assumes that xol_area->vaddr is always correct but a malicious application can remap its "[uprobes]" vma to another adress to confuse the kernel. Introduce xol_mremap() to make this impossible.
With this change utask->xol_vaddr in xol_free_insn_slot() can't be invalid, we can turn the offset check into WARN_ON_ONCE(offset >= PAGE_SIZE).
--- kernel/events/uprobes.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-)
kernel/events/uprobes.c +17 -13
@@ -1475,9 +1475,15 @@ static vm_fault_t xol_fault(const struct vm_special_mapping *sm,return 0 ; } +static int xol_mremap (const struct vm_special_mapping *sm,struct vm_area_struct *new_vma) +{ +return -EPERM ; +} +static const struct vm_special_mapping xol_mapping = { .name ="[uprobes]" , .fault = xol_fault, + .mremap = xol_mremap, };/* Slot allocation for XOL */
@@ -1670,21 +1676,19 @@ static void xol_free_insn_slot(struct uprobe_task *utask) {struct xol_area *area = current->mm->uprobes_state.xol_area;unsigned long offset = utask->xol_vaddr - area->vaddr; +unsigned int slot_nr; utask->xol_vaddr =0 ; -/* -* xol_vaddr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). -* This check can only fail if the "[uprobes]" vma was mremap'ed. -*/ -if (offset <PAGE_SIZE ) { -int slot_nr = offset /UPROBE_XOL_SLOT_BYTES ; - -clear_bit (slot_nr, area->bitmap); -atomic_dec (&area->slot_count); -smp_mb__after_atomic ();/* pairs with prepare_to_wait() */ -if (waitqueue_active (&area->wq)) -wake_up (&area->wq); - } +/* xol_vaddr must fit into [area->vaddr, area->vaddr + PAGE_SIZE) */ +if (WARN_ON_ONCE (offset >=PAGE_SIZE )) +return ; + + slot_nr = offset /UPROBE_XOL_SLOT_BYTES ; +clear_bit (slot_nr, area->bitmap); +atomic_dec (&area->slot_count); +smp_mb__after_atomic ();/* pairs with prepare_to_wait() */ +if (waitqueue_active (&area->wq)) +wake_up (&area->wq); } void __weak arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
Signature · 1 line
2.25.1.362.g51ebf55
Peter Zijlstra
maintainerQuoted 6 lines · Oleg Nesterov
Hello,
No functional changes, please review.
I'd like to push these cleanups first, then I'll send some optimizations on top of this series.
They look okay to me, I'll queue them up. Thanks!
[tip: perf/core] uprobes: pass utask to xol_get_insn_slot() and xol_free_insn_slot()
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: c5356ab1db28cafc448a50c26ba84442237abb98 Gitweb: https://git.kernel.org/tip/c5356ab1db28cafc448a50c26ba84442237abb98 Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 29 Sep 2024 16:42:53 +02:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Mon, 07 Oct 2024 09:28:45 +02:00
uprobes: pass utask to xol_get_insn_slot() and xol_free_insn_slot()
Add the "struct uprobe_task *utask" argument to xol_get_insn_slot() and xol_free_insn_slot(), their callers already have it so we can avoid the unnecessary dereference and simplify the code.
Kill the "tsk" argument of xol_free_insn_slot(), it is always current.
--- kernel/events/uprobes.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-)
kernel/events/uprobes.c +8 -9
@@ -1647,9 +1647,8 @@ static unsigned long xol_take_insn_slot(struct xol_area *area)/* * xol_get_insn_slot - allocate a slot for xol. */ -static bool xol_get_insn_slot (struct uprobe *uprobe) +static bool xol_get_insn_slot (struct uprobe *uprobe,struct uprobe_task *utask) { -struct uprobe_task *utask = current->utask;struct xol_area *area =get_xol_area ();if (!area)
@@ -1664,12 +1663,12 @@ static bool xol_get_insn_slot(struct uprobe *uprobe)/* * xol_free_insn_slot - free the slot allocated by xol_get_insn_slot() */ -static void xol_free_insn_slot (struct task_struct *tsk) +static void xol_free_insn_slot (struct uprobe_task *utask) { -struct xol_area *area = tsk->mm->uprobes_state.xol_area; -unsigned long offset = tsk->utask->xol_vaddr - area->vaddr; +struct xol_area *area = current->mm->uprobes_state.xol_area; +unsigned long offset = utask->xol_vaddr - area->vaddr; - tsk->utask->xol_vaddr =0 ; + utask->xol_vaddr =0 ;/* * xol_vaddr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). * This check can only fail if the "[uprobes]" vma was mremap'ed.
@@ -1951,7 +1950,7 @@ pre_ssout(struct uprobe *uprobe, struct pt_regs *regs, unsigned long bp_vaddr)if (!try_get_uprobe (uprobe))return -EINVAL ; -if (!xol_get_insn_slot (uprobe)) { +if (!xol_get_insn_slot (uprobe, utask)) { err = -ENOMEM ; goto err_out; }
@@ -1959,7 +1958,7 @@ pre_ssout(struct uprobe *uprobe, struct pt_regs *regs, unsigned long bp_vaddr) utask->vaddr = bp_vaddr; err =arch_uprobe_pre_xol (&uprobe->arch, regs);if (unlikely (err)) { -xol_free_insn_slot (current); +xol_free_insn_slot (utask); goto err_out; }
@@ -2307,7 +2306,7 @@ static void handle_singlestep(struct uprobe_task *utask, struct pt_regs *regs)put_uprobe (uprobe); utask->active_uprobe =NULL ; utask->state =UTASK_RUNNING ; -xol_free_insn_slot (current); +xol_free_insn_slot (utask);spin_lock_irq (¤t->sighand->siglock);recalc_sigpending ();/* see uprobe_deny_signal() */
[tip: perf/core] uprobes: deny mremap(xol_vma)
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: c16e2fdd746c78f5b2ce3c2ab8a26a61b6ed09e5 Gitweb: https://git.kernel.org/tip/c16e2fdd746c78f5b2ce3c2ab8a26a61b6ed09e5 Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 29 Sep 2024 16:42:58 +02:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Mon, 07 Oct 2024 09:28:45 +02:00
uprobes: deny mremap(xol_vma)
kernel/events/uprobes.c assumes that xol_area->vaddr is always correct but a malicious application can remap its "[uprobes]" vma to another adress to confuse the kernel. Introduce xol_mremap() to make this impossible.
With this change utask->xol_vaddr in xol_free_insn_slot() can't be invalid, we can turn the offset check into WARN_ON_ONCE(offset >= PAGE_SIZE).
--- kernel/events/uprobes.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-)
kernel/events/uprobes.c +17 -13
@@ -1472,9 +1472,15 @@ static vm_fault_t xol_fault(const struct vm_special_mapping *sm,return 0 ; } +static int xol_mremap (const struct vm_special_mapping *sm,struct vm_area_struct *new_vma) +{ +return -EPERM ; +} +static const struct vm_special_mapping xol_mapping = { .name ="[uprobes]" , .fault = xol_fault, + .mremap = xol_mremap, };/* Slot allocation for XOL */
@@ -1667,21 +1673,19 @@ static void xol_free_insn_slot(struct uprobe_task *utask) {struct xol_area *area = current->mm->uprobes_state.xol_area;unsigned long offset = utask->xol_vaddr - area->vaddr; +unsigned int slot_nr; utask->xol_vaddr =0 ; -/* -* xol_vaddr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). -* This check can only fail if the "[uprobes]" vma was mremap'ed. -*/ -if (offset <PAGE_SIZE ) { -int slot_nr = offset /UPROBE_XOL_SLOT_BYTES ; - -clear_bit (slot_nr, area->bitmap); -atomic_dec (&area->slot_count); -smp_mb__after_atomic ();/* pairs with prepare_to_wait() */ -if (waitqueue_active (&area->wq)) -wake_up (&area->wq); - } +/* xol_vaddr must fit into [area->vaddr, area->vaddr + PAGE_SIZE) */ +if (WARN_ON_ONCE (offset >=PAGE_SIZE )) +return ; + + slot_nr = offset /UPROBE_XOL_SLOT_BYTES ; +clear_bit (slot_nr, area->bitmap); +atomic_dec (&area->slot_count); +smp_mb__after_atomic ();/* pairs with prepare_to_wait() */ +if (waitqueue_active (&area->wq)) +wake_up (&area->wq); } void __weak arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
[tip: perf/core] uprobes: simplify xol_take_insn_slot() and its caller
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: 6ffe8c7d871b327d16ae6b6f1db4c8ecb0f15c64 Gitweb: https://git.kernel.org/tip/6ffe8c7d871b327d16ae6b6f1db4c8ecb0f15c64 Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 29 Sep 2024 16:42:44 +02:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Mon, 07 Oct 2024 09:28:44 +02:00
uprobes: simplify xol_take_insn_slot() and its caller
The do / while (slot_nr >= UINSNS_PER_PAGE) loop in xol_take_insn_slot() makes no sense, the checked condition is always true. Change this code to use the "for (;;)" loop, this way we do not need to change slot_nr if test_and_set_bit() fails.
Also, kill the unnecessary xol_vaddr != NULL check in xol_get_insn_slot(), xol_take_insn_slot() never returns NULL.
--- kernel/events/uprobes.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-)
kernel/events/uprobes.c +4 -13
@@ -1628,25 +1628,20 @@ void uprobe_dup_mmap(struct mm_struct *oldmm, struct mm_struct *newmm)*/ static unsigned long xol_take_insn_slot (struct xol_area *area) { -unsigned long slot_addr; -int slot_nr; +unsigned int slot_nr; -do { +for (;;) { slot_nr =find_first_zero_bit (area->bitmap,UINSNS_PER_PAGE );if (slot_nr <UINSNS_PER_PAGE ) {if (!test_and_set_bit (slot_nr, area->bitmap))break ; - - slot_nr =UINSNS_PER_PAGE ;continue ; }wait_event (area->wq, (atomic_read (&area->slot_count) <UINSNS_PER_PAGE )); - }while (slot_nr >=UINSNS_PER_PAGE ); + } - slot_addr = area->vaddr + (slot_nr *UPROBE_XOL_SLOT_BYTES );atomic_inc (&area->slot_count); - -return slot_addr; +return area->vaddr + slot_nr *UPROBE_XOL_SLOT_BYTES ; }/*
@@ -1663,12 +1658,8 @@ static unsigned long xol_get_insn_slot(struct uprobe *uprobe)return 0 ; xol_vaddr =xol_take_insn_slot (area); -if (unlikely (!xol_vaddr)) -return 0 ; -arch_uprobe_copy_ixol (area->page, xol_vaddr, &uprobe->arch.ixol,sizeof (uprobe->arch.ixol)); -return xol_vaddr; }
[tip: perf/core] uprobes: move the initialization of utask->xol_vaddr from pre_ssout() to xol_get_insn_slot()
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: 1cee988c1d21eabc936d1401811012522083e36f Gitweb: https://git.kernel.org/tip/1cee988c1d21eabc936d1401811012522083e36f Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 29 Sep 2024 16:42:48 +02:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Mon, 07 Oct 2024 09:28:45 +02:00
uprobes: move the initialization of utask->xol_vaddr from pre_ssout() to xol_get_insn_slot()
This simplifies the code and makes xol_get_insn_slot() symmetric with xol_free_insn_slot() which clears utask->xol_vaddr.
--- kernel/events/uprobes.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-)
kernel/events/uprobes.c +8 -14
@@ -1646,21 +1646,19 @@ static unsigned long xol_take_insn_slot(struct xol_area *area)/* * xol_get_insn_slot - allocate a slot for xol. -* Returns the allocated slot address or 0. */ -static unsigned long xol_get_insn_slot (struct uprobe *uprobe) +static bool xol_get_insn_slot (struct uprobe *uprobe) { -struct xol_area *area; -unsigned long xol_vaddr; +struct uprobe_task *utask = current->utask; +struct xol_area *area =get_xol_area (); - area =get_xol_area ();if (!area) -return 0 ; +return false; - xol_vaddr =xol_take_insn_slot (area); -arch_uprobe_copy_ixol (area->page, xol_vaddr, + utask->xol_vaddr =xol_take_insn_slot (area); +arch_uprobe_copy_ixol (area->page, utask->xol_vaddr, &uprobe->arch.ixol,sizeof (uprobe->arch.ixol)); -return xol_vaddr; +return true; }/*
@@ -1948,21 +1946,17 @@ static int pre_ssout(struct uprobe *uprobe, struct pt_regs *regs, unsigned long bp_vaddr) { struct uprobe_task *utask = current->utask; -unsigned long xol_vaddr;int err;if (!try_get_uprobe (uprobe))return -EINVAL ; - xol_vaddr =xol_get_insn_slot (uprobe); -if (!xol_vaddr) { +if (!xol_get_insn_slot (uprobe)) { err = -ENOMEM ; goto err_out; } - utask->xol_vaddr = xol_vaddr; utask->vaddr = bp_vaddr; - err =arch_uprobe_pre_xol (&uprobe->arch, regs);if (unlikely (err)) {xol_free_insn_slot (current);
[tip: perf/core] uprobes: kill the unnecessary put_uprobe/xol_free_insn_slot in uprobe_free_utask()
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: 430af825ba991730f8acc3c804a4aef82e9f7ff6 Gitweb: https://git.kernel.org/tip/430af825ba991730f8acc3c804a4aef82e9f7ff6 Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 29 Sep 2024 16:42:39 +02:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Mon, 07 Oct 2024 09:28:44 +02:00
uprobes: kill the unnecessary put_uprobe/xol_free_insn_slot in uprobe_free_utask()
If pre_ssout() succeeds and sets utask->active_uprobe and utask->xol_vaddr the task must not exit until it calls handle_singlestep() which does the necessary put_uprobe() and xol_free_insn_slot().
Remove put_uprobe() and xol_free_insn_slot() from uprobe_free_utask(). With this change xol_free_insn_slot() can't hit xol_area/utask/xol_vaddr == NULL, we can kill the unnecessary checks checks and simplify this function more.
--- kernel/events/uprobes.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-)
kernel/events/uprobes.c +5 -19
@@ -1673,28 +1673,16 @@ static unsigned long xol_get_insn_slot(struct uprobe *uprobe) }/* -* xol_free_insn_slot - If slot was earlier allocated by -* @xol_get_insn_slot(), make the slot available for -* subsequent requests. +* xol_free_insn_slot - free the slot allocated by xol_get_insn_slot() */ static void xol_free_insn_slot (struct task_struct *tsk) { -struct xol_area *area; -unsigned long slot_addr; -unsigned long offset; - -if (!tsk->mm || !tsk->mm->uprobes_state.xol_area || !tsk->utask) -return ; - - slot_addr = tsk->utask->xol_vaddr; -if (unlikely (!slot_addr)) -return ; +struct xol_area *area = tsk->mm->uprobes_state.xol_area; +unsigned long offset = tsk->utask->xol_vaddr - area->vaddr; tsk->utask->xol_vaddr =0 ; - area = tsk->mm->uprobes_state.xol_area; - offset = slot_addr - area->vaddr;/* -* slot_addr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). +* xol_vaddr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). * This check can only fail if the "[uprobes]" vma was mremap'ed. */ if (offset <PAGE_SIZE ) {
@@ -1764,14 +1752,12 @@ void uprobe_free_utask(struct task_struct *t)if (!utask)return ; -if (utask->active_uprobe) -put_uprobe (utask->active_uprobe); +WARN_ON_ONCE (utask->active_uprobe || utask->xol_vaddr); ri = utask->return_instances;while (ri) ri =free_ret_instance (ri); -xol_free_insn_slot (t);kfree (utask); t->utask =NULL ; }
[tip: perf/core] uprobes: sanitiize xol_free_insn_slot()
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: c7b4133c48445dde789ed30b19ccb0448c7593f7 Gitweb: https://git.kernel.org/tip/c7b4133c48445dde789ed30b19ccb0448c7593f7 Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 29 Sep 2024 16:42:35 +02:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Mon, 07 Oct 2024 09:28:44 +02:00
uprobes: sanitiize xol_free_insn_slot()
1. Clear utask->xol_vaddr unconditionally, even if this addr is not valid, xol_free_insn_slot() should never return with utask->xol_vaddr != NULL.
2. Add a comment to explain why do we need to validate slot_addr.
3. Simplify the validation above. We can simply check offset < PAGE_SIZE, unsigned underflows are fine, it should work if slot_addr < area->vaddr.
4. Kill the unnecessary "slot_nr >= UINSNS_PER_PAGE" check, slot_nr must be valid if offset < PAGE_SIZE.
The next patches will cleanup this function even more.
--- kernel/events/uprobes.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-)
kernel/events/uprobes.c +9 -12
@@ -1680,8 +1680,8 @@ static unsigned long xol_get_insn_slot(struct uprobe *uprobe)static void xol_free_insn_slot (struct task_struct *tsk) {struct xol_area *area; -unsigned long vma_end;unsigned long slot_addr; +unsigned long offset;if (!tsk->mm || !tsk->mm->uprobes_state.xol_area || !tsk->utask)return ;
@@ -1690,24 +1690,21 @@ static void xol_free_insn_slot(struct task_struct *tsk)if (unlikely (!slot_addr))return ; + tsk->utask->xol_vaddr =0 ; area = tsk->mm->uprobes_state.xol_area; - vma_end = area->vaddr +PAGE_SIZE ; -if (area->vaddr <= slot_addr && slot_addr < vma_end) { -unsigned long offset; -int slot_nr; - - offset = slot_addr - area->vaddr; - slot_nr = offset /UPROBE_XOL_SLOT_BYTES ; -if (slot_nr >=UINSNS_PER_PAGE ) -return ; + offset = slot_addr - area->vaddr; +/* +* slot_addr must fit into [area->vaddr, area->vaddr + PAGE_SIZE). +* This check can only fail if the "[uprobes]" vma was mremap'ed. +*/ +if (offset <PAGE_SIZE ) { +int slot_nr = offset /UPROBE_XOL_SLOT_BYTES ;clear_bit (slot_nr, area->bitmap);atomic_dec (&area->slot_count);smp_mb__after_atomic ();/* pairs with prepare_to_wait() */ if (waitqueue_active (&area->wq))wake_up (&area->wq); - - tsk->utask->xol_vaddr =0 ; } }
[tip: perf/core] uprobes: don't abuse get_utask() in pre_ssout() and prepare_uretprobe()
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: b302d5a6fff5dd7ddb1e4752d60c0eaa4cc4f7f3 Gitweb: https://git.kernel.org/tip/b302d5a6fff5dd7ddb1e4752d60c0eaa4cc4f7f3 Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 29 Sep 2024 16:42:30 +02:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Mon, 07 Oct 2024 09:28:44 +02:00
uprobes: don't abuse get_utask() in pre_ssout() and prepare_uretprobe()
handle_swbp() calls get_utask() before prepare_uretprobe() or pre_ssout() can be called, they can simply use current->utask which can't be NULL.
--- kernel/events/uprobes.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-)
kernel/events/uprobes.c +3 -11
@@ -1905,18 +1905,14 @@ static void cleanup_return_instances(struct uprobe_task *utask, bool chained,static void prepare_uretprobe (struct uprobe *uprobe,struct pt_regs *regs) { -struct return_instance *ri; -struct uprobe_task *utask; +struct uprobe_task *utask = current->utask;unsigned long orig_ret_vaddr, trampoline_vaddr; +struct return_instance *ri;bool chained;if (!get_xol_area ())return ; - utask =get_utask (); -if (!utask) -return ; -if (utask->depth >=MAX_URETPROBE_DEPTH ) { printk_ratelimited(KERN_INFO "uprobe: omit uretprobe due to" " nestedness limit pid/tgid=%d/%d\n",
@@ -1977,14 +1973,10 @@ fail:static int pre_ssout (struct uprobe *uprobe,struct pt_regs *regs,unsigned long bp_vaddr) { -struct uprobe_task *utask; +struct uprobe_task *utask = current->utask;unsigned long xol_vaddr;int err; - utask =get_utask (); -if (!utask) -return -ENOMEM ; -if (!try_get_uprobe (uprobe))return -EINVAL ;