# 2021q3 Homework1 (quiz1) contributed by < alan23273850 > :::success 延伸問題: 1. 解釋上述程式碼運作原理,包含 [ftrace](https://www.kernel.org/doc/Documentation/trace/ftrace.txt) 的使用 ::: 難度有點高... 等待課堂講解! :::success 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) ::: ``` $ uname -r 5.4.0-66-generic ``` 我的核心不新! :::success 3. 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID ::: 我只有實作「給定一組 PID 列表」的功能。方法如下:把 `static ssize_t device_write(...)` 裡面 add block `if (!memcmp(message, add_message, sizeof(add_message) - 1))` 的實作改成 ``` char *start = message + sizeof(add_message); char *next = NULL; while ((next = strsep(&start, ",")) != NULL) { if (kstrtol(next, 10, &pid)) break; // failure hide_process(pid); } ``` 而 delete block `else if (!memcmp(message, del_message, sizeof(del_message) - 1))` 的實作改成 ``` char *start = message + sizeof(del_message); char *next = NULL; while ((next = strsep(&start, ",")) != NULL) { if (kstrtol(next, 10, &pid)) break; // failure unhide_process(pid); } ``` 原理就是 [strsep](https://www.kernel.org/doc/htmldocs/kernel-api/API-strsep.html) 其實就類似 C 語言 strtok 的角色,唯一的差異是 strsep 的第一個參數要傳的是字串的位址 (char \*\*) 而非字串的開頭 (char \*)。 :::success 4. 指出程式碼可改進的地方,並動手實作 ::: 需要修改的地方大概有三處: * `static int hide_process(...)` 這裡原本並不會防止重複加入同一個 PID,修正後可以做到。 ```cpp static int hide_process(pid_t pid) { /* FIX: avoid adding an already presenting element */ pid_node_t *proc; list_for_each_entry (proc, &hidden_proc, list_node) { if (proc->id == pid) return SUCCESS; } /***************************************************/ proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL); proc->id = pid; list_add_tail(&proc->list_node, &hidden_proc); // CCC return SUCCESS; } ``` * `static int unhide_process(...)` 這裡原本並不會防止重複刪除同一個 PID,修正後可以做到。 ```cpp static int unhide_process(pid_t pid) { pid_node_t *proc, *tmp_proc; list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) // BBB { /* FIX: delete only the target element */ if (proc->id == pid) { list_del(&proc->list_node); // DDD kfree(proc); return SUCCESS; } /***************************************/ } return SUCCESS; } ``` * `static void _hideproc_exit(...)` 如同許多同學所說,如果 rmmod 的時候不釋放資源的話,下次再 insmod 的話就會當機,造成實驗上非常大的麻煩,所以理論上必須優先更改這個部分,再去改進其他地方。 仔細觀察一下,有註冊資源的地方,先是 `_hideproc_init` 裡面的 `alloc_chrdev_region`、 `class_create`、`cdev_add`、`device_create` 以及 `init_hook` 裡面的 `hook_install`,接著才是過程中會被加加減減的序列 `hidden_proc`。 由此可知,在釋放資源的地方建議反順序進行,先對 `hidden_proc` 的每個成員作記憶體的釋放,再依序執行已經提供好 `hook_install` 的對偶函式 `hook_remove`、`device_create` 的對偶函式 `device_destroy`、`cdev_add` 的對偶函式 `cdev_del`、`class_create` 對偶函式 `class_destroy`、 `alloc_chrdev_region` 的對偶函式 `unregister_chrdev_region`。 比較特別的是 `unregister_chrdev_region` 的第一個參數必須是原型,和 `alloc_chrdev_region` 所要求的位址有所不同。 ```cpp static void _hideproc_exit(void) { pid_node_t *proc, *tmp_proc; // FIX: for later use printk(KERN_INFO "@ %s\n", __func__); /* FIXME: ensure the release of all allocated resources */ list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) { list_del(&proc->list_node); kvfree(proc); } hook_remove(&hook); device_destroy(hideproc_class, MKDEV(dev_major, MINOR_VERSION)); class_destroy(hideproc_class); cdev_del(&cdev); unregister_chrdev_region(dev, MINOR_VERSION); /********************************************************/ } ``` 過程中最引起我好奇的是 cdev_add 和 device_create 的差異,其實僅在於 kernel space (前者) 和 user space (後者) 的差別,[這篇](https://stackoverflow.com/questions/50377327/diffrences-between-cdev-add-and-device-create-function)寫得很好,我獲益良多。 --- :::warning 我對程式碼唯一的疑問是,為什麼這個 module 所有的函式與變數前面都加上了 static 關鍵字?這是必要的嗎? ::: --- 最後程式碼: ```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 1 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; list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) // AAA { 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) { /* FIX: avoid adding an already presenting element */ pid_node_t *proc; list_for_each_entry (proc, &hidden_proc, list_node) { if (proc->id == pid) return SUCCESS; } /***************************************************/ proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL); proc->id = pid; list_add_tail(&proc->list_node, &hidden_proc); // CCC return SUCCESS; } static int unhide_process(pid_t pid) { pid_node_t *proc, *tmp_proc; list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) // BBB { /* FIX: delete only the target element */ if (proc->id == pid) { list_del(&proc->list_node); // DDD kfree(proc); return SUCCESS; } /***************************************/ } 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); // Ref: https://www.kernel.org/doc/htmldocs/kernel-api/API-strsep.html // Ref: https://www.kernel.org/doc/htmldocs/kernel-api/API-kstrtol.html if (!memcmp(message, add_message, sizeof(add_message) - 1)) { /* FIX: support multiple pids */ char *start = message + sizeof(add_message); char *next = NULL; while ((next = strsep(&start, ",")) != NULL) { if (kstrtol(next, 10, &pid)) break; // failure hide_process(pid); } /******************************/ // kstrtol(message + sizeof(add_message), 10, &pid); // hide_process(pid); } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { /* FIX: support multiple pids */ char *start = message + sizeof(del_message); char *next = NULL; while ((next = strsep(&start, ",")) != NULL) { if (kstrtol(next, 10, &pid)) break; // failure unhide_process(pid); } /******************************/ // kstrtol(message + sizeof(del_message), 10, &pid); // unhide_process(pid); } else { kfree(message); return -EAGAIN; } *offset = len; kfree(message); return len; } static dev_t dev; // Q: 全域變數都有加 static,為什麼? static int dev_major; // FIX: move dev and dev_major to global variables 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; 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) { pid_node_t *proc, *tmp_proc; // FIX: for later use printk(KERN_INFO "@ %s\n", __func__); /* FIXME: ensure the release of all allocated resources */ list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) { list_del(&proc->list_node); kvfree(proc); } hook_remove(&hook); device_destroy(hideproc_class, MKDEV(dev_major, MINOR_VERSION)); class_destroy(hideproc_class); cdev_del(&cdev); unregister_chrdev_region(dev, MINOR_VERSION); /********************************************************/ } module_init(_hideproc_init); module_exit(_hideproc_exit); ```