| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Linux Kernel Dump Test Module for testing kernel crashes conditions: |
| * induces system failures at predefined crashpoints and under predefined |
| * operational conditions in order to evaluate the reliability of kernel |
| * sanity checking and crash dumps obtained using different dumping |
| * solutions. |
| * |
| * Copyright (C) IBM Corporation, 2006 |
| * |
| * Author: Ankita Garg <ankita@in.ibm.com> |
| * |
| * It is adapted from the Linux Kernel Dump Test Tool by |
| * Fernando Luis Vazquez Cao <http://lkdtt.sourceforge.net> |
| * |
| * Debugfs support added by Simon Kagstrom <simon.kagstrom@netinsight.net> |
| * |
| * See Documentation/fault-injection/provoke-crashes.rst for instructions |
| */ |
| #include "lkdtm.h" |
| #include <linux/fs.h> |
| #include <linux/module.h> |
| #include <linux/buffer_head.h> |
| #include <linux/kprobes.h> |
| #include <linux/list.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/debugfs.h> |
| #include <linux/utsname.h> |
| |
| #define DEFAULT_COUNT 10 |
| |
| static int lkdtm_debugfs_open(struct inode *inode, struct file *file); |
| static ssize_t lkdtm_debugfs_read(struct file *f, char __user *user_buf, |
| size_t count, loff_t *off); |
| static ssize_t direct_entry(struct file *f, const char __user *user_buf, |
| size_t count, loff_t *off); |
| |
| #ifdef CONFIG_KPROBES |
| static int lkdtm_kprobe_handler(struct kprobe *kp, struct pt_regs *regs); |
| static ssize_t lkdtm_debugfs_entry(struct file *f, |
| const char __user *user_buf, |
| size_t count, loff_t *off); |
| # define CRASHPOINT_KPROBE(_symbol) \ |
| .kprobe = { \ |
| .symbol_name = (_symbol), \ |
| .pre_handler = lkdtm_kprobe_handler, \ |
| }, |
| # define CRASHPOINT_WRITE(_symbol) \ |
| (_symbol) ? lkdtm_debugfs_entry : direct_entry |
| #else |
| # define CRASHPOINT_KPROBE(_symbol) |
| # define CRASHPOINT_WRITE(_symbol) direct_entry |
| #endif |
| |
| /* Crash points */ |
| struct crashpoint { |
| const char *name; |
| const struct file_operations fops; |
| struct kprobe kprobe; |
| }; |
| |
| #define CRASHPOINT(_name, _symbol) \ |
| { \ |
| .name = _name, \ |
| .fops = { \ |
| .read = lkdtm_debugfs_read, \ |
| .llseek = generic_file_llseek, \ |
| .open = lkdtm_debugfs_open, \ |
| .write = CRASHPOINT_WRITE(_symbol) \ |
| }, \ |
| CRASHPOINT_KPROBE(_symbol) \ |
| } |
| |
| /* Define the possible places where we can trigger a crash point. */ |
| static struct crashpoint crashpoints[] = { |
| CRASHPOINT("DIRECT", NULL), |
| #ifdef CONFIG_KPROBES |
| CRASHPOINT("INT_HARDWARE_ENTRY", "do_IRQ"), |
| CRASHPOINT("INT_HW_IRQ_EN", "handle_irq_event"), |
| CRASHPOINT("INT_TASKLET_ENTRY", "tasklet_action"), |
| CRASHPOINT("FS_DEVRW", "ll_rw_block"), |
| CRASHPOINT("MEM_SWAPOUT", "shrink_inactive_list"), |
| CRASHPOINT("TIMERADD", "hrtimer_start"), |
| CRASHPOINT("SCSI_QUEUE_RQ", "scsi_queue_rq"), |
| #endif |
| }; |
| |
| /* List of possible types for crashes that can be triggered. */ |
| static const struct crashtype_category *crashtype_categories[] = { |
| &bugs_crashtypes, |
| &heap_crashtypes, |
| &perms_crashtypes, |
| &refcount_crashtypes, |
| &usercopy_crashtypes, |
| &stackleak_crashtypes, |
| &cfi_crashtypes, |
| &fortify_crashtypes, |
| #ifdef CONFIG_PPC_64S_HASH_MMU |
| &powerpc_crashtypes, |
| #endif |
| }; |
| |
| /* Global kprobe entry and crashtype. */ |
| static struct kprobe *lkdtm_kprobe; |
| static struct crashpoint *lkdtm_crashpoint; |
| static const struct crashtype *lkdtm_crashtype; |
| |
| /* Module parameters */ |
| static int recur_count = -1; |
| module_param(recur_count, int, 0644); |
| MODULE_PARM_DESC(recur_count, " Recursion level for the stack overflow test"); |
| |
| static char* cpoint_name; |
| module_param(cpoint_name, charp, 0444); |
| MODULE_PARM_DESC(cpoint_name, " Crash Point, where kernel is to be crashed"); |
| |
| static char* cpoint_type; |
| module_param(cpoint_type, charp, 0444); |
| MODULE_PARM_DESC(cpoint_type, " Crash Point Type, action to be taken on "\ |
| "hitting the crash point"); |
| |
| static int cpoint_count = DEFAULT_COUNT; |
| 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"); |
| |
| /* |
| * For test debug reporting when CI systems provide terse summaries. |
| * TODO: Remove this once reasonable reporting exists in most CI systems: |
| * https://lore.kernel.org/lkml/CAHk-=wiFvfkoFixTapvvyPMN9pq5G-+Dys2eSyBa1vzDGAO5+A@mail.gmail.com |
| */ |
| char *lkdtm_kernel_info; |
| |
| /* Return the crashtype number or NULL if the name is invalid */ |
| static const struct crashtype *find_crashtype(const char *name) |
| { |
| int cat, idx; |
| |
| for (cat = 0; cat < ARRAY_SIZE(crashtype_categories); cat++) { |
| for (idx = 0; idx < crashtype_categories[cat]->len; idx++) { |
| struct crashtype *crashtype; |
| |
| crashtype = &crashtype_categories[cat]->crashtypes[idx]; |
| if (!strcmp(name, crashtype->name)) |
| return crashtype; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * This is forced noinline just so it distinctly shows up in the stackdump |
| * which makes validation of expected lkdtm crashes easier. |
| */ |
| static noinline void lkdtm_do_action(const struct crashtype *crashtype) |
| { |
| if (WARN_ON(!crashtype || !crashtype->func)) |
| return; |
| crashtype->func(); |
| } |
| |
| static int lkdtm_register_cpoint(struct crashpoint *crashpoint, |
| const struct crashtype *crashtype) |
| { |
| int ret; |
| |
| /* If this doesn't have a symbol, just call immediately. */ |
| if (!crashpoint->kprobe.symbol_name) { |
| lkdtm_do_action(crashtype); |
| return 0; |
| } |
| |
| if (lkdtm_kprobe != NULL) |
| unregister_kprobe(lkdtm_kprobe); |
| |
| lkdtm_crashpoint = crashpoint; |
| lkdtm_crashtype = crashtype; |
| lkdtm_kprobe = &crashpoint->kprobe; |
| ret = register_kprobe(lkdtm_kprobe); |
| if (ret < 0) { |
| pr_info("Couldn't register kprobe %s\n", |
| crashpoint->kprobe.symbol_name); |
| lkdtm_kprobe = NULL; |
| lkdtm_crashpoint = NULL; |
| lkdtm_crashtype = NULL; |
| } |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_KPROBES |
| /* Global crash counter and spinlock. */ |
| static int crash_count = DEFAULT_COUNT; |
| static DEFINE_SPINLOCK(crash_count_lock); |
| |
| /* Called by kprobe entry points. */ |
| static int lkdtm_kprobe_handler(struct kprobe *kp, struct pt_regs *regs) |
| { |
| unsigned long flags; |
| bool do_it = false; |
| |
| if (WARN_ON(!lkdtm_crashpoint || !lkdtm_crashtype)) |
| return 0; |
| |
| spin_lock_irqsave(&crash_count_lock, flags); |
| crash_count--; |
| pr_info("Crash point %s of type %s hit, trigger in %d rounds\n", |
| lkdtm_crashpoint->name, lkdtm_crashtype->name, crash_count); |
| |
| if (crash_count == 0) { |
| do_it = true; |
| crash_count = cpoint_count; |
| } |
| spin_unlock_irqrestore(&crash_count_lock, flags); |
| |
| if (do_it) |
| lkdtm_do_action(lkdtm_crashtype); |
| |
| return 0; |
| } |
| |
| static ssize_t lkdtm_debugfs_entry(struct file *f, |
| const char __user *user_buf, |
| size_t count, loff_t *off) |
| { |
| struct crashpoint *crashpoint = file_inode(f)->i_private; |
| const struct crashtype *crashtype = NULL; |
| 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); |
| |
| crashtype = find_crashtype(buf); |
| free_page((unsigned long)buf); |
| |
| if (!crashtype) |
| return -EINVAL; |
| |
| err = lkdtm_register_cpoint(crashpoint, crashtype); |
| if (err < 0) |
| return err; |
| |
| *off += count; |
| |
| return count; |
| } |
| #endif |
| |
| /* 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) |
| { |
| int n, cat, idx; |
| ssize_t out; |
| char *buf; |
| |
| buf = (char *)__get_free_page(GFP_KERNEL); |
| if (buf == NULL) |
| return -ENOMEM; |
| |
| n = scnprintf(buf, PAGE_SIZE, "Available crash types:\n"); |
| |
| for (cat = 0; cat < ARRAY_SIZE(crashtype_categories); cat++) { |
| for (idx = 0; idx < crashtype_categories[cat]->len; idx++) { |
| struct crashtype *crashtype; |
| |
| crashtype = &crashtype_categories[cat]->crashtypes[idx]; |
| n += scnprintf(buf + n, PAGE_SIZE - n, "%s\n", |
| crashtype->name); |
| } |
| } |
| 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; |
| } |
| |
| /* 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) |
| { |
| const struct crashtype *crashtype; |
| 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); |
| |
| crashtype = find_crashtype(buf); |
| free_page((unsigned long) buf); |
| if (!crashtype) |
| return -EINVAL; |
| |
| pr_info("Performing direct entry %s\n", crashtype->name); |
| lkdtm_do_action(crashtype); |
| *off += count; |
| |
| return count; |
| } |
| |
| #ifndef MODULE |
| /* |
| * To avoid needing to export parse_args(), just don't use this code |
| * when LKDTM is built as a module. |
| */ |
| struct check_cmdline_args { |
| const char *param; |
| int value; |
| }; |
| |
| static int lkdtm_parse_one(char *param, char *val, |
| const char *unused, void *arg) |
| { |
| struct check_cmdline_args *args = arg; |
| |
| /* short circuit if we already found a value. */ |
| if (args->value != -ESRCH) |
| return 0; |
| if (strncmp(param, args->param, strlen(args->param)) == 0) { |
| bool bool_result; |
| int ret; |
| |
| ret = kstrtobool(val, &bool_result); |
| if (ret == 0) |
| args->value = bool_result; |
| } |
| return 0; |
| } |
| |
| int lkdtm_check_bool_cmdline(const char *param) |
| { |
| char *command_line; |
| struct check_cmdline_args args = { |
| .param = param, |
| .value = -ESRCH, |
| }; |
| |
| command_line = kstrdup(saved_command_line, GFP_KERNEL); |
| if (!command_line) |
| return -ENOMEM; |
| |
| parse_args("Setting sysctl args", command_line, |
| NULL, 0, -1, -1, &args, lkdtm_parse_one); |
| |
| kfree(command_line); |
| |
| return args.value; |
| } |
| #endif |
| |
| static struct dentry *lkdtm_debugfs_root; |
| |
| static int __init lkdtm_module_init(void) |
| { |
| struct crashpoint *crashpoint = NULL; |
| const struct crashtype *crashtype = NULL; |
| int ret; |
| int i; |
| |
| /* Neither or both of these need to be set */ |
| if ((cpoint_type || cpoint_name) && !(cpoint_type && cpoint_name)) { |
| pr_err("Need both cpoint_type and cpoint_name or neither\n"); |
| return -EINVAL; |
| } |
| |
| if (cpoint_type) { |
| crashtype = find_crashtype(cpoint_type); |
| if (!crashtype) { |
| pr_err("Unknown crashtype '%s'\n", cpoint_type); |
| return -EINVAL; |
| } |
| } |
| |
| if (cpoint_name) { |
| for (i = 0; i < ARRAY_SIZE(crashpoints); i++) { |
| if (!strcmp(cpoint_name, crashpoints[i].name)) |
| crashpoint = &crashpoints[i]; |
| } |
| |
| /* Refuse unknown crashpoints. */ |
| if (!crashpoint) { |
| pr_err("Invalid crashpoint %s\n", cpoint_name); |
| return -EINVAL; |
| } |
| } |
| |
| #ifdef CONFIG_KPROBES |
| /* Set crash count. */ |
| crash_count = cpoint_count; |
| #endif |
| |
| /* Common initialization. */ |
| lkdtm_kernel_info = kasprintf(GFP_KERNEL, "kernel (%s %s)", |
| init_uts_ns.name.release, |
| init_uts_ns.name.machine); |
| |
| /* Handle test-specific initialization. */ |
| lkdtm_bugs_init(&recur_count); |
| lkdtm_perms_init(); |
| lkdtm_usercopy_init(); |
| lkdtm_heap_init(); |
| |
| /* Register debugfs interface */ |
| lkdtm_debugfs_root = debugfs_create_dir("provoke-crash", NULL); |
| |
| /* Install debugfs trigger files. */ |
| for (i = 0; i < ARRAY_SIZE(crashpoints); i++) { |
| struct crashpoint *cur = &crashpoints[i]; |
| |
| debugfs_create_file(cur->name, 0644, lkdtm_debugfs_root, cur, |
| &cur->fops); |
| } |
| |
| /* Install crashpoint if one was selected. */ |
| if (crashpoint) { |
| ret = lkdtm_register_cpoint(crashpoint, crashtype); |
| if (ret < 0) { |
| pr_info("Invalid crashpoint %s\n", crashpoint->name); |
| goto out_err; |
| } |
| pr_info("Crash point %s of type %s registered\n", |
| crashpoint->name, cpoint_type); |
| } else { |
| pr_info("No crash points registered, enable through debugfs\n"); |
| } |
| |
| return 0; |
| |
| out_err: |
| debugfs_remove_recursive(lkdtm_debugfs_root); |
| return ret; |
| } |
| |
| static void __exit lkdtm_module_exit(void) |
| { |
| debugfs_remove_recursive(lkdtm_debugfs_root); |
| |
| /* Handle test-specific clean-up. */ |
| lkdtm_heap_exit(); |
| lkdtm_usercopy_exit(); |
| |
| if (lkdtm_kprobe != NULL) |
| unregister_kprobe(lkdtm_kprobe); |
| |
| kfree(lkdtm_kernel_info); |
| |
| pr_info("Crash point unregistered\n"); |
| } |
| |
| module_init(lkdtm_module_init); |
| module_exit(lkdtm_module_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Kernel crash testing module"); |