Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > fc11cd6e1c513a17304da94a5390f3cd > files > 1675

kernel-2.6.18-194.11.1.el5.src.rpm

From: John Villalovos <jvillalo@redhat.com>
Date: Mon, 4 May 2009 10:17:35 -0400
Subject: [misc] kprobes: fix deadlock issue
Message-id: 20090504141735.GA580@linuxjohn.usersys.redhat.com
O-Subject: [RHEL 5.4 BZ 210555 V2] Fix deadlock issue with kprobes
Bugzilla: 210555
RH-Acked-by: Dave Anderson <anderson@redhat.com>
RH-Acked-by: Chuck Ebbert <cebbert@redhat.com>

[Version 2] Reposting with whitespace fixes.  There were some lines that were
using spaces instead of tabs.  The only change to the patch has been to make
sure that all line additions are usings tabs instead of spaces for
indentation.  If a line previously used spaces for indentation and was
modified, then the modified line will use tabs for indentation.  On the lines
changed, upstream uses tabs instead of spaces.

Those who have already ACKed, please ACK again.  And anyone else please
feel free to ACK.

Fix deadlock issue in kprobes
https://bugzilla.redhat.com/show_bug.cgi?id=210555

This is a backport of two patches:
[PATCH] kretprobe spinlock deadlock patch
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=99219a3fbc2dcf2eaa954f7b2ac27299fd7894cd;hp=f2aa85a0ccd90110e76c6375535adc3ae358f971
kprobe_flush_task() possibly calls kfree function during holding
kretprobe_lock spinlock, if kfree function is probed by kretprobe that will
incur spinlock deadlock.  This patch moves kfree function out scope of
kretprobe_lock.

[PATCH] disallow kprobes on notifier_call_chain
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=f2aa85a0ccd90110e76c6375535adc3ae358f971
When kprobe is re-entered, the re-entered kprobe kernel path will will call
atomic_notifier_call_chain function, if this function is kprobed that will
incur numerous kprobe recursive fault.  This patch disallows kprobes on
atomic_notifier_call_chain function.

Testing:
The patched kernel has been tested and the deadlock issue does not occur with
the patched kernel.  The non-patched kernel has the deadlock issue.

Brew build passed all builds:
https://brewweb.devel.redhat.com/taskinfo?taskID=1784831

RHTS:
An RHTS KernelTier1 test pass has been done:
http://rhts.redhat.com/cgi-bin/rhts/jobs.cgi?id=56985
It passed all the tests, with the exception of the KABI test and that is
because the KABI test doesn't work testing brew scratch builds.  So it will
show "Completed - Fail" on individual architecures, but if you look closer you
will see that everything passed except for KABI:

diff --git a/arch/i386/kernel/kprobes.c b/arch/i386/kernel/kprobes.c
index 149e8b9..7ce4d82 100644
--- a/arch/i386/kernel/kprobes.c
+++ b/arch/i386/kernel/kprobes.c
@@ -408,11 +408,12 @@ no_kprobe:
 fastcall void *__kprobes trampoline_handler(struct pt_regs *regs)
 {
         struct kretprobe_instance *ri = NULL;
-        struct hlist_head *head;
+	struct hlist_head *head, empty_rp;
         struct hlist_node *node, *tmp;
 	unsigned long flags, orig_ret_address = 0;
 	unsigned long trampoline_address =(unsigned long)&kretprobe_trampoline;
 
+	INIT_HLIST_HEAD(&empty_rp);
 	spin_lock_irqsave(&kretprobe_lock, flags);
         head = kretprobe_inst_table_head(current);
 
@@ -441,7 +442,7 @@ fastcall void *__kprobes trampoline_handler(struct pt_regs *regs)
 		}
 
 		orig_ret_address = (unsigned long)ri->ret_addr;
-		recycle_rp_inst(ri);
+		recycle_rp_inst(ri, &empty_rp);
 
 		if (orig_ret_address != trampoline_address)
 			/*
@@ -456,6 +457,10 @@ fastcall void *__kprobes trampoline_handler(struct pt_regs *regs)
 
 	spin_unlock_irqrestore(&kretprobe_lock, flags);
 
+	hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {
+		hlist_del(&ri->hlist);
+		kfree(ri);
+	}
 	return (void*)orig_ret_address;
 }
 
diff --git a/arch/ia64/kernel/kprobes.c b/arch/ia64/kernel/kprobes.c
index 8e1a009..8b506ea 100644
--- a/arch/ia64/kernel/kprobes.c
+++ b/arch/ia64/kernel/kprobes.c
@@ -353,12 +353,13 @@ static void kretprobe_trampoline(void)
 int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
 {
 	struct kretprobe_instance *ri = NULL;
-	struct hlist_head *head;
+	struct hlist_head *head, empty_rp;
 	struct hlist_node *node, *tmp;
 	unsigned long flags, orig_ret_address = 0;
 	unsigned long trampoline_address =
 		((struct fnptr *)kretprobe_trampoline)->ip;
 
+	INIT_HLIST_HEAD(&empty_rp);
 	spin_lock_irqsave(&kretprobe_lock, flags);
 	head = kretprobe_inst_table_head(current);
 
@@ -401,7 +402,7 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
 			ri->rp->handler(ri, regs);
 
 		orig_ret_address = (unsigned long)ri->ret_addr;
-		recycle_rp_inst(ri);
+		recycle_rp_inst(ri, &empty_rp);
 
 		if (orig_ret_address != trampoline_address)
 			/*
@@ -418,6 +419,10 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
 	spin_unlock_irqrestore(&kretprobe_lock, flags);
 	preempt_enable_no_resched();
 
+	hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {
+		hlist_del(&ri->hlist);
+		kfree(ri);
+	}
 	/*
 	 * By returning a non-zero value, we are telling
 	 * kprobe_handler() that we don't want the post_handler
diff --git a/arch/powerpc/kernel/kprobes.c b/arch/powerpc/kernel/kprobes.c
index 4e94fcb..ec96388 100644
--- a/arch/powerpc/kernel/kprobes.c
+++ b/arch/powerpc/kernel/kprobes.c
@@ -262,11 +262,12 @@ void kretprobe_trampoline_holder(void)
 int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
 {
         struct kretprobe_instance *ri = NULL;
-        struct hlist_head *head;
+	struct hlist_head *head, empty_rp;
         struct hlist_node *node, *tmp;
 	unsigned long flags, orig_ret_address = 0;
 	unsigned long trampoline_address =(unsigned long)&kretprobe_trampoline;
 
+	INIT_HLIST_HEAD(&empty_rp);
 	spin_lock_irqsave(&kretprobe_lock, flags);
         head = kretprobe_inst_table_head(current);
 
@@ -292,7 +293,7 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
 			ri->rp->handler(ri, regs);
 
 		orig_ret_address = (unsigned long)ri->ret_addr;
-		recycle_rp_inst(ri);
+		recycle_rp_inst(ri, &empty_rp);
 
 		if (orig_ret_address != trampoline_address)
 			/*
@@ -310,6 +311,11 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
 	spin_unlock_irqrestore(&kretprobe_lock, flags);
 	preempt_enable_no_resched();
 
+	hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {
+		hlist_del(&ri->hlist);
+		kfree(ri);
+	}
+
         /*
          * By returning a non-zero value, we are telling
          * kprobe_handler() that we don't want the post_handler
diff --git a/arch/s390/kernel/kprobes.c b/arch/s390/kernel/kprobes.c
index 6363356..a8d8b9d 100644
--- a/arch/s390/kernel/kprobes.c
+++ b/arch/s390/kernel/kprobes.c
@@ -388,11 +388,12 @@ void kretprobe_trampoline_holder(void)
 int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
 {
 	struct kretprobe_instance *ri = NULL;
-	struct hlist_head *head;
+	struct hlist_head *head, empty_rp;
 	struct hlist_node *node, *tmp;
 	unsigned long flags, orig_ret_address = 0;
 	unsigned long trampoline_address = (unsigned long)&kretprobe_trampoline;
 
+	INIT_HLIST_HEAD(&empty_rp);
 	spin_lock_irqsave(&kretprobe_lock, flags);
 	head = kretprobe_inst_table_head(current);
 
@@ -418,7 +419,7 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
 			ri->rp->handler(ri, regs);
 
 		orig_ret_address = (unsigned long)ri->ret_addr;
-		recycle_rp_inst(ri);
+		recycle_rp_inst(ri, &empty_rp);
 
 		if (orig_ret_address != trampoline_address) {
 			/*
@@ -436,6 +437,10 @@ int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
 	spin_unlock_irqrestore(&kretprobe_lock, flags);
 	preempt_enable_no_resched();
 
+	hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {
+		hlist_del(&ri->hlist);
+		kfree(ri);
+	}
 	/*
 	 * By returning a non-zero value, we are telling
 	 * kprobe_handler() that we don't want the post_handler
diff --git a/arch/x86_64/kernel/kprobes.c b/arch/x86_64/kernel/kprobes.c
index 005a0de..9a49802 100644
--- a/arch/x86_64/kernel/kprobes.c
+++ b/arch/x86_64/kernel/kprobes.c
@@ -592,11 +592,12 @@ no_kprobe:
 fastcall void * __kprobes trampoline_handler(struct pt_regs *regs)
 {
         struct kretprobe_instance *ri = NULL;
-        struct hlist_head *head;
+	struct hlist_head *head, empty_rp;
         struct hlist_node *node, *tmp;
 	unsigned long flags, orig_ret_address = 0;
 	unsigned long trampoline_address =(unsigned long)&kretprobe_trampoline;
 
+	INIT_HLIST_HEAD(&empty_rp);
 	spin_lock_irqsave(&kretprobe_lock, flags);
         head = kretprobe_inst_table_head(current);
 	/* fixup rt_regs */
@@ -629,7 +630,7 @@ fastcall void * __kprobes trampoline_handler(struct pt_regs *regs)
 		}
 
 		orig_ret_address = (unsigned long)ri->ret_addr;
-		recycle_rp_inst(ri);
+		recycle_rp_inst(ri, &empty_rp);
 
 		if (orig_ret_address != trampoline_address)
 			/*
@@ -644,6 +645,11 @@ fastcall void * __kprobes trampoline_handler(struct pt_regs *regs)
 
 	spin_unlock_irqrestore(&kretprobe_lock, flags);
 
+	hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {
+		hlist_del(&ri->hlist);
+		kfree(ri);
+	}
+
         return (void *)orig_ret_address;
 }
 
diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
index 3830a0c..29de63c 100644
--- a/include/linux/kprobes.h
+++ b/include/linux/kprobes.h
@@ -215,7 +215,7 @@ void unregister_kretprobes(struct kretprobe **rps, int num);
 struct kretprobe_instance *get_free_rp_inst(struct kretprobe *rp);
 void add_rp_inst(struct kretprobe_instance *ri);
 void kprobe_flush_task(struct task_struct *tk);
-void recycle_rp_inst(struct kretprobe_instance *ri);
+void recycle_rp_inst(struct kretprobe_instance *ri, struct hlist_head *head);
 #else /* CONFIG_KPROBES */
 
 #define __kprobes	/**/
diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index ac07f87..01c9cba 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -308,7 +308,8 @@ void __kprobes add_rp_inst(struct kretprobe_instance *ri)
 }
 
 /* Called with kretprobe_lock held */
-void __kprobes recycle_rp_inst(struct kretprobe_instance *ri)
+void __kprobes recycle_rp_inst(struct kretprobe_instance *ri,
+				struct hlist_head *head)
 {
 	/* remove rp inst off the rprobe_inst_table */
 	hlist_del(&ri->hlist);
@@ -320,7 +321,7 @@ void __kprobes recycle_rp_inst(struct kretprobe_instance *ri)
 		hlist_add_head(&ri->uflist, &ri->rp->free_instances);
 	} else
 		/* Unregistering */
-		kfree(ri);
+		hlist_add_head(&ri->hlist, head);
 }
 
 struct hlist_head __kprobes *kretprobe_inst_table_head(struct task_struct *tsk)
@@ -337,17 +338,23 @@ struct hlist_head __kprobes *kretprobe_inst_table_head(struct task_struct *tsk)
 void __kprobes kprobe_flush_task(struct task_struct *tk)
 {
         struct kretprobe_instance *ri;
-        struct hlist_head *head;
+	struct hlist_head *head, empty_rp;
 	struct hlist_node *node, *tmp;
 	unsigned long flags = 0;
 
+	INIT_HLIST_HEAD(&empty_rp);
 	spin_lock_irqsave(&kretprobe_lock, flags);
         head = kretprobe_inst_table_head(tk);
         hlist_for_each_entry_safe(ri, node, tmp, head, hlist) {
                 if (ri->task == tk)
-                        recycle_rp_inst(ri);
+			recycle_rp_inst(ri, &empty_rp);
         }
 	spin_unlock_irqrestore(&kretprobe_lock, flags);
+
+	hlist_for_each_entry_safe(ri, node, tmp, &empty_rp, hlist) {
+		hlist_del(&ri->hlist);
+		kfree(ri);
+	}
 }
 
 static inline void free_rp_inst(struct kretprobe *rp)
diff --git a/kernel/sys.c b/kernel/sys.c
index 4a46572..cb1d0e5 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -221,7 +221,7 @@ EXPORT_SYMBOL_GPL(atomic_notifier_chain_unregister);
  *	of the last notifier function called.
  */
  
-int atomic_notifier_call_chain(struct atomic_notifier_head *nh,
+int __kprobes atomic_notifier_call_chain(struct atomic_notifier_head *nh,
 		unsigned long val, void *v)
 {
 	int ret;