# 2021q3 Homework1 (quiz1)
contributed by < `OscarShiang` >
## 解釋 hideproc 程式碼運作原理,包含 ftrace 的使用
`hideproc` 的原理在於使用 ftrace hook 進行 live patching 進而更改 `find_ge_pid()` 函式的行為。
當我們利用 `ps` 或是列出 `/proc/` 目錄底下的程序時,實際上會使用到 `find_ge_pid()` 函式來進行查找 (在 `/fs/proc/base.c` 中的 [`proc_pid_readdir()`](https://elixir.bootlin.com/linux/v5.4.80/source/fs/proc/base.c#L3313) 使用 [`next_tgid()`](https://elixir.bootlin.com/linux/v5.4.80/source/fs/proc/base.c#L3275) 遍尋所有程序,而其實作則使用 [`find_ge_pid()`](https://elixir.bootlin.com/linux/v5.4.80/source/fs/proc/base.c#L3284)),所以若我們改變 `find_ge_pid()` 的行為,就可以達到隱藏程序的效果。
但是我們的目的並不是要完全取代原本的實作,而是偏移部份程序查照的結果。我們使用的方式是利用 ftrace 註冊 `hook_ftrace_thunk()` 函式,當有程序想要呼叫 `find_ge_pid()` 時, ftrace 會將其跳轉到我們先前指定的 `hook_ftrace_thunk()` 函式,透過更改 Instruction Pointer 位址從而跳轉到 `hook_find_ge_pid()` ,利用原先的 `find_ge_pid()` 函式進行操作後,如果預期的 pid 是我們想要隱藏者時,我們將代入到 `find_ge_pid()` 的 `pid` + 1 讓其無法取得預期的結果,將列表中的 pid 隱藏。
而我們操作 `hidden_proc` 的 hidden list 的方式是使用 VFS 來進行。
在範例程式中,我們使用以下命令將指定 pid 隱藏
```shell
$ echo "add <pid>" | sudo tee /dev/hideproc
```
實際上進行處理的函式是在 `device_write()`,經由命令分析後,使我們能夠動態修改 hidden list。
## 允許其 PPID 也跟著隱藏
我參考 [`next_tgid()`](https://elixir.bootlin.com/linux/v5.4.80/source/fs/proc/base.c#L3275) 的方式來進行實作
因為 parent 的資訊保存在 `task_struct` 結構裡面,所以我們需要依序經由以下步驟取得 ppid
- [`find_get_pid()`](https://elixir.bootlin.com/linux/v5.4.80/source/kernel/pid.c#L393): 利用 pid 取得 `struct pid` 的位址
- [`get_task_pid()`](https://elixir.bootlin.com/linux/v5.4.80/source/kernel/pid.c#L381): 透過 `struct pid` 的位址取得 `struct task_struct` 的位址
- `task->parent->pid`: 得到 ppid
為了方便使用我將其包裝成 `get_ppid()`:
```cpp
static pid_t get_ppid(pid_t pid)
{
    struct pid *pid_struct;
    struct task_struct *task;
    pid_struct = find_get_pid(pid);
    task = get_pid_task(pid_struct, PIDTYPE_PID);
    return task->parent->pid;
}
```
接著在模組中新增一個新的命令 `addwp` (add with parent) 用以將指定 pid 以及其 parent pid 加入隱藏的 list 中
> 相關的 commit 可以參考 [`50f8eb9`](https://github.com/OscarShiang/hideproc/commit/50f8eb9c86b57b917759f6d41bb2b731139fa76b)
這邊為了驗證模組行為,我準備了一個簡單的程式:
```cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
    printf("My pid is \t %d\n", getpid());
    pid_t child = fork();
    if (!child) {
        pause();
    } else {
        printf("Child's pid is \t %d\n\n", child);
        printf("(Use Ctrl + C to exit)\n");
        wait(0);
    }
    return 0;
}
```
上述這段程式碼的作用就是產生一組 process 與 child process,並印出兩者的 pid
編譯後,執行結果大致如下
```shell
$ gcc -o test_proc test_proc.c
$ ./test_proc
My pid is        25735
Child's pid is   25736
(Use Ctrl + C to exit)
```
我們可以透過 `ps aux` 檢查二者是否可以被看見
```shell
$ ps aux | grep test_proc
ubuntu     25735  0.0  0.0   2488   588 pts/1    S+   21:17   0:00 ./test_proc
ubuntu     25736  0.0  0.0   2488    84 pts/1    S+   21:17   0:00 ./test_proc
ubuntu     25738  0.0  0.0   8160   736 pts/0    S+   21:21   0:00 grep --color=auto test_proc
```
接著我們使用 `addwp` 命令將 child process pid 加入到 hidden list 中
```shell
$ echo "addwp 25736" | sudo tee /dev/hideproc
```
透過讀取 `/dev/hideproc` 檢查二者是否被加入到 list 之中
```shell
$ sudo cat /dev/hideproc
pid: 25736
pid: 25735
```
此時若使用 `ps aux` 則無法查到兩個 `test_proc` 的狀態
```shell
$ ps aux | grep test_proc
ubuntu     25771  0.0  0.0   8160   668 pts/0    S+   21:25   0:00 grep --color=auto test_proc
```
## 允許給定一組 PID 列表,而非僅有單一 PID
> TODO
## 改進實作
### 離開模組時釋放相關資源
在原本的實作中,因為沒有 exit function 中實作資源釋放。所以如果我們將模組載入後移除,使用 `insmod` 重新載入模組時會得到 `Killed` 的輸出,並無法再次載入模組。
解決的方式就是在 `_hideproc_exit()` 加入與 `_hideproc_init()` 成對的資源釋放即可。
```diff
diff --git a/main.c b/main.c
index 45922af..c9e42b2 100644
--- a/main.c
+++ b/main.c
@@ -63,7 +63,6 @@ static int hook_install(struct ftrace_hook *hook)
     return 0;
 }
-#if 0
 void hook_remove(struct ftrace_hook *hook)
 {
     int err = unregister_ftrace_function(&hook->ops);
@@ -73,7 +72,6 @@ void hook_remove(struct ftrace_hook *hook)
     if (err)
         printk("ftrace_set_filter_ip() failed: %d\n", err);
 }
-#endif
 typedef struct {
     pid_t id;
@@ -212,10 +210,11 @@ static const struct file_operations fops = {
 #define MINOR_VERSION 1
 #define DEVICE_NAME "hideproc"
+static dev_t dev;
+
 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);
@@ -235,7 +234,21 @@ static int _hideproc_init(void)
 static void _hideproc_exit(void)
 {
     printk(KERN_INFO "@ %s\n", __func__);
-    /* FIXME: ensure the release of all allocated resources */
+
+    /* Destroy the hidden list */
+    pid_node_t *proc, *tmp_proc;
+    list_for_each_entry_safe(proc, tmp_proc, &hidden_proc, list_node)
+    {
+        list_del(&proc->list_node);
+        kfree(proc);
+    }
+
+    /* Unregister the device */
+    device_destroy(hideproc_class, MKDEV(MAJOR(dev), MINOR_VERSION));
+    cdev_del(&cdev);
+    class_destroy(hideproc_class);
+    unregister_chrdev_region(MKDEV(dev, MINOR_VERSION), MINOR_VERSION);
+    hook_remove(&hook);
 }
 module_init(_hideproc_init);
```
### `kstrtol` 回傳值檢查
根據 [Kernel API Doc](https://www.kernel.org/doc/htmldocs/kernel-api/API-kstrtol.html) 對於 return value 的描述
> Returns 0 on success, -ERANGE on overflow and -EINVAL on parsing error. Used as a replacement for the obsolete simple_strtoull. Return code must be checked.
但在 `device_write()` 處並沒有針對 `kstrtol()` 的回傳值做檢查,
因此需要在這邊加上檢查
```diff
diff --git a/main.c b/main.c
index 9593cf2..c969b29 100644
--- a/main.c
+++ b/main.c
@@ -170,6 +170,7 @@ static ssize_t device_write(struct file *filep,
                             size_t len,
                             loff_t *offset)
 {
+    int ret;
     long pid;
     char *message;
@@ -181,11 +182,17 @@ static ssize_t device_write(struct file *filep,
     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);
+        ret = kstrtol(message + sizeof(add_message), 10, &pid);
+        if (!ret)
+            hide_process(pid);
+        else
+            return ret;
     } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) {
-        kstrtol(message + sizeof(del_message), 10, &pid);
-        unhide_process(pid);
+        ret = kstrtol(message + sizeof(del_message), 10, &pid);
+        if (!ret)
+           unhide_process(pid);
+        else
+           return ret;
     } else {
         kfree(message);
         return -EAGAIN;
```
因為 `kstrtol` 錯誤原因有兩種,所以不直接回傳 `-EINVAL`,而是將 `kstrtol` 產生的回傳值回傳回去。
### 取消隱藏指定 pid
在原本的實作中,`hideproc` 在收到 `del <pid>` 的命令時並不只會將我們指定的 pid 從隱藏列表中刪除,而是會將所有列表中的 node 全部刪除
因此我在 `unhide_process()` 中加上核對 pid 的機制,避免其將所有節點刪除
```diff
diff --git a/main.c b/main.c
index 5b63b66..f529c1e 100644
--- a/main.c
+++ b/main.c
@@ -133,8 +133,11 @@ 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)
     {
-        list_del(&proc->list_node);
-        kfree(proc);
+        if (proc->id == pid) {
+            list_del(&proc->list_node);
+            kfree(proc);
+            break;
+        }
     }
     return SUCCESS;
 }
```
### 重複加入已被隱藏的 pid 
我們可以透過對 `/dev/hideproc` 寫入命令來增刪我們想要隱藏的 pid,但是在 `hide_process()`  函式中我們並不會對 pid 進行任何的檢查,因此就會有 list 中有重複好幾個 pid 的情況產生
我們可以利用下列的命令重複將 pid 589 加入 list 中
```shell
$ echo "add 589" | sudo tee /dev/hideproc
$ echo "add 589" | sudo tee /dev/hideproc
$ echo "add 589" | sudo tee /dev/hideproc
```
接著查看隱藏的清單即可見三個 pid node
```shell
$ sudo cat /dev/hideproc
pid: 589
pid: 589
pid: 589
```
我們在 `hide_process()` 中加入檢查機制以避免將相同的 pid 加入 list 中
```diff
diff --git a/main.c b/main.c
index c969b29..e99f137 100644
--- a/main.c
+++ b/main.c
@@ -115,7 +115,14 @@ static void init_hook(void)
 static int hide_process(pid_t pid)
 {
-    pid_node_t *proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL);
+    pid_node_t *proc;
+
+    /* Check if the pid is in the list */
+    if (is_hidden_proc(pid))
+        return -EAGAIN;
+
+    /* insert pid node into hidden_proc */
+    proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL);
     proc->id = pid;
     list_add_tail(&proc->list_node, &hidden_proc);
     return SUCCESS;
```
若我們這時嘗試重複加入一個 pid 589 時,就會產生以下的錯誤提示
```shell
$ echo "add 589" | sudo tee /dev/hideproc # first add
add 589
$ echo "add 589" | sudo tee /dev/hideproc # second add
add 589
tee: /dev/hideproc: File exists
```
### 避免 `device_read()` 發生 buffer overflow
> TODO
###### tags: `linux2021`