# 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);
```