# [2021 年暑期 Linux 核心](https://hackmd.io/@sysprog/linux2021-summer/) 第 1 週測驗題 ###### tags: `linux2021` :::info 目的: 檢驗學員對 ==[Linux 核心行程](https://hackmd.io/@sysprog/linux-process)==, ==[List Management API](https://www.kernel.org/doc/html/latest/core-api/kernel-api.html)==, ==[Linux 核心模組](https://hackmd.io/@sysprog/linux-kernel-module)== 的認知 ::: ==[作答表單](https://docs.google.com/forms/d/e/1FAIpQLSdxUat6RS15EiHqRXXoqIc-Ueq99BoCqTSCFL55lX6sQVXReA/viewform)== (僅在 Linux v5.4 測試) 考慮 `hideproc` 這個 Linux 核心模組可隱藏特定的 process ID,用法如下: (假設 `cron` 的 PID 為 644) ```shell $ sudo insmod hideproc.ko $ pidof cron $ echo "add 644" | sudo tee /dev/hideproc $ pidof cron # 無法見到 cron $ echo "del 644" | sudo tee /dev/hideproc $ pidof cron # 再次見到 cron $ sudo rmmod hideproc ``` > 你或許會好奇:為何不能採用 `sudo echo "add 644" > /dev/hideproc` 命令呢?請參見 [How do I use sudo to redirect output to a location I don't have permission to write to?](https://stackoverflow.com/questions/82256/how-do-i-use-sudo-to-redirect-output-to-a-location-i-dont-have-permission-to-wr) 以下是 `main.c` 的程式碼: (部分遮蔽) ```cpp #include <linux/cdev.h> #include <linux/ftrace.h> #include <linux/kallsyms.h> #include <linux/list.h> #include <linux/module.h> #include <linux/proc_fs.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("National Cheng Kung University, Taiwan"); enum RETURN_CODE { SUCCESS }; struct ftrace_hook { const char *name; void *func, *orig; unsigned long address; struct ftrace_ops ops; }; static int hook_resolve_addr(struct ftrace_hook *hook) { hook->address = kallsyms_lookup_name(hook->name); if (!hook->address) { printk("unresolved symbol: %s\n", hook->name); return -ENOENT; } *((unsigned long *) hook->orig) = hook->address; return 0; } static void notrace hook_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct pt_regs *regs) { struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops); if (!within_module(parent_ip, THIS_MODULE)) regs->ip = (unsigned long) hook->func; } static int hook_install(struct ftrace_hook *hook) { int err = hook_resolve_addr(hook); if (err) return err; hook->ops.func = hook_ftrace_thunk; hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION_SAFE | FTRACE_OPS_FL_IPMODIFY; err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0); if (err) { printk("ftrace_set_filter_ip() failed: %d\n", err); return err; } err = register_ftrace_function(&hook->ops); if (err) { printk("register_ftrace_function() failed: %d\n", err); ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0); return err; } return 0; } #if 0 void hook_remove(struct ftrace_hook *hook) { int err = unregister_ftrace_function(&hook->ops); if (err) printk("unregister_ftrace_function() failed: %d\n", err); err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0); if (err) printk("ftrace_set_filter_ip() failed: %d\n", err); } #endif typedef struct { pid_t id; struct list_head list_node; } pid_node_t; LIST_HEAD(hidden_proc); typedef struct pid *(*find_ge_pid_func)(int nr, struct pid_namespace *ns); static find_ge_pid_func real_find_ge_pid; static struct ftrace_hook hook; static bool is_hidden_proc(pid_t pid) { pid_node_t *proc, *tmp_proc; AAA (proc, tmp_proc, &hidden_proc, list_node) { if (proc->id == pid) return true; } return false; } static struct pid *hook_find_ge_pid(int nr, struct pid_namespace *ns) { struct pid *pid = real_find_ge_pid(nr, ns); while (pid && is_hidden_proc(pid->numbers->nr)) pid = real_find_ge_pid(pid->numbers->nr + 1, ns); return pid; } static void init_hook(void) { real_find_ge_pid = (find_ge_pid_func) kallsyms_lookup_name("find_ge_pid"); hook.name = "find_ge_pid"; hook.func = hook_find_ge_pid; hook.orig = &real_find_ge_pid; hook_install(&hook); } static int hide_process(pid_t pid) { pid_node_t *proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL); proc->id = pid; CCC; return SUCCESS; } static int unhide_process(pid_t pid) { pid_node_t *proc, *tmp_proc; BBB (proc, tmp_proc, &hidden_proc, list_node) { DDD; kfree(proc); } return SUCCESS; } #define OUTPUT_BUFFER_FORMAT "pid: %d\n" #define MAX_MESSAGE_SIZE (sizeof(OUTPUT_BUFFER_FORMAT) + 4) static int device_open(struct inode *inode, struct file *file) { return SUCCESS; } static int device_close(struct inode *inode, struct file *file) { return SUCCESS; } static ssize_t device_read(struct file *filep, char *buffer, size_t len, loff_t *offset) { pid_node_t *proc, *tmp_proc; char message[MAX_MESSAGE_SIZE]; if (*offset) return 0; list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) { memset(message, 0, MAX_MESSAGE_SIZE); sprintf(message, OUTPUT_BUFFER_FORMAT, proc->id); copy_to_user(buffer + *offset, message, strlen(message)); *offset += strlen(message); } return *offset; } static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { long pid; char *message; char add_message[] = "add", del_message[] = "del"; if (len < sizeof(add_message) - 1 && len < sizeof(del_message) - 1) return -EAGAIN; message = kmalloc(len + 1, GFP_KERNEL); memset(message, 0, len + 1); copy_from_user(message, buffer, len); if (!memcmp(message, add_message, sizeof(add_message) - 1)) { kstrtol(message + sizeof(add_message), 10, &pid); hide_process(pid); } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { kstrtol(message + sizeof(del_message), 10, &pid); unhide_process(pid); } else { kfree(message); return -EAGAIN; } *offset = len; kfree(message); return len; } static struct cdev cdev; static struct class *hideproc_class = NULL; static const struct file_operations fops = { .owner = THIS_MODULE, .open = device_open, .release = device_close, .read = device_read, .write = device_write, }; #define MINOR_VERSION 1 #define DEVICE_NAME "hideproc" static int _hideproc_init(void) { int err, dev_major; dev_t dev; printk(KERN_INFO "@ %s\n", __func__); err = alloc_chrdev_region(&dev, 0, MINOR_VERSION, DEVICE_NAME); dev_major = MAJOR(dev); hideproc_class = class_create(THIS_MODULE, DEVICE_NAME); cdev_init(&cdev, &fops); cdev_add(&cdev, MKDEV(dev_major, MINOR_VERSION), 1); device_create(hideproc_class, NULL, MKDEV(dev_major, MINOR_VERSION), NULL, DEVICE_NAME); init_hook(); return 0; } static void _hideproc_exit(void) { printk(KERN_INFO "@ %s\n", __func__); /* FIXME: ensure the release of all allocated resources */ } module_init(_hideproc_init); module_exit(_hideproc_exit); ``` 對應的 `Makefile`: ```shell MODULENAME := hideproc obj-m += $(MODULENAME).o $(MODULENAME)-y += main.o KERNELDIR ?= /lib/modules/`uname -r`/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean ``` 請補上以上程式碼,使其運作符合預期。 ==作答區== AAA = ? (應使用 `list_for_each_entry_safe` 巨集) BBB = ? (是 [list_for_each_entry](https://www.kernel.org/doc/htmldocs/kernel-api/API-list-for-each-entry.html) 的變形) CCC = ? DDD = ? :::success 延伸問題: 1. 解釋上述程式碼運作原理,包含 [ftrace](https://www.kernel.org/doc/Documentation/trace/ftrace.txt) 的使用 2. 本程式僅在 Linux v5.4 測試,若你用的核心較新,請試著找出替代方案 > 2020 年的變更 [Unexporting kallsyms_lookup_name()](https://lwn.net/Articles/813350/) > [Access to kallsyms on Linux 5.7+ ](https://github.com/h33p/kallsyms-mod) 3. 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID 4. 指出程式碼可改進的地方,並動手實作 :::