[RFC] [PATCH 1/2] Insertion/Removal of Breakpoint

Srinivasa D S srinivasa at in.ibm.com
Mon May 12 14:06:10 UTC 2008


This patch contains code for insertion and removal of breakpoint on user
process. 

Each virtual address at which breakpoint is inserted, is represented
through "ubp" structure. It has fields to store virtual address,
instruction at the virtual address and hlist_node to  insert it in ubp_table
maintained by ubp_per_process data structure.

"ubp_per_process" represents each process for which user breakpoints are
inserted. It maintains table of "ubp" objects. 

"ubp_process_table" is the global hash table of processes for which 
breakpoints are inserted. "ubp_process_table_lock" protects this table from
concurrent access.

insert_user_bp() 
	This procedure inserts breakpoint on the process identified by pid
at virtual address vaddr after validating vaddr. It copies the original
instruction at the virtual address to ubp object. Then ubp object is inserted 
in to ubp_table.
	insert_user_bp() returns successfully, if "ubp" object already exists 
for vaddr provided by the user. ("ubp" may be created by same instrumentation
module or by another module which uses this infrastructure.)

remove_user_bp() 
	This procedure removes breakpoint from the virtual address vaddr of 
process identified by pid. It goes through the ubp_process_table to fetch
ubp_per_process object and then ubp object associated with the virtual
address. It replaces breakpoint instruction by the original instruction
found in ubp object.

 Since insert_user_bp()/remove_user_bp() is called from a quiesce handler 
of a thread when all of its sibling threads are quiesced, no care has been 
taken to protect the data structures except ubp_process_table_lock. 

Limitation
==========
1) This patch doesn't support multiple unrelated(using different underlying
infrastructure for insertion/removal of breakpoints ) instrumentation modules
to probe same process.

todo
====
1) To support this feature on other architectures like x86/x86_64.


Signed-off-by: Srinivasa DS <srinivasa at in.ibm.com>


---
 include/asm-powerpc/user_breakpoint.h |   29 ++++
 include/linux/user_breakpoint.h       |   49 ++++++
 init/Kconfig                          |   11 +
 kernel/Makefile                       |    1 
 kernel/user_breakpoint.c              |  242 
++++++++++++++++++++++++++++++++++
 5 files changed, 332 insertions(+)

Index: linux-2.6.25-rc6/include/asm-powerpc/user_breakpoint.h
===================================================================
--- /dev/null
+++ linux-2.6.25-rc6/include/asm-powerpc/user_breakpoint.h
@@ -0,0 +1,29 @@
+#ifndef _ASM_USER_BREAKPOINT_H
+#define _ASM_USER_BREAKPOINT_H
+
+/*
+ * Insertion/Removal of breakpoint
+ * user_breakpoint.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) IBM Corporation, 2006
+ */
+
+#define BREAKPOINT_INSTRUCTION  0x7fe00008
+#define BP_INSN_SIZE 4
+#define MAX_UINSN_BYTES 4
+
+#endif /* ASM_POWERPC_USER_BREAKPOINT_H */
Index: linux-2.6.25-rc6/include/linux/user_breakpoint.h
===================================================================
--- /dev/null
+++ linux-2.6.25-rc6/include/linux/user_breakpoint.h
@@ -0,0 +1,49 @@
+#ifndef _USER_BREAKPOINT_H
+#define _USER_BREAKPOINT_H
+/*
+ * Insertion/Removal of breakpoint
+ * user_breakpoint.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) IBM Corporation, 2006
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <asm/user_breakpoint.h>
+#include <asm/uaccess.h>
+
+#define BP_HASH_BITS 5
+#define BP_TABLE_SIZE (1 << BP_HASH_BITS)
+
+typedef unsigned int user_opcode_t;
+
+struct ubp {
+	user_opcode_t opcode;
+	user_opcode_t insn[MAX_UINSN_BYTES / sizeof(user_opcode_t)];
+	unsigned long vaddr;
+	struct hlist_node bpt_node;
+};
+
+/*
+ * Represents each process for which user breakpoints are inserted.
+ */
+struct ubp_per_process {
+	struct hlist_head ubp_table[BP_TABLE_SIZE];
+	struct hlist_node hlist;   /* node for global user breakpoints table */
+	pid_t tgid;
+};
+
+#endif     /*_USER_BREAKPOINT_H */
Index: linux-2.6.25-rc6/init/Kconfig
===================================================================
--- linux-2.6.25-rc6.orig/init/Kconfig
+++ linux-2.6.25-rc6/init/Kconfig
@@ -887,6 +887,17 @@ config UTRACE
 	  applications.  Unless you are making a specially stripped-down
 	  kernel and are very sure you don't need these facilitiies,
 	  say Y.
+
+config USER_BP_ASSIST
+	bool "Infrastructure to insert/remove breakpoints on user processses"
+	default y
+	depends on  UTRACE
+	help
+	  This option provides infrastructure to insert/remove
+	  breakpoints on user processes. UTRACE exploiters can
+	  make use of this mechanism for insertion/removal of
+	  breakpoints.
+	  Say Y here if you want to enable USER_BP_ASSIST.
 endmenu
 
 source "block/Kconfig"
Index: linux-2.6.25-rc6/kernel/Makefile
===================================================================
--- linux-2.6.25-rc6.orig/kernel/Makefile
+++ linux-2.6.25-rc6/kernel/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
 obj-$(CONFIG_AUDIT_TREE) += audit_tree.o
 obj-$(CONFIG_KPROBES) += kprobes.o
 obj-$(CONFIG_UTRACE) += utrace.o
+obj-$(CONFIG_USER_BP_ASSIST) += user_breakpoint.o
 obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o
 obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
 obj-$(CONFIG_SECCOMP) += seccomp.o
Index: linux-2.6.25-rc6/kernel/user_breakpoint.c
===================================================================
--- /dev/null
+++ linux-2.6.25-rc6/kernel/user_breakpoint.c
@@ -0,0 +1,242 @@
+/*
+ *  Insertion/Removal of breakpoint
+ *  user_breakpoint.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) IBM Corporation, 2006
+ */
+#include <linux/user_breakpoint.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/hash.h>
+#include <asm/signal.h>
+
+/* Global hashtable of processes for which breakpoints are inserted */
+struct hlist_head ubp_process_table[BP_TABLE_SIZE];
+
+/* lock to protect ubp_process_table[]*/
+DEFINE_SPINLOCK(ubp_process_table_lock);
+
+static struct ubp_per_process *find_ubp_process(struct task_struct *tsk)
+{
+	struct hlist_head *head;
+	struct hlist_node *node;
+	struct ubp_per_process *ubp_proc = NULL;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ubp_process_table_lock, flags);
+	head = &ubp_process_table[hash_long(tsk->tgid, BP_HASH_BITS)];
+	hlist_for_each_entry(ubp_proc, node, head, hlist) {
+		if (ubp_proc->tgid == tsk->tgid) {
+			spin_unlock_irqrestore(&ubp_process_table_lock, flags);
+			return ubp_proc;
+		}
+	}
+	spin_unlock_irqrestore(&ubp_process_table_lock, flags);
+	return NULL;
+}
+
+static struct ubp_per_process *create_ubp_process(struct task_struct *p)
+{
+	struct ubp_per_process *ubp_proc;
+	unsigned long flags;
+	int i;
+
+	ubp_proc = kzalloc(sizeof *ubp_proc, GFP_USER);
+	if (ubp_proc == NULL)
+		return ERR_PTR(-ENOMEM);
+	for (i = 0; i < BP_TABLE_SIZE; i++)
+		INIT_HLIST_HEAD(&ubp_proc->ubp_table[i]);
+	INIT_HLIST_NODE(&ubp_proc->hlist);
+	ubp_proc->tgid = p->tgid;
+	spin_lock_irqsave(&ubp_process_table_lock, flags);
+	hlist_add_head(&ubp_proc->hlist,
+		&ubp_process_table[hash_long(p->tgid, BP_HASH_BITS)]);
+	spin_unlock_irqrestore(&ubp_process_table_lock, flags);
+	return ubp_proc;
+}
+
+/* When a process dies, cleanup the data structures, like delete ubp_proc 
from
+ * ubp_process_table and free ubp_proc. Clients can call this function from
+ * utrace exit_handler().
+ */
+void del_ubp_process(struct task_struct *tsk)
+{
+	struct hlist_head *head;
+	struct hlist_node *node;
+	struct ubp_per_process *ubp_proc = NULL;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ubp_process_table_lock, flags);
+	head = &ubp_process_table[hash_long(tsk->pid, BP_HASH_BITS)];
+	hlist_for_each_entry(ubp_proc, node, head, hlist) {
+		if (ubp_proc->tgid == tsk->pid) {
+			hlist_del(&ubp_proc->hlist);
+			kfree(ubp_proc);
+			spin_unlock_irqrestore(&ubp_process_table_lock, flags);
+			return;
+		}
+	}
+	spin_unlock_irqrestore(&ubp_process_table_lock, flags);
+	return;
+}
+
+static struct ubp *ubp_add(unsigned long vaddr,
+			 struct ubp_per_process *ubp_proc)
+{
+	struct ubp *bp;
+
+	bp = kzalloc(sizeof *bp, GFP_USER);
+	if (bp == NULL)
+		return ERR_PTR(-ENOMEM);
+	bp->vaddr = vaddr;
+	INIT_HLIST_NODE(&bp->bpt_node);
+	hlist_add_head(&bp->bpt_node,
+	   &ubp_proc->ubp_table[hash_long(bp->vaddr, BP_HASH_BITS)]);
+	return bp;
+}
+
+static struct ubp *ubp_find(unsigned long vaddr,
+			struct ubp_per_process *ubp_proc)
+{
+	struct hlist_node *node;
+	struct hlist_head *head;
+	struct ubp *bp;
+
+	head = &ubp_proc->ubp_table[hash_long(vaddr, BP_HASH_BITS)];
+	hlist_for_each_entry(bp, node, head, bpt_node) {
+		if (bp->vaddr == vaddr)
+			return bp;
+	}
+	return NULL;
+}
+
+static int validate_vaddr(struct task_struct *p, unsigned long vaddr)
+{
+	struct vm_area_struct *vma;
+	struct mm_struct *mm = p->mm;
+	int err = 0;
+
+	if (!mm)
+		return -EINVAL;
+
+	down_read(&mm->mmap_sem);
+	vma = find_vma(mm, vaddr);
+	if (!vma || (vaddr < vma->vm_start) || !(vma->vm_flags & VM_EXEC))
+		err = -EINVAL;
+
+	up_read(&mm->mmap_sem);
+
+	return err;
+}
+
+static int set_bp(struct ubp *bp, struct task_struct *tsk)
+{
+	user_opcode_t bp_insn = BREAKPOINT_INSTRUCTION;
+	return access_process_vm(tsk, bp->vaddr, &bp_insn, BP_INSN_SIZE, 1);
+}
+
+int insert_user_bp(unsigned long vaddr, struct task_struct *p)
+{
+	struct ubp_per_process *ubp_proc;
+	struct ubp *bp;
+	int ret, len;
+
+	ret = validate_vaddr(p, vaddr);
+	if (ret) {
+		printk(KERN_ERR "Invalid vitrual address %#lx in %d\n",
+						vaddr, p->tgid);
+		return -EINVAL;
+	}
+
+	ubp_proc = find_ubp_process(p);
+	if (!ubp_proc) {
+		ubp_proc = create_ubp_process(p);
+		if (IS_ERR(ubp_proc))
+			return -ENOMEM;
+	}
+
+	bp = ubp_find(vaddr, ubp_proc);
+	if (bp)             /*breakpoint is already inserted at this vaddr */
+		return 0;
+	else {
+		bp = ubp_add(vaddr, ubp_proc);
+		if (IS_ERR(bp))
+			return -ENOMEM;
+	}
+
+	len = access_process_vm(p, vaddr, bp->insn, MAX_UINSN_BYTES, 0);
+	if (len < BP_INSN_SIZE) {
+		printk(KERN_ERR "Failed to read original instruction at %#lx"
+						"in %d \n", vaddr, p->tgid);
+		goto bp_fail;
+	}
+
+	memcpy(&bp->opcode, bp->insn, BP_INSN_SIZE);
+	if (bp->opcode == BREAKPOINT_INSTRUCTION) {
+		printk(KERN_INFO "breakpoint already exists at %#lx addr"
+					"in %d \n", bp->vaddr, p->tgid);
+		goto bp_fail;
+	}
+	len = set_bp(bp, p);
+	if (len < BP_INSN_SIZE) {
+		printk(KERN_ERR "failed to insert breakpoint instruction at %d"
+					"vaddr %#lx \n", p->tgid, bp->vaddr);
+		goto bp_fail;
+	}
+	return 0;
+
+bp_fail:
+	hlist_del(&bp->bpt_node);
+	kfree(bp);
+	return -EINVAL;
+
+}
+EXPORT_SYMBOL(insert_user_bp);
+
+static int set_orig_insn(struct ubp *bp, struct task_struct *tsk)
+{
+	return access_process_vm(tsk, bp->vaddr, &bp->opcode, BP_INSN_SIZE, 1);
+}
+
+int remove_user_bp(unsigned long vaddr, struct task_struct *tsk)
+{
+	struct ubp *bp;
+	struct ubp_per_process *ubp_proc;
+	int len;
+
+
+	ubp_proc = find_ubp_process(tsk);
+	if (!ubp_proc)
+		return -EINVAL;
+
+	bp = ubp_find(vaddr, ubp_proc);
+	if (!bp)
+		return -EINVAL;
+
+	len = set_orig_insn(bp, tsk);
+	if (len < BP_INSN_SIZE) {
+		printk(KERN_ERR "Failed to remove breakpoint at %#lx:",
+								bp->vaddr);
+		send_sig(SIGKILL, tsk, 0);
+	}
+	hlist_del(&bp->bpt_node);
+	kfree(bp);
+
+	return 0;
+}
+EXPORT_SYMBOL(remove_user_bp);




More information about the utrace-devel mailing list