# 2021q3 Homework1 (quiz1)
contributed by < `new-type` >
## 簡介
* **問題描述**:
* 請參閱 [2021 年暑期 Linux 核心 第 1 週測驗題](https://hackmd.io/@sysprog/linux2021-summer-quiz1)
* [原程式](https://gist.github.com/jserv/b2562431901905b6f826521e3d3bb865)
* **開發環境**:
* Linux kernel: 5.4.0-80-generic
* OS: ubuntu-20.04.2
* Processors: 2 processor cores
* Memory: 4096 MB
## 延伸問題 - 1:解釋上述程式碼運作原理,包含 ftrace 的使用
此次測驗程式主要功能是讓 ps 中特定的 PID 消失。
原理是建立一個 char device 記錄需要被隱藏 process 的串列 (hidden_proc)。隱藏的方式是利用原本的 find_ge_pid 的回傳元素若是在需隱藏的串列中,則回傳下一個元素;反之就是回傳該元素。
char device 的建立在
```c
static int _hideproc_init(void)
```
而如何取代原本的 find_ge_pid 則要利用 Ftrace 的 callback 實現。這部分是在 init_hook 中,先利用 kallsyms_lookup_name 找到 find_ge_pid 實際的位置記錄到 hook.orig,另外填上我們更改後的到 hook.func。
```c
struct ftrace_hook {
const char *name;
void *func, *orig;
unsigned long address;
struct ftrace_ops ops;
};
```
實際安裝 Ftrace hook 則是在 hook_install 中。首先是設定 **ftrace_ops**
```c
struct ftrace_ops ops = {
.func = my_callback_func,
.flags = MY_FTRACE_FLAGS
.private = any_private_data_structure,
};
```
測試程式裏對應的設定是
```c
hook->ops.func = hook_ftrace_thunk;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION_SAFE | FTRACE_OPS_FL_IPMODIFY;
```
再來要設定 **filter** ,為了讓特定的函數才會呼叫 callback,這裏的 ip 就是原本 find_ge_pid 的實際位置:
```c
int ftrace_set_filter_ip(struct ftrace_ops *ops, unsigned long ip,
int remove, int reset);
```
最後要**啟用 tracing**:
```c
register_ftrace_function(&ops);
```
以上完成後的 char device 利用 insmod 安裝在 /dev 目錄。當對它寫入時,就會呼叫到程式中的 device_write,這裏分成是要加入隱藏或是刪除隱藏,對應到相對的 hide_process 和 unhide_process。
而 hide_process 是將目前的 node 加到 hidden_proc 的串列中。反之;unhide_process 就是自 hidden_proc 串列中刪除該 node。
```c
static int hide_process(pid_t pid)
{
pid_node_t *proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL);
proc->id = pid;
list_add_tail(&proc->list_node, &hidden_proc);
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) {
if (proc->id == pid) {
list_del(&proc->list_node);
kfree(proc);
}
}
return SUCCESS;
}
```
此外為什麼選擇 find_ge_pid?由於 ps 是從 /proc 目錄下得到目前系統上所有 process 的資訊,而系統會幫每個 process 在 /proc 目錄下建立名為 PID 的目錄,這裏的 PID 也就是用 pidof 得到的數字。
在 Linux kernel 的 fs/proc/base.c 中可以看到讀取 /proc 目錄的函數是
```c
/* for the /proc/ directory itself, after non-process stuff has been done */
int proc_pid_readdir(struct file *file, struct dir_context *ctx)
...
```
在此函數中會有對其下的每個目錄做處理
```c
...
for (iter = next_tgid(ns, iter);
iter.task;
iter.tgid += 1, iter = next_tgid(ns, iter)) {
...
```
而 next_tgid 又會利用 find_ge_pid 來搜尋特定的 PID。這裏的 find_ge_pid 也就是測驗程式要 hook 的對象。
```c
static struct tgid_iter next_tgid(struct pid_namespace *ns, struct tgid_iter iter)
{
struct pid *pid;
if (iter.task)
put_task_struct(iter.task);
rcu_read_lock();
retry:
iter.task = NULL;
pid = find_ge_pid(iter.tgid, ns)
...
```
## 延伸問題 - 2:本程式僅在 Linux v5.4 測試,若你用的核心較新,請試著找出替代方案
## 延伸問題 - 3:本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID
### **允許其 PPID 也跟著隱藏**:
```c
static int hide_process(pid_t pid)
{
pid_node_t *proc;
if (! is_hidden_proc(pid)) {
proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL);
proc->id = pid;
list_add_tail(&proc->list_node, &hidden_proc);
}
return SUCCESS;
}
static int hide_parent_process(pid_t pid)
{
struct task_struct *p;
p = pid_task(find_vpid(pid), PIDTYPE_PID);
if(p && p->parent) {
hide_process(p->parent->pid);
}
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) {
if (proc->id == pid) {
list_del(&proc->list_node);
kfree(proc);
}
}
return SUCCESS;
}
static int unhide_parent_process(pid_t pid)
{
struct task_struct *p;
p = pid_task(find_vpid(pid), PIDTYPE_PID);
if(p && p->parent) {
unhide_process(p->parent->pid);
}
return SUCCESS;
}
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);
hide_parent_process(pid);
} else if (!memcmp(message, del_message, sizeof(del_message) - 1)) {
kstrtol(message + sizeof(del_message), 10, &pid);
unhide_process(pid);
unhide_parent_process(pid);
} else {
kfree(message);
return -EAGAIN;
}
*offset = len;
kfree(message);
return len;
}
```
### **給定一組 PID 列表**隱藏
這裏是利用 Linux kernel API 中的 strsep 將輸入的字串分離開,再照原流程進行:
```c
static ssize_t device_write(struct file *filep,
const char *buffer,
size_t len,
loff_t *offset)
{
long pid;
char *message;
char *token, *cur;
int (*do_action)(pid_t pid);
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)) {
do_action = hide_process;
} else if (!memcmp(message, del_message, sizeof(del_message) - 1)) {
do_action = unhide_process;
} else {
kfree(message);
return -EAGAIN;
}
cur = message + sizeof(add_message);
while ((token = strsep(&cur, " "))) {
kstrtol(token, 10, &pid);
do_action(pid);
}
*offset = len;
kfree(message);
return len;
}
```
## 延伸問題 - 4:指出程式碼可改進的地方,並動手實作
有關處理程式結束時記憶體管理的部份,以觀察程式在 _hideproc_init 時的呼叫順序找出其對應的呼叫:
* alloc_chrdev_region unregister_chrdev_region
* class_create
class destory
* cdev_add
cdev_del
* device_create
device_destory
* register_ftrace_function
unregister_ftrace_function
另外在 hidden_proc 串列中正在使用的記憶體也需要釋放。
```c
static void _hideproc_exit(void)
{
pid_node_t *proc, *tmp_proc;
printk(KERN_INFO "@ %s\n", __func__);
list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) {
list_del(&proc->list_node);
kfree(proc);
}
unregister_ftrace_function(&hook.ops);
cdev_del(&cdev);
device_destroy(hideproc_class, MKDEV(MAJOR(cdev.dev), MINOR_VERSION));
class_destroy(hideproc_class);
unregister_chrdev_region(MKDEV(MAJOR(cdev.dev), MINOR_VERSION), MINOR_VERSION);
}
```
## 參考
* [The Linux Kernel Module Programming Guide](https://github.com/sysprog21/lkmpg)
* [The Linux Kernel API](https://www.kernel.org/doc/htmldocs/kernel-api/index.html)
* [Ftrace]( https://www.kernel.org/doc/Documentation/trace/ftrace.txt)