kprobes sample driver
PATCH 1/1 kprobes sample driver
This is a sample kprobes module that implements a simple misc char device that can cause arbitrary text to be printk'ed when arbitrary kernel addresses are executed.
This patch was created on top of a 2.5.47 kernel with the kprobes patch applied.
I created this driver just to learn what kprobes was all about. I am sending this to the mailing list for other people that are curious about kprobes (and also to have people who know more about kprobes show me where my understanding is flawed.)
To use the device, create a misc char node with a minor of 221, and then write strings of the format "0xADDRESS MESSAGE" to the device. When the file is closed all kprobes are unregistered, so I usually test by doing:
$ mknod /dev/noisy c 10 221 $ cat > /dev/noisy 0xc0107d50 sys_fork
.. and then go do something that will trigger a sys_fork.
-rustyl
drivers/char/Kconfig +15 -0
@@ -1270,5 +1270,20 @@ Once bound, I/O against /dev/raw/rawN uses efficient zero-copy I/O. See the raw(8) manpage for more details. +config NOISY + tristate "Noisy Interface Support" + ---help--- + If you say Y here and create a character special file /dev/noisy with + major number 10 and minor number 221 using mknod ("man mknod"), you + will get access to an interface for inserting arbitrary printk's + into executing kernel code. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module is called noisy.o. If you want to compile it as a module, + say M here and read <file:Documentation/modules.txt>. + + If unsure, say N. + endmenu
drivers/char/Makefile +1 -0
@@ -102,6 +102,7 @@ obj-$(CONFIG_AGP) += agp/ obj-$(CONFIG_DRM) += drm/ obj-$(CONFIG_PCMCIA) += pcmcia/ +obj-$(CONFIG_NOISY) += noisy.o # Files generated that shall be removed upon make clean clean-files := consolemap_deftbl.c defkeymap.c qtronixmap.c
drivers/char/noisy.c +259 -0
259 lines not on this page · raw message
include/linux/miscdevice.h +1 -0
@@ -23,6 +23,7 @@#define MICROCODE_MINOR 184#define MWAVE_MINOR 219/* ACP/Mwave Modem */ #define MPT_MINOR 220 +#define NOISY_MINOR 221#define MISC_DYNAMIC_MINOR 255#define SGI_GRAPHICS_MINOR 146
Hi,
This is very nice. In fact, I will probably start using this for testing kprobes myself. I have a few comments, given below inline.
Thank you,
Vamsi.
PS: I am cc'ing dprobes mailing list for folks who hang out there to have a chance to take a look at this and may be comment/use. Hope you don't mind.
Signature · 5 lines
Vamsi Krishna S. Linux Technology Center, IBM Software Lab, Bangalore. Ph: +91 80 5044959 Internet: [email protected]
Quoted 4 lines · Rusty Lynch
This is a sample kprobes module that implements a simple misc char device that can cause arbitrary text to be printk'ed when arbitrary kernel addresses are executed.
Nice idea.
drivers/char/noisy.c line 28
Quoted 7 lines
drivers/char/noisy.c +7
-0+static struct list_head probe_list; +struct nprobe { +struct list_head list; +struct kprobe probe; +char message[MAX_MSG_SIZE +1 ]; +}; +
Good. This is how I meant struct kprobe to be used: as a part of a bigger structure that the caller uses to manage probes.
drivers/char/noisy.c line 43
Quoted 12 lines
drivers/char/noisy.c +12
-0+static void noisy_pre_handler (struct kprobe *p,struct pt_regs *r) +{ +struct list_head *tmp; + +printk (KERN_CRIT "noisy: noisy_prehandler\n" ); +list_for_each (tmp, &probe_list) { + struct nprobe *c = list_entry(tmp, struct nprobe, list); +if (&(c->probe) == p) { +printk (KERN_CRIT "%s\n" , c->message); + } + } +}
Actually, you can do this in a much easier way without having to loop through all probes. All you need is:
static void noisy_pre_handler(struct kprobe *p, struct pt_regs *r)
{
struct nprobe *c = container_of(p, struct nprobe, probe);
printk(KERN_CRIT "%s: %s\n", __FUNCTION__, c->message);
}drivers/char/noisy.c line 100
Quoted 8 lines
drivers/char/noisy.c +8
-0+static ssize_t noisy_read (struct file *file,char *buf, +size_t count,loff_t *ppos) +{ +struct list_head *tmp; + +printk (KERN_CRIT "noisy: noisy_read\n" ); +list_for_each (tmp, &probe_list) { + struct nprobe *p = list_entry(tmp, struct nprobe, list);
You could have used list_for_each_entry as:
struct nprobe *p;
list_for_each_entry(p, &probe_list, list) {
...
}Quoted 7 lines
drivers/char/noisy.c +7
-0+static ssize_t noisy_write (struct file *file,const char *buf,size_t count, +loff_t *ppos) +{ +struct nprobe *n =0 ; +size_t ret = -ENOMEM ; +char *tmp =0 ; +
drivers/char/noisy.c line 144
Quoted 10 lines
drivers/char/noisy.c +10
-0+printk (KERN_CRIT "noisy: noisy_write\n" ); +if (count >MAX_MSG_SIZE ) { +printk (KERN_CRIT +"noisy: Input buffer (%i bytes) is too big!\n" , + count); + ret = -EINVAL ; + goto out; + } + + tmp = (char *)kmalloc (count +1 ,GFP_KERNEL );
Don't bother casting the return values from kmalloc. It is not needed. Same for all other kmalloc calls here.
Quoted 9 lines
drivers/char/noisy.c +9
-0+if (!tmp) { + ret = -ENOMEM ; + goto out; + } + + n = (struct nprobe *)kmalloc (sizeof (struct nprobe ),GFP_KERNEL ); +if (!n) { + ret = -ENOMEM ; + goto out;
drivers/char/noisy.c line 163
Quoted 10 lines
drivers/char/noisy.c +10
-0+ } +memset (n,'\0' ,sizeof (struct nprobe )); + +if (copy_from_user ((void *)tmp, (void *)buf, count)) { + ret = -ENOMEM ; + goto out; + } + tmp[count] ='\0' ; + + n = (struct nprobe *)kmalloc (sizeof (struct nprobe ),GFP_KERNEL );
This is a duplicate call, kill it. You have already alloc'ed n above.
Quoted 19 lines
drivers/char/noisy.c +19
-0+if (!n) { + ret = -ENOMEM ; + goto out; + } + +if (2 !=sscanf (tmp,"0x%x %s" , &(n->probe).addr, n->message)) { + ret = -EINVAL ; + goto out; + } + (n->probe).pre_handler = noisy_pre_handler; + (n->probe).post_handler = noisy_post_handler; + (n->probe).fault_handler = noisy_fault_handler; + + { +/* +* I am attempting to verify that the kernel-mode address +* passed in is valid, but I suspect this is not the +* right way of doing this. +*
drivers/char/noisy.c line 192
Quoted 10 lines
drivers/char/noisy.c +10
-0+* Although, it appears to work. I can attempt to setup +* a probe for 0xfffffff0, and the write operation fails with +* -EINVAL. +*/ +unsigned short eip; +if (__get_user (eip, (unsigned short *)(n->probe).addr)) { + ret = -EINVAL ; + goto out; + } + }
This is not quite right. I will reply to your other post on how to improve this check.
drivers/char/noisy.c line 198
Quoted 6 lines
drivers/char/noisy.c +6
-0+ +if (register_kprobe (&(n->probe))) { +printk (KERN_CRIT "Unable to register probe at %p\n" , + (n->probe).addr); +if (n) +kfree (n);
kfree(NULL) is valid. No need for if (n). Same comment for kfree(tmp);