diff mbox series

[SRU,jammy,v2,3/5] kprobes: Add kretprobe_find_ret_addr() for searching return address

Message ID 20230331140041.2112510-4-john.cabaj@canonical.com
State New
Headers show
Series ftrace graph return address recovery support for s390x Livepatch | expand

Commit Message

John Cabaj March 31, 2023, 2 p.m. UTC
From: Masami Hiramatsu <mhiramat@kernel.org>

BugLink: https://bugs.launchpad.net/bugs/2013603 (Kernel livepatch ftrace graph fix)

Introduce kretprobe_find_ret_addr() and is_kretprobe_trampoline().
These APIs will be used by the ORC stack unwinder and ftrace, so that
they can check whether the given address points kretprobe trampoline
code and query the correct return address in that case.

Link: https://lkml.kernel.org/r/163163046461.489837.1044778356430293962.stgit@devnote2

Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Tested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
(backported from commit 03bac0df2886882c43e6d0bfff9dee84a184fc7e)
[John Cabaj: cleaning-up kernel panic that was relocated to conditional]
Signed-off-by: John Cabaj <john.cabaj@canonical.com>
---
 include/linux/kprobes.h |  22 ++++++++
 kernel/kprobes.c        | 109 ++++++++++++++++++++++++++++++----------
 2 files changed, 105 insertions(+), 26 deletions(-)

Comments

Stefan Bader April 6, 2023, 1:42 p.m. UTC | #1
On 31.03.23 16:00, John Cabaj wrote:
> From: Masami Hiramatsu <mhiramat@kernel.org>
> 
> BugLink: https://bugs.launchpad.net/bugs/2013603 (Kernel livepatch ftrace graph fix)
> 
> Introduce kretprobe_find_ret_addr() and is_kretprobe_trampoline().
> These APIs will be used by the ORC stack unwinder and ftrace, so that
> they can check whether the given address points kretprobe trampoline
> code and query the correct return address in that case.
> 
> Link: https://lkml.kernel.org/r/163163046461.489837.1044778356430293962.stgit@devnote2
> 
> Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
> Tested-by: Andrii Nakryiko <andrii@kernel.org>
> Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
> (backported from commit 03bac0df2886882c43e6d0bfff9dee84a184fc7e)
> [John Cabaj: cleaning-up kernel panic that was relocated to conditional]
> Signed-off-by: John Cabaj <john.cabaj@canonical.com>
> ---
>   include/linux/kprobes.h |  22 ++++++++
>   kernel/kprobes.c        | 109 ++++++++++++++++++++++++++++++----------
>   2 files changed, 105 insertions(+), 26 deletions(-)
> 
> diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
> index f6b0aef35f66..9e24d8b79100 100644
> --- a/include/linux/kprobes.h
> +++ b/include/linux/kprobes.h
> @@ -511,6 +511,28 @@ static inline bool is_kprobe_optinsn_slot(unsigned long addr)
>   }
>   #endif
>   
> +#ifdef CONFIG_KRETPROBES
> +static nokprobe_inline bool is_kretprobe_trampoline(unsigned long addr)
> +{
> +	return (void *)addr == kretprobe_trampoline_addr();
> +}
> +
> +unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,
> +				      struct llist_node **cur);
> +#else
> +static nokprobe_inline bool is_kretprobe_trampoline(unsigned long addr)
> +{
> +	return false;
> +}
> +
> +static nokprobe_inline
> +unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,
> +				      struct llist_node **cur)
> +{
> +	return 0;
> +}
> +#endif
> +
>   /* Returns true if kprobes handled the fault */
>   static nokprobe_inline bool kprobe_page_fault(struct pt_regs *regs,
>   					      unsigned int trap)

The set fails to apply to current jammy:linux/master-next:

Applying: kprobes: treewide: Remove trampoline_address from 
kretprobe_trampoline_handler()
Applying: kprobes: treewide: Make it harder to refer 
kretprobe_trampoline directly
Applying: kprobes: Add kretprobe_find_ret_addr() for searching return 
address
error: patch failed: kernel/kprobes.c:1866
error: kernel/kprobes.c: patch does not apply
Patch failed at 0003 kprobes: Add kretprobe_find_ret_addr() for 
searching return address

checking file include/linux/kprobes.h
checking file kernel/kprobes.c
Hunk #1 FAILED at 1866.
Hunk #2 succeeded at 1913 (offset -2 lines).
1 out of 2 hunks FAILED

Please submit again (v3) once this is fixed.

-Stefan

> diff --git a/kernel/kprobes.c b/kernel/kprobes.c
> index 7da4a5048eb7..8e06a8d6516c 100644
> --- a/kernel/kprobes.c
> +++ b/kernel/kprobes.c
> @@ -1866,45 +1866,87 @@ unsigned long __weak arch_deref_entry_point(void *entry)
>   
>   #ifdef CONFIG_KRETPROBES
>   
> -unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
> -					     void *frame_pointer)
> +/* This assumes the 'tsk' is the current task or the is not running. */
> +static kprobe_opcode_t *__kretprobe_find_ret_addr(struct task_struct *tsk,
> +						  struct llist_node **cur)
>   {
> -	kprobe_opcode_t *correct_ret_addr = NULL;
>   	struct kretprobe_instance *ri = NULL;
> -	struct llist_node *first, *node;
> -	struct kretprobe *rp;
> +	struct llist_node *node = *cur;
> +
> +	if (!node)
> +		node = tsk->kretprobe_instances.first;
> +	else
> +		node = node->next;
>   
> -	/* Find all nodes for this frame. */
> -	first = node = current->kretprobe_instances.first;
>   	while (node) {
>   		ri = container_of(node, struct kretprobe_instance, llist);
> -
> -		BUG_ON(ri->fp != frame_pointer);
> -
>   		if (ri->ret_addr != kretprobe_trampoline_addr()) {
> -			correct_ret_addr = ri->ret_addr;
> -			/*
> -			 * This is the real return address. Any other
> -			 * instances associated with this task are for
> -			 * other calls deeper on the call stack
> -			 */
> -			goto found;
> +			*cur = node;
> +			return ri->ret_addr;
>   		}
> -
>   		node = node->next;
>   	}
> -	pr_err("Oops! Kretprobe fails to find correct return address.\n");
> -	BUG_ON(1);
> +	return NULL;
> +}
> +NOKPROBE_SYMBOL(__kretprobe_find_ret_addr);
>   
> -found:
> -	/* Unlink all nodes for this frame. */
> -	current->kretprobe_instances.first = node->next;
> -	node->next = NULL;
> +/**
> + * kretprobe_find_ret_addr -- Find correct return address modified by kretprobe
> + * @tsk: Target task
> + * @fp: A frame pointer
> + * @cur: a storage of the loop cursor llist_node pointer for next call
> + *
> + * Find the correct return address modified by a kretprobe on @tsk in unsigned
> + * long type. If it finds the return address, this returns that address value,
> + * or this returns 0.
> + * The @tsk must be 'current' or a task which is not running. @fp is a hint
> + * to get the currect return address - which is compared with the
> + * kretprobe_instance::fp field. The @cur is a loop cursor for searching the
> + * kretprobe return addresses on the @tsk. The '*@cur' should be NULL at the
> + * first call, but '@cur' itself must NOT NULL.
> + */
> +unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,
> +				      struct llist_node **cur)
> +{
> +	struct kretprobe_instance *ri = NULL;
> +	kprobe_opcode_t *ret;
> +
> +	if (WARN_ON_ONCE(!cur))
> +		return 0;
> +
> +	do {
> +		ret = __kretprobe_find_ret_addr(tsk, cur);
> +		if (!ret)
> +			break;
> +		ri = container_of(*cur, struct kretprobe_instance, llist);
> +	} while (ri->fp != fp);
>   
> -	/* Run them..  */
> +	return (unsigned long)ret;
> +}
> +NOKPROBE_SYMBOL(kretprobe_find_ret_addr);
> +
> +unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
> +					     void *frame_pointer)
> +{
> +	kprobe_opcode_t *correct_ret_addr = NULL;
> +	struct kretprobe_instance *ri = NULL;
> +	struct llist_node *first, *node = NULL;
> +	struct kretprobe *rp;
> +
> +	/* Find correct address and all nodes for this frame. */
> +	correct_ret_addr = __kretprobe_find_ret_addr(current, &node);
> +	if (!correct_ret_addr) {
> +		pr_err("kretprobe: Return address not found, not execute handler. Maybe there is a bug in the kernel.\n");
> +		BUG_ON(1);
> +	}
> +
> +	/* Run the user handler of the nodes. */
> +	first = current->kretprobe_instances.first;
>   	while (first) {
>   		ri = container_of(first, struct kretprobe_instance, llist);
> -		first = first->next;
> +
> +		if (WARN_ON_ONCE(ri->fp != frame_pointer))
> +			break;
>   
>   		rp = get_kretprobe(ri);
>   		if (rp && rp->handler) {
> @@ -1915,6 +1957,21 @@ unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
>   			rp->handler(ri, regs);
>   			__this_cpu_write(current_kprobe, prev);
>   		}
> +		if (first == node)
> +			break;
> +
> +		first = first->next;
> +	}
> +
> +	/* Unlink all nodes for this frame. */
> +	first = current->kretprobe_instances.first;
> +	current->kretprobe_instances.first = node->next;
> +	node->next = NULL;
> +
> +	/* Recycle free instances. */
> +	while (first) {
> +		ri = container_of(first, struct kretprobe_instance, llist);
> +		first = first->next;
>   
>   		recycle_rp_inst(ri);
>   	}
John Cabaj April 6, 2023, 3:15 p.m. UTC | #2
On 4/6/23 8:42 AM, Stefan Bader wrote:
> On 31.03.23 16:00, John Cabaj wrote:
>> From: Masami Hiramatsu <mhiramat@kernel.org>
>>
>> BugLink: https://bugs.launchpad.net/bugs/2013603 (Kernel livepatch ftrace graph fix)
>>
>> Introduce kretprobe_find_ret_addr() and is_kretprobe_trampoline().
>> These APIs will be used by the ORC stack unwinder and ftrace, so that
>> they can check whether the given address points kretprobe trampoline
>> code and query the correct return address in that case.
>>
>> Link: https://lkml.kernel.org/r/163163046461.489837.1044778356430293962.stgit@devnote2
>>
>> Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
>> Tested-by: Andrii Nakryiko <andrii@kernel.org>
>> Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
>> (backported from commit 03bac0df2886882c43e6d0bfff9dee84a184fc7e)
>> [John Cabaj: cleaning-up kernel panic that was relocated to conditional]
>> Signed-off-by: John Cabaj <john.cabaj@canonical.com>
>> ---
>>   include/linux/kprobes.h |  22 ++++++++
>>   kernel/kprobes.c        | 109 ++++++++++++++++++++++++++++++----------
>>   2 files changed, 105 insertions(+), 26 deletions(-)
>>
>> diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
>> index f6b0aef35f66..9e24d8b79100 100644
>> --- a/include/linux/kprobes.h
>> +++ b/include/linux/kprobes.h
>> @@ -511,6 +511,28 @@ static inline bool is_kprobe_optinsn_slot(unsigned long addr)
>>   }
>>   #endif
>>   +#ifdef CONFIG_KRETPROBES
>> +static nokprobe_inline bool is_kretprobe_trampoline(unsigned long addr)
>> +{
>> +    return (void *)addr == kretprobe_trampoline_addr();
>> +}
>> +
>> +unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,
>> +                      struct llist_node **cur);
>> +#else
>> +static nokprobe_inline bool is_kretprobe_trampoline(unsigned long addr)
>> +{
>> +    return false;
>> +}
>> +
>> +static nokprobe_inline
>> +unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,
>> +                      struct llist_node **cur)
>> +{
>> +    return 0;
>> +}
>> +#endif
>> +
>>   /* Returns true if kprobes handled the fault */
>>   static nokprobe_inline bool kprobe_page_fault(struct pt_regs *regs,
>>                             unsigned int trap)
> 
> The set fails to apply to current jammy:linux/master-next:
> 
> Applying: kprobes: treewide: Remove trampoline_address from kretprobe_trampoline_handler()
> Applying: kprobes: treewide: Make it harder to refer kretprobe_trampoline directly
> Applying: kprobes: Add kretprobe_find_ret_addr() for searching return address
> error: patch failed: kernel/kprobes.c:1866
> error: kernel/kprobes.c: patch does not apply
> Patch failed at 0003 kprobes: Add kretprobe_find_ret_addr() for searching return address
> 
> checking file include/linux/kprobes.h
> checking file kernel/kprobes.c
> Hunk #1 FAILED at 1866.
> Hunk #2 succeeded at 1913 (offset -2 lines).
> 1 out of 2 hunks FAILED

This likely collided with changes being applied since this patch was originally submitted, possibly c3694073333c ("kprobes: treewide: Cleanup the error messages for kprobes")

Resubmitted and checked with a re-application - hopefully it doesn't conflict with more kprobes changes before next application.


Thanks,
John

> 
> Please submit again (v3) once this is fixed.
> 
> -Stefan
> 
>> diff --git a/kernel/kprobes.c b/kernel/kprobes.c
>> index 7da4a5048eb7..8e06a8d6516c 100644
>> --- a/kernel/kprobes.c
>> +++ b/kernel/kprobes.c
>> @@ -1866,45 +1866,87 @@ unsigned long __weak arch_deref_entry_point(void *entry)
>>     #ifdef CONFIG_KRETPROBES
>>   -unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
>> -                         void *frame_pointer)
>> +/* This assumes the 'tsk' is the current task or the is not running. */
>> +static kprobe_opcode_t *__kretprobe_find_ret_addr(struct task_struct *tsk,
>> +                          struct llist_node **cur)
>>   {
>> -    kprobe_opcode_t *correct_ret_addr = NULL;
>>       struct kretprobe_instance *ri = NULL;
>> -    struct llist_node *first, *node;
>> -    struct kretprobe *rp;
>> +    struct llist_node *node = *cur;
>> +
>> +    if (!node)
>> +        node = tsk->kretprobe_instances.first;
>> +    else
>> +        node = node->next;
>>   -    /* Find all nodes for this frame. */
>> -    first = node = current->kretprobe_instances.first;
>>       while (node) {
>>           ri = container_of(node, struct kretprobe_instance, llist);
>> -
>> -        BUG_ON(ri->fp != frame_pointer);
>> -
>>           if (ri->ret_addr != kretprobe_trampoline_addr()) {
>> -            correct_ret_addr = ri->ret_addr;
>> -            /*
>> -             * This is the real return address. Any other
>> -             * instances associated with this task are for
>> -             * other calls deeper on the call stack
>> -             */
>> -            goto found;
>> +            *cur = node;
>> +            return ri->ret_addr;
>>           }
>> -
>>           node = node->next;
>>       }
>> -    pr_err("Oops! Kretprobe fails to find correct return address.\n");
>> -    BUG_ON(1);
>> +    return NULL;
>> +}
>> +NOKPROBE_SYMBOL(__kretprobe_find_ret_addr);
>>   -found:
>> -    /* Unlink all nodes for this frame. */
>> -    current->kretprobe_instances.first = node->next;
>> -    node->next = NULL;
>> +/**
>> + * kretprobe_find_ret_addr -- Find correct return address modified by kretprobe
>> + * @tsk: Target task
>> + * @fp: A frame pointer
>> + * @cur: a storage of the loop cursor llist_node pointer for next call
>> + *
>> + * Find the correct return address modified by a kretprobe on @tsk in unsigned
>> + * long type. If it finds the return address, this returns that address value,
>> + * or this returns 0.
>> + * The @tsk must be 'current' or a task which is not running. @fp is a hint
>> + * to get the currect return address - which is compared with the
>> + * kretprobe_instance::fp field. The @cur is a loop cursor for searching the
>> + * kretprobe return addresses on the @tsk. The '*@cur' should be NULL at the
>> + * first call, but '@cur' itself must NOT NULL.
>> + */
>> +unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,
>> +                      struct llist_node **cur)
>> +{
>> +    struct kretprobe_instance *ri = NULL;
>> +    kprobe_opcode_t *ret;
>> +
>> +    if (WARN_ON_ONCE(!cur))
>> +        return 0;
>> +
>> +    do {
>> +        ret = __kretprobe_find_ret_addr(tsk, cur);
>> +        if (!ret)
>> +            break;
>> +        ri = container_of(*cur, struct kretprobe_instance, llist);
>> +    } while (ri->fp != fp);
>>   -    /* Run them..  */
>> +    return (unsigned long)ret;
>> +}
>> +NOKPROBE_SYMBOL(kretprobe_find_ret_addr);
>> +
>> +unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
>> +                         void *frame_pointer)
>> +{
>> +    kprobe_opcode_t *correct_ret_addr = NULL;
>> +    struct kretprobe_instance *ri = NULL;
>> +    struct llist_node *first, *node = NULL;
>> +    struct kretprobe *rp;
>> +
>> +    /* Find correct address and all nodes for this frame. */
>> +    correct_ret_addr = __kretprobe_find_ret_addr(current, &node);
>> +    if (!correct_ret_addr) {
>> +        pr_err("kretprobe: Return address not found, not execute handler. Maybe there is a bug in the kernel.\n");
>> +        BUG_ON(1);
>> +    }
>> +
>> +    /* Run the user handler of the nodes. */
>> +    first = current->kretprobe_instances.first;
>>       while (first) {
>>           ri = container_of(first, struct kretprobe_instance, llist);
>> -        first = first->next;
>> +
>> +        if (WARN_ON_ONCE(ri->fp != frame_pointer))
>> +            break;
>>             rp = get_kretprobe(ri);
>>           if (rp && rp->handler) {
>> @@ -1915,6 +1957,21 @@ unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
>>               rp->handler(ri, regs);
>>               __this_cpu_write(current_kprobe, prev);
>>           }
>> +        if (first == node)
>> +            break;
>> +
>> +        first = first->next;
>> +    }
>> +
>> +    /* Unlink all nodes for this frame. */
>> +    first = current->kretprobe_instances.first;
>> +    current->kretprobe_instances.first = node->next;
>> +    node->next = NULL;
>> +
>> +    /* Recycle free instances. */
>> +    while (first) {
>> +        ri = container_of(first, struct kretprobe_instance, llist);
>> +        first = first->next;
>>             recycle_rp_inst(ri);
>>       }
>
diff mbox series

Patch

diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
index f6b0aef35f66..9e24d8b79100 100644
--- a/include/linux/kprobes.h
+++ b/include/linux/kprobes.h
@@ -511,6 +511,28 @@  static inline bool is_kprobe_optinsn_slot(unsigned long addr)
 }
 #endif
 
+#ifdef CONFIG_KRETPROBES
+static nokprobe_inline bool is_kretprobe_trampoline(unsigned long addr)
+{
+	return (void *)addr == kretprobe_trampoline_addr();
+}
+
+unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,
+				      struct llist_node **cur);
+#else
+static nokprobe_inline bool is_kretprobe_trampoline(unsigned long addr)
+{
+	return false;
+}
+
+static nokprobe_inline
+unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,
+				      struct llist_node **cur)
+{
+	return 0;
+}
+#endif
+
 /* Returns true if kprobes handled the fault */
 static nokprobe_inline bool kprobe_page_fault(struct pt_regs *regs,
 					      unsigned int trap)
diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index 7da4a5048eb7..8e06a8d6516c 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -1866,45 +1866,87 @@  unsigned long __weak arch_deref_entry_point(void *entry)
 
 #ifdef CONFIG_KRETPROBES
 
-unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
-					     void *frame_pointer)
+/* This assumes the 'tsk' is the current task or the is not running. */
+static kprobe_opcode_t *__kretprobe_find_ret_addr(struct task_struct *tsk,
+						  struct llist_node **cur)
 {
-	kprobe_opcode_t *correct_ret_addr = NULL;
 	struct kretprobe_instance *ri = NULL;
-	struct llist_node *first, *node;
-	struct kretprobe *rp;
+	struct llist_node *node = *cur;
+
+	if (!node)
+		node = tsk->kretprobe_instances.first;
+	else
+		node = node->next;
 
-	/* Find all nodes for this frame. */
-	first = node = current->kretprobe_instances.first;
 	while (node) {
 		ri = container_of(node, struct kretprobe_instance, llist);
-
-		BUG_ON(ri->fp != frame_pointer);
-
 		if (ri->ret_addr != kretprobe_trampoline_addr()) {
-			correct_ret_addr = ri->ret_addr;
-			/*
-			 * This is the real return address. Any other
-			 * instances associated with this task are for
-			 * other calls deeper on the call stack
-			 */
-			goto found;
+			*cur = node;
+			return ri->ret_addr;
 		}
-
 		node = node->next;
 	}
-	pr_err("Oops! Kretprobe fails to find correct return address.\n");
-	BUG_ON(1);
+	return NULL;
+}
+NOKPROBE_SYMBOL(__kretprobe_find_ret_addr);
 
-found:
-	/* Unlink all nodes for this frame. */
-	current->kretprobe_instances.first = node->next;
-	node->next = NULL;
+/**
+ * kretprobe_find_ret_addr -- Find correct return address modified by kretprobe
+ * @tsk: Target task
+ * @fp: A frame pointer
+ * @cur: a storage of the loop cursor llist_node pointer for next call
+ *
+ * Find the correct return address modified by a kretprobe on @tsk in unsigned
+ * long type. If it finds the return address, this returns that address value,
+ * or this returns 0.
+ * The @tsk must be 'current' or a task which is not running. @fp is a hint
+ * to get the currect return address - which is compared with the
+ * kretprobe_instance::fp field. The @cur is a loop cursor for searching the
+ * kretprobe return addresses on the @tsk. The '*@cur' should be NULL at the
+ * first call, but '@cur' itself must NOT NULL.
+ */
+unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,
+				      struct llist_node **cur)
+{
+	struct kretprobe_instance *ri = NULL;
+	kprobe_opcode_t *ret;
+
+	if (WARN_ON_ONCE(!cur))
+		return 0;
+
+	do {
+		ret = __kretprobe_find_ret_addr(tsk, cur);
+		if (!ret)
+			break;
+		ri = container_of(*cur, struct kretprobe_instance, llist);
+	} while (ri->fp != fp);
 
-	/* Run them..  */
+	return (unsigned long)ret;
+}
+NOKPROBE_SYMBOL(kretprobe_find_ret_addr);
+
+unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
+					     void *frame_pointer)
+{
+	kprobe_opcode_t *correct_ret_addr = NULL;
+	struct kretprobe_instance *ri = NULL;
+	struct llist_node *first, *node = NULL;
+	struct kretprobe *rp;
+
+	/* Find correct address and all nodes for this frame. */
+	correct_ret_addr = __kretprobe_find_ret_addr(current, &node);
+	if (!correct_ret_addr) {
+		pr_err("kretprobe: Return address not found, not execute handler. Maybe there is a bug in the kernel.\n");
+		BUG_ON(1);
+	}
+
+	/* Run the user handler of the nodes. */
+	first = current->kretprobe_instances.first;
 	while (first) {
 		ri = container_of(first, struct kretprobe_instance, llist);
-		first = first->next;
+
+		if (WARN_ON_ONCE(ri->fp != frame_pointer))
+			break;
 
 		rp = get_kretprobe(ri);
 		if (rp && rp->handler) {
@@ -1915,6 +1957,21 @@  unsigned long __kretprobe_trampoline_handler(struct pt_regs *regs,
 			rp->handler(ri, regs);
 			__this_cpu_write(current_kprobe, prev);
 		}
+		if (first == node)
+			break;
+
+		first = first->next;
+	}
+
+	/* Unlink all nodes for this frame. */
+	first = current->kretprobe_instances.first;
+	current->kretprobe_instances.first = node->next;
+	node->next = NULL;
+
+	/* Recycle free instances. */
+	while (first) {
+		ri = container_of(first, struct kretprobe_instance, llist);
+		first = first->next;
 
 		recycle_rp_inst(ri);
 	}