lkdtm.c 18.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/*
 * Kprobe module for testing crash dumps
 *
 * 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
 *
 * Author: Ankita Garg <ankita@in.ibm.com>
 *
 * This module induces system failures at predefined crashpoints to
 * evaluate the reliability of crash dumps obtained using different dumping
 * solutions.
 *
 * It is adapted from the Linux Kernel Dump Test Tool by
 * Fernando Luis Vazquez Cao <http://lkdtt.sourceforge.net>
 *
29
 * Debugfs support added by Simon Kagstrom <simon.kagstrom@netinsight.net>
30
 *
31
 * See Documentation/fault-injection/provoke-crashes.txt for instructions
32 33 34
 */

#include <linux/kernel.h>
35
#include <linux/fs.h>
36
#include <linux/module.h>
37
#include <linux/buffer_head.h>
38
#include <linux/kprobes.h>
39
#include <linux/list.h>
40 41
#include <linux/init.h>
#include <linux/interrupt.h>
42
#include <linux/hrtimer.h>
43
#include <linux/slab.h>
44
#include <scsi/scsi_cmnd.h>
45
#include <linux/debugfs.h>
K
Kees Cook 已提交
46
#include <linux/vmalloc.h>
47
#include <linux/mman.h>
48 49 50 51 52

#ifdef CONFIG_IDE
#include <linux/ide.h>
#endif

53 54 55 56 57 58 59 60 61 62 63 64
/*
 * Make sure our attempts to over run the kernel stack doesn't trigger
 * a compiler warning when CONFIG_FRAME_WARN is set. Then make sure we
 * recurse past the end of THREAD_SIZE by default.
 */
#if defined(CONFIG_FRAME_WARN) && (CONFIG_FRAME_WARN > 0)
#define REC_STACK_SIZE (CONFIG_FRAME_WARN / 2)
#else
#define REC_STACK_SIZE (THREAD_SIZE / 8)
#endif
#define REC_NUM_DEFAULT ((THREAD_SIZE / REC_STACK_SIZE) * 2)

65
#define DEFAULT_COUNT 10
K
Kees Cook 已提交
66
#define EXEC_SIZE 64
67 68

enum cname {
N
Namhyung Kim 已提交
69 70 71 72 73 74 75 76 77 78
	CN_INVALID,
	CN_INT_HARDWARE_ENTRY,
	CN_INT_HW_IRQ_EN,
	CN_INT_TASKLET_ENTRY,
	CN_FS_DEVRW,
	CN_MEM_SWAPOUT,
	CN_TIMERADD,
	CN_SCSI_DISPATCH_CMD,
	CN_IDE_CORE_CP,
	CN_DIRECT,
79 80 81
};

enum ctype {
N
Namhyung Kim 已提交
82 83 84
	CT_NONE,
	CT_PANIC,
	CT_BUG,
K
Kees Cook 已提交
85
	CT_WARNING,
N
Namhyung Kim 已提交
86 87 88 89 90 91 92 93 94
	CT_EXCEPTION,
	CT_LOOP,
	CT_OVERFLOW,
	CT_CORRUPT_STACK,
	CT_UNALIGNED_LOAD_STORE_WRITE,
	CT_OVERWRITE_ALLOCATION,
	CT_WRITE_AFTER_FREE,
	CT_SOFTLOCKUP,
	CT_HARDLOCKUP,
K
Kees Cook 已提交
95
	CT_SPINLOCKUP,
N
Namhyung Kim 已提交
96
	CT_HUNG_TASK,
K
Kees Cook 已提交
97 98 99 100
	CT_EXEC_DATA,
	CT_EXEC_STACK,
	CT_EXEC_KMALLOC,
	CT_EXEC_VMALLOC,
101 102 103
	CT_EXEC_USERSPACE,
	CT_ACCESS_USERSPACE,
	CT_WRITE_RO,
104 105 106 107 108 109 110 111 112 113
};

static char* cp_name[] = {
	"INT_HARDWARE_ENTRY",
	"INT_HW_IRQ_EN",
	"INT_TASKLET_ENTRY",
	"FS_DEVRW",
	"MEM_SWAPOUT",
	"TIMERADD",
	"SCSI_DISPATCH_CMD",
114 115
	"IDE_CORE_CP",
	"DIRECT",
116 117 118 119 120
};

static char* cp_type[] = {
	"PANIC",
	"BUG",
K
Kees Cook 已提交
121
	"WARNING",
122 123
	"EXCEPTION",
	"LOOP",
124 125 126 127 128
	"OVERFLOW",
	"CORRUPT_STACK",
	"UNALIGNED_LOAD_STORE_WRITE",
	"OVERWRITE_ALLOCATION",
	"WRITE_AFTER_FREE",
129 130
	"SOFTLOCKUP",
	"HARDLOCKUP",
K
Kees Cook 已提交
131
	"SPINLOCKUP",
132
	"HUNG_TASK",
K
Kees Cook 已提交
133 134 135 136
	"EXEC_DATA",
	"EXEC_STACK",
	"EXEC_KMALLOC",
	"EXEC_VMALLOC",
137 138 139
	"EXEC_USERSPACE",
	"ACCESS_USERSPACE",
	"WRITE_RO",
140 141 142 143 144 145 146
};

static struct jprobe lkdtm;

static int lkdtm_parse_commandline(void);
static void lkdtm_handler(void);

147 148
static char* cpoint_name;
static char* cpoint_type;
149 150 151
static int cpoint_count = DEFAULT_COUNT;
static int recur_count = REC_NUM_DEFAULT;

N
Namhyung Kim 已提交
152 153
static enum cname cpoint = CN_INVALID;
static enum ctype cptype = CT_NONE;
154
static int count = DEFAULT_COUNT;
155
static DEFINE_SPINLOCK(count_lock);
K
Kees Cook 已提交
156
static DEFINE_SPINLOCK(lock_me_up);
157

K
Kees Cook 已提交
158 159
static u8 data_area[EXEC_SIZE];

160 161
static const unsigned long rodata = 0xAA55AA55;

162
module_param(recur_count, int, 0644);
163
MODULE_PARM_DESC(recur_count, " Recursion level for the stack overflow test");
164
module_param(cpoint_name, charp, 0444);
165
MODULE_PARM_DESC(cpoint_name, " Crash Point, where kernel is to be crashed");
166
module_param(cpoint_type, charp, 0444);
167 168 169 170 171
MODULE_PARM_DESC(cpoint_type, " Crash Point Type, action to be taken on "\
				"hitting the crash point");
module_param(cpoint_count, int, 0644);
MODULE_PARM_DESC(cpoint_count, " Crash Point Count, number of times the "\
				"crash point is to be hit to trigger action");
172

A
Adrian Bunk 已提交
173
static unsigned int jp_do_irq(unsigned int irq)
174 175 176 177 178 179
{
	lkdtm_handler();
	jprobe_return();
	return 0;
}

A
Adrian Bunk 已提交
180 181
static irqreturn_t jp_handle_irq_event(unsigned int irq,
				       struct irqaction *action)
182 183 184 185 186 187
{
	lkdtm_handler();
	jprobe_return();
	return 0;
}

A
Adrian Bunk 已提交
188
static void jp_tasklet_action(struct softirq_action *a)
189 190 191 192 193
{
	lkdtm_handler();
	jprobe_return();
}

A
Adrian Bunk 已提交
194
static void jp_ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
195 196 197 198 199 200 201
{
	lkdtm_handler();
	jprobe_return();
}

struct scan_control;

A
Adrian Bunk 已提交
202 203 204
static unsigned long jp_shrink_inactive_list(unsigned long max_scan,
					     struct zone *zone,
					     struct scan_control *sc)
205 206 207 208 209 210
{
	lkdtm_handler();
	jprobe_return();
	return 0;
}

A
Adrian Bunk 已提交
211 212
static int jp_hrtimer_start(struct hrtimer *timer, ktime_t tim,
			    const enum hrtimer_mode mode)
213 214 215 216 217 218
{
	lkdtm_handler();
	jprobe_return();
	return 0;
}

A
Adrian Bunk 已提交
219
static int jp_scsi_dispatch_cmd(struct scsi_cmnd *cmd)
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
{
	lkdtm_handler();
	jprobe_return();
	return 0;
}

#ifdef CONFIG_IDE
int jp_generic_ide_ioctl(ide_drive_t *drive, struct file *file,
			struct block_device *bdev, unsigned int cmd,
			unsigned long arg)
{
	lkdtm_handler();
	jprobe_return();
	return 0;
}
#endif

237 238 239 240 241 242 243 244 245 246
/* Return the crashpoint number or NONE if the name is invalid */
static enum ctype parse_cp_type(const char *what, size_t count)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(cp_type); i++) {
		if (!strcmp(what, cp_type[i]))
			return i + 1;
	}

N
Namhyung Kim 已提交
247
	return CT_NONE;
248 249 250 251
}

static const char *cp_type_to_str(enum ctype type)
{
N
Namhyung Kim 已提交
252
	if (type == CT_NONE || type < 0 || type > ARRAY_SIZE(cp_type))
253 254 255 256 257 258 259
		return "None";

	return cp_type[type - 1];
}

static const char *cp_name_to_str(enum cname name)
{
N
Namhyung Kim 已提交
260
	if (name == CN_INVALID || name < 0 || name > ARRAY_SIZE(cp_name))
261 262 263 264 265 266
		return "INVALID";

	return cp_name[name - 1];
}


267 268 269
static int lkdtm_parse_commandline(void)
{
	int i;
270
	unsigned long flags;
271

272
	if (cpoint_count < 1 || recur_count < 1)
273 274
		return -EINVAL;

275
	spin_lock_irqsave(&count_lock, flags);
276
	count = cpoint_count;
277
	spin_unlock_irqrestore(&count_lock, flags);
278 279 280 281 282 283 284 285 286 287

	/* No special parameters */
	if (!cpoint_type && !cpoint_name)
		return 0;

	/* Neither or both of these need to be set */
	if (!cpoint_type || !cpoint_name)
		return -EINVAL;

	cptype = parse_cp_type(cpoint_type, strlen(cpoint_type));
N
Namhyung Kim 已提交
288
	if (cptype == CT_NONE)
289 290 291
		return -EINVAL;

	for (i = 0; i < ARRAY_SIZE(cp_name); i++) {
292 293
		if (!strcmp(cpoint_name, cp_name[i])) {
			cpoint = i + 1;
294
			return 0;
295 296 297
		}
	}

298 299
	/* Could not find a valid crash point */
	return -EINVAL;
300 301
}

302
static int recursive_loop(int remaining)
303
{
304
	char buf[REC_STACK_SIZE];
305

306 307 308
	/* Make sure compiler does not optimize this away. */
	memset(buf, (remaining & 0xff) | 0x1, REC_STACK_SIZE);
	if (!remaining)
309 310
		return 0;
	else
311
		return recursive_loop(remaining - 1);
312 313
}

K
Kees Cook 已提交
314 315 316 317 318
static void do_nothing(void)
{
	return;
}

K
Kees Cook 已提交
319 320 321 322 323 324 325 326
static noinline void corrupt_stack(void)
{
	/* Use default char array length that triggers stack protection. */
	char data[8];

	memset((void *)data, 0, 64);
}

K
Kees Cook 已提交
327 328 329 330 331 332 333 334
static void execute_location(void *dst)
{
	void (*func)(void) = dst;

	memcpy(dst, do_nothing, EXEC_SIZE);
	func();
}

335 336 337 338 339 340 341 342 343
static void execute_user_location(void *dst)
{
	void (*func)(void) = dst;

	if (copy_to_user(dst, do_nothing, EXEC_SIZE))
		return;
	func();
}

344
static void lkdtm_do_action(enum ctype which)
345
{
346
	switch (which) {
N
Namhyung Kim 已提交
347
	case CT_PANIC:
348 349
		panic("dumptest");
		break;
N
Namhyung Kim 已提交
350
	case CT_BUG:
351 352
		BUG();
		break;
K
Kees Cook 已提交
353 354 355
	case CT_WARNING:
		WARN_ON(1);
		break;
N
Namhyung Kim 已提交
356
	case CT_EXCEPTION:
357 358
		*((int *) 0) = 0;
		break;
N
Namhyung Kim 已提交
359
	case CT_LOOP:
360 361 362
		for (;;)
			;
		break;
N
Namhyung Kim 已提交
363
	case CT_OVERFLOW:
364
		(void) recursive_loop(recur_count);
365
		break;
K
Kees Cook 已提交
366 367
	case CT_CORRUPT_STACK:
		corrupt_stack();
368
		break;
N
Namhyung Kim 已提交
369
	case CT_UNALIGNED_LOAD_STORE_WRITE: {
370 371 372 373 374 375 376 377 378 379 380
		static u8 data[5] __attribute__((aligned(4))) = {1, 2,
				3, 4, 5};
		u32 *p;
		u32 val = 0x12345678;

		p = (u32 *)(data + 1);
		if (*p == 0)
			val = 0x87654321;
		*p = val;
		 break;
	}
N
Namhyung Kim 已提交
381
	case CT_OVERWRITE_ALLOCATION: {
382 383 384 385 386 387 388
		size_t len = 1020;
		u32 *data = kmalloc(len, GFP_KERNEL);

		data[1024 / sizeof(u32)] = 0x12345678;
		kfree(data);
		break;
	}
N
Namhyung Kim 已提交
389
	case CT_WRITE_AFTER_FREE: {
390 391 392 393 394 395 396 397
		size_t len = 1024;
		u32 *data = kmalloc(len, GFP_KERNEL);

		kfree(data);
		schedule();
		memset(data, 0x78, len);
		break;
	}
N
Namhyung Kim 已提交
398
	case CT_SOFTLOCKUP:
399 400 401 402
		preempt_disable();
		for (;;)
			cpu_relax();
		break;
N
Namhyung Kim 已提交
403
	case CT_HARDLOCKUP:
404 405 406 407
		local_irq_disable();
		for (;;)
			cpu_relax();
		break;
K
Kees Cook 已提交
408 409 410 411
	case CT_SPINLOCKUP:
		/* Must be called twice to trigger. */
		spin_lock(&lock_me_up);
		break;
N
Namhyung Kim 已提交
412
	case CT_HUNG_TASK:
413 414 415
		set_current_state(TASK_UNINTERRUPTIBLE);
		schedule();
		break;
K
Kees Cook 已提交
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
	case CT_EXEC_DATA:
		execute_location(data_area);
		break;
	case CT_EXEC_STACK: {
		u8 stack_area[EXEC_SIZE];
		execute_location(stack_area);
		break;
	}
	case CT_EXEC_KMALLOC: {
		u32 *kmalloc_area = kmalloc(EXEC_SIZE, GFP_KERNEL);
		execute_location(kmalloc_area);
		kfree(kmalloc_area);
		break;
	}
	case CT_EXEC_VMALLOC: {
		u32 *vmalloc_area = vmalloc(EXEC_SIZE);
		execute_location(vmalloc_area);
		vfree(vmalloc_area);
		break;
	}
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
	case CT_EXEC_USERSPACE: {
		unsigned long user_addr;

		user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
				    PROT_READ | PROT_WRITE | PROT_EXEC,
				    MAP_ANONYMOUS | MAP_PRIVATE, 0);
		if (user_addr >= TASK_SIZE) {
			pr_warn("Failed to allocate user memory\n");
			return;
		}
		execute_user_location((void *)user_addr);
		vm_munmap(user_addr, PAGE_SIZE);
		break;
	}
	case CT_ACCESS_USERSPACE: {
		unsigned long user_addr, tmp;
		unsigned long *ptr;

		user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
				    PROT_READ | PROT_WRITE | PROT_EXEC,
				    MAP_ANONYMOUS | MAP_PRIVATE, 0);
		if (user_addr >= TASK_SIZE) {
			pr_warn("Failed to allocate user memory\n");
			return;
		}

		ptr = (unsigned long *)user_addr;
		tmp = *ptr;
		tmp += 0xc0dec0de;
		*ptr = tmp;

		vm_munmap(user_addr, PAGE_SIZE);

		break;
	}
	case CT_WRITE_RO: {
		unsigned long *ptr;

		ptr = (unsigned long *)&rodata;
		*ptr ^= 0xabcd1234;

		break;
	}
N
Namhyung Kim 已提交
479
	case CT_NONE:
480 481 482 483 484 485 486 487
	default:
		break;
	}

}

static void lkdtm_handler(void)
{
488
	unsigned long flags;
489
	bool do_it = false;
490 491

	spin_lock_irqsave(&count_lock, flags);
492 493 494
	count--;
	printk(KERN_INFO "lkdtm: Crash point %s of type %s hit, trigger in %d rounds\n",
			cp_name_to_str(cpoint), cp_type_to_str(cptype), count);
495 496

	if (count == 0) {
497
		do_it = true;
498 499
		count = cpoint_count;
	}
500
	spin_unlock_irqrestore(&count_lock, flags);
501 502 503

	if (do_it)
		lkdtm_do_action(cptype);
504 505
}

506
static int lkdtm_register_cpoint(enum cname which)
507 508 509
{
	int ret;

N
Namhyung Kim 已提交
510
	cpoint = CN_INVALID;
511 512
	if (lkdtm.entry != NULL)
		unregister_jprobe(&lkdtm);
513

514
	switch (which) {
N
Namhyung Kim 已提交
515
	case CN_DIRECT:
516 517
		lkdtm_do_action(cptype);
		return 0;
N
Namhyung Kim 已提交
518
	case CN_INT_HARDWARE_ENTRY:
M
M. Mohan Kumar 已提交
519
		lkdtm.kp.symbol_name = "do_IRQ";
520 521
		lkdtm.entry = (kprobe_opcode_t*) jp_do_irq;
		break;
N
Namhyung Kim 已提交
522
	case CN_INT_HW_IRQ_EN:
523 524 525
		lkdtm.kp.symbol_name = "handle_IRQ_event";
		lkdtm.entry = (kprobe_opcode_t*) jp_handle_irq_event;
		break;
N
Namhyung Kim 已提交
526
	case CN_INT_TASKLET_ENTRY:
527 528 529
		lkdtm.kp.symbol_name = "tasklet_action";
		lkdtm.entry = (kprobe_opcode_t*) jp_tasklet_action;
		break;
N
Namhyung Kim 已提交
530
	case CN_FS_DEVRW:
531 532 533
		lkdtm.kp.symbol_name = "ll_rw_block";
		lkdtm.entry = (kprobe_opcode_t*) jp_ll_rw_block;
		break;
N
Namhyung Kim 已提交
534
	case CN_MEM_SWAPOUT:
535 536
		lkdtm.kp.symbol_name = "shrink_inactive_list";
		lkdtm.entry = (kprobe_opcode_t*) jp_shrink_inactive_list;
537
		break;
N
Namhyung Kim 已提交
538
	case CN_TIMERADD:
539 540 541
		lkdtm.kp.symbol_name = "hrtimer_start";
		lkdtm.entry = (kprobe_opcode_t*) jp_hrtimer_start;
		break;
N
Namhyung Kim 已提交
542
	case CN_SCSI_DISPATCH_CMD:
543 544 545
		lkdtm.kp.symbol_name = "scsi_dispatch_cmd";
		lkdtm.entry = (kprobe_opcode_t*) jp_scsi_dispatch_cmd;
		break;
N
Namhyung Kim 已提交
546
	case CN_IDE_CORE_CP:
547 548 549 550
#ifdef CONFIG_IDE
		lkdtm.kp.symbol_name = "generic_ide_ioctl";
		lkdtm.entry = (kprobe_opcode_t*) jp_generic_ide_ioctl;
#else
551 552
		printk(KERN_INFO "lkdtm: Crash point not available\n");
		return -EINVAL;
553 554 555
#endif
		break;
	default:
556 557
		printk(KERN_INFO "lkdtm: Invalid Crash Point\n");
		return -EINVAL;
558 559
	}

560
	cpoint = which;
561
	if ((ret = register_jprobe(&lkdtm)) < 0) {
562
		printk(KERN_INFO "lkdtm: Couldn't register jprobe\n");
N
Namhyung Kim 已提交
563
		cpoint = CN_INVALID;
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
	}

	return ret;
}

static ssize_t do_register_entry(enum cname which, struct file *f,
		const char __user *user_buf, size_t count, loff_t *off)
{
	char *buf;
	int err;

	if (count >= PAGE_SIZE)
		return -EINVAL;

	buf = (char *)__get_free_page(GFP_KERNEL);
	if (!buf)
		return -ENOMEM;
	if (copy_from_user(buf, user_buf, count)) {
		free_page((unsigned long) buf);
		return -EFAULT;
	}
	/* NULL-terminate and remove enter */
	buf[count] = '\0';
	strim(buf);

	cptype = parse_cp_type(buf, count);
	free_page((unsigned long) buf);

N
Namhyung Kim 已提交
592
	if (cptype == CT_NONE)
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
		return -EINVAL;

	err = lkdtm_register_cpoint(which);
	if (err < 0)
		return err;

	*off += count;

	return count;
}

/* Generic read callback that just prints out the available crash types */
static ssize_t lkdtm_debugfs_read(struct file *f, char __user *user_buf,
		size_t count, loff_t *off)
{
	char *buf;
	int i, n, out;

	buf = (char *)__get_free_page(GFP_KERNEL);
612 613
	if (buf == NULL)
		return -ENOMEM;
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635

	n = snprintf(buf, PAGE_SIZE, "Available crash types:\n");
	for (i = 0; i < ARRAY_SIZE(cp_type); i++)
		n += snprintf(buf + n, PAGE_SIZE - n, "%s\n", cp_type[i]);
	buf[n] = '\0';

	out = simple_read_from_buffer(user_buf, count, off,
				      buf, n);
	free_page((unsigned long) buf);

	return out;
}

static int lkdtm_debugfs_open(struct inode *inode, struct file *file)
{
	return 0;
}


static ssize_t int_hardware_entry(struct file *f, const char __user *buf,
		size_t count, loff_t *off)
{
N
Namhyung Kim 已提交
636
	return do_register_entry(CN_INT_HARDWARE_ENTRY, f, buf, count, off);
637 638 639 640 641
}

static ssize_t int_hw_irq_en(struct file *f, const char __user *buf,
		size_t count, loff_t *off)
{
N
Namhyung Kim 已提交
642
	return do_register_entry(CN_INT_HW_IRQ_EN, f, buf, count, off);
643 644 645 646 647
}

static ssize_t int_tasklet_entry(struct file *f, const char __user *buf,
		size_t count, loff_t *off)
{
N
Namhyung Kim 已提交
648
	return do_register_entry(CN_INT_TASKLET_ENTRY, f, buf, count, off);
649 650 651 652 653
}

static ssize_t fs_devrw_entry(struct file *f, const char __user *buf,
		size_t count, loff_t *off)
{
N
Namhyung Kim 已提交
654
	return do_register_entry(CN_FS_DEVRW, f, buf, count, off);
655 656 657 658 659
}

static ssize_t mem_swapout_entry(struct file *f, const char __user *buf,
		size_t count, loff_t *off)
{
N
Namhyung Kim 已提交
660
	return do_register_entry(CN_MEM_SWAPOUT, f, buf, count, off);
661 662 663 664 665
}

static ssize_t timeradd_entry(struct file *f, const char __user *buf,
		size_t count, loff_t *off)
{
N
Namhyung Kim 已提交
666
	return do_register_entry(CN_TIMERADD, f, buf, count, off);
667 668 669 670 671
}

static ssize_t scsi_dispatch_cmd_entry(struct file *f,
		const char __user *buf, size_t count, loff_t *off)
{
N
Namhyung Kim 已提交
672
	return do_register_entry(CN_SCSI_DISPATCH_CMD, f, buf, count, off);
673 674 675 676 677
}

static ssize_t ide_core_cp_entry(struct file *f, const char __user *buf,
		size_t count, loff_t *off)
{
N
Namhyung Kim 已提交
678
	return do_register_entry(CN_IDE_CORE_CP, f, buf, count, off);
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
}

/* Special entry to just crash directly. Available without KPROBEs */
static ssize_t direct_entry(struct file *f, const char __user *user_buf,
		size_t count, loff_t *off)
{
	enum ctype type;
	char *buf;

	if (count >= PAGE_SIZE)
		return -EINVAL;
	if (count < 1)
		return -EINVAL;

	buf = (char *)__get_free_page(GFP_KERNEL);
	if (!buf)
		return -ENOMEM;
	if (copy_from_user(buf, user_buf, count)) {
		free_page((unsigned long) buf);
		return -EFAULT;
	}
	/* NULL-terminate and remove enter */
	buf[count] = '\0';
	strim(buf);

	type = parse_cp_type(buf, count);
	free_page((unsigned long) buf);
N
Namhyung Kim 已提交
706
	if (type == CT_NONE)
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
		return -EINVAL;

	printk(KERN_INFO "lkdtm: Performing direct entry %s\n",
			cp_type_to_str(type));
	lkdtm_do_action(type);
	*off += count;

	return count;
}

struct crash_entry {
	const char *name;
	const struct file_operations fops;
};

static const struct crash_entry crash_entries[] = {
	{"DIRECT", {.read = lkdtm_debugfs_read,
724
			.llseek = generic_file_llseek,
725 726 727
			.open = lkdtm_debugfs_open,
			.write = direct_entry} },
	{"INT_HARDWARE_ENTRY", {.read = lkdtm_debugfs_read,
728
			.llseek = generic_file_llseek,
729 730 731
			.open = lkdtm_debugfs_open,
			.write = int_hardware_entry} },
	{"INT_HW_IRQ_EN", {.read = lkdtm_debugfs_read,
732
			.llseek = generic_file_llseek,
733 734 735
			.open = lkdtm_debugfs_open,
			.write = int_hw_irq_en} },
	{"INT_TASKLET_ENTRY", {.read = lkdtm_debugfs_read,
736
			.llseek = generic_file_llseek,
737 738 739
			.open = lkdtm_debugfs_open,
			.write = int_tasklet_entry} },
	{"FS_DEVRW", {.read = lkdtm_debugfs_read,
740
			.llseek = generic_file_llseek,
741 742 743
			.open = lkdtm_debugfs_open,
			.write = fs_devrw_entry} },
	{"MEM_SWAPOUT", {.read = lkdtm_debugfs_read,
744
			.llseek = generic_file_llseek,
745 746 747
			.open = lkdtm_debugfs_open,
			.write = mem_swapout_entry} },
	{"TIMERADD", {.read = lkdtm_debugfs_read,
748
			.llseek = generic_file_llseek,
749 750 751
			.open = lkdtm_debugfs_open,
			.write = timeradd_entry} },
	{"SCSI_DISPATCH_CMD", {.read = lkdtm_debugfs_read,
752
			.llseek = generic_file_llseek,
753 754 755
			.open = lkdtm_debugfs_open,
			.write = scsi_dispatch_cmd_entry} },
	{"IDE_CORE_CP",	{.read = lkdtm_debugfs_read,
756
			.llseek = generic_file_llseek,
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797
			.open = lkdtm_debugfs_open,
			.write = ide_core_cp_entry} },
};

static struct dentry *lkdtm_debugfs_root;

static int __init lkdtm_module_init(void)
{
	int ret = -EINVAL;
	int n_debugfs_entries = 1; /* Assume only the direct entry */
	int i;

	/* Register debugfs interface */
	lkdtm_debugfs_root = debugfs_create_dir("provoke-crash", NULL);
	if (!lkdtm_debugfs_root) {
		printk(KERN_ERR "lkdtm: creating root dir failed\n");
		return -ENODEV;
	}

#ifdef CONFIG_KPROBES
	n_debugfs_entries = ARRAY_SIZE(crash_entries);
#endif

	for (i = 0; i < n_debugfs_entries; i++) {
		const struct crash_entry *cur = &crash_entries[i];
		struct dentry *de;

		de = debugfs_create_file(cur->name, 0644, lkdtm_debugfs_root,
				NULL, &cur->fops);
		if (de == NULL) {
			printk(KERN_ERR "lkdtm: could not create %s\n",
					cur->name);
			goto out_err;
		}
	}

	if (lkdtm_parse_commandline() == -EINVAL) {
		printk(KERN_INFO "lkdtm: Invalid command\n");
		goto out_err;
	}

N
Namhyung Kim 已提交
798
	if (cpoint != CN_INVALID && cptype != CT_NONE) {
799 800 801 802 803 804 805 806 807 808
		ret = lkdtm_register_cpoint(cpoint);
		if (ret < 0) {
			printk(KERN_INFO "lkdtm: Invalid crash point %d\n",
					cpoint);
			goto out_err;
		}
		printk(KERN_INFO "lkdtm: Crash point %s of type %s registered\n",
				cpoint_name, cpoint_type);
	} else {
		printk(KERN_INFO "lkdtm: No crash points registered, enable through debugfs\n");
809 810 811
	}

	return 0;
812 813 814 815

out_err:
	debugfs_remove_recursive(lkdtm_debugfs_root);
	return ret;
816 817
}

A
Adrian Bunk 已提交
818
static void __exit lkdtm_module_exit(void)
819
{
820 821 822 823
	debugfs_remove_recursive(lkdtm_debugfs_root);

	unregister_jprobe(&lkdtm);
	printk(KERN_INFO "lkdtm: Crash point unregistered\n");
824 825 826 827 828 829
}

module_init(lkdtm_module_init);
module_exit(lkdtm_module_exit);

MODULE_LICENSE("GPL");