# 2021q3 Homework1 (quiz1)
contributed by < `GundamBox` >
###### tags: `linux2021`
## 開發環境
- 2021/07/27
- CPU
- AMD Ryzen 7 1700 (8C16T)
- Distro
- Ubuntu 20.04-LTS
- Kernel
- 5.10.16.3-microsoft-standard-WSL2
- Host
- 2021/07/28
- WSL 太多問題要解,擠出一台舊電腦安裝 Linux 原生環境繼續寫作業
- CPU
- Intel i3-4160 (2C4T)
- Distro
- Ubuntu 20.04-LTS
- Kernel
- 5.8.0-63-generic
> 覺得文件寫清楚硬體環境比較好 debug
> 啟發自 st9540808 的[開發記錄][st9540808 開發記錄]
## 開發之前
- [作業連結][linux2021-summer-quiz1]
- [設定環境遇到的困難][where_is_linux_header]
- [設定環境遇到的困難-part2][install_ubuntu_20]
## 題目
### 1. 解釋上述程式碼運作原理,包含 [ftrace][ftrace.txt] 的使用
運作原理可以分為三塊
#### 建立裝置
`_hideproc_init` 作為核心模組進入點,做的事情主要是建立 device `hideproc`
而裝置驅動程式的四個動作 open, release, read, write 中
最重要的是 `device_write`
- "add [pid]" 就把 pid 加到 list 記下來
- "del [pid]" 就把 pid 從 list 移除
#### 建立 list 存隱藏的 pid
`LIST_HEAD` 這個巨集會建立 doubly linked list
核心模組藉此紀錄被隱藏的 pid
#### ftrace
- `kallsyms_lookup_name` 找出 `find_ge_pid` 符號的地址
- hook 這個 function
- `real_find_ge_pid` 函式指標指向原本的 `find_ge_pid`
- `hook_find_ge_pid` 是欲變更的 `find_ge_pid`
- 在 `is_hidden_proc` 檢查 pid 有沒有在 list 裡面,有的話就跳過
### 2. 本程式僅在 Linux v5.4 測試,若你用的核心較新,請試著找出替代方案 ###
> 2020 年的變更 [Unexporting kallsyms_lookup_name()][kallsyms_lookup_name]
> [Access to kallsyms on Linux 5.7+][kallsyms-mod]
#### 替代方案的演化
:::info
"file" 的翻譯是「檔案」,"document" 的翻譯是「文件」
請尊重台灣資訊科技前輩的篳路藍縷
:notes: jserv
:::
- 工程師 Ctrl C+V 大法(誤)
直接將 [kallsyms-mod][kallsyms-mod] 的原始碼加進來,接著 failed (X)
```bash
$ make -C /lib/modules/`uname -r`/build M=`pwd` modules
CC [M] main.o
LD [M] hideproc.o
MODPOST Module.symvers
ERROR: modpost: "kallsyms_lookup_name" [hideproc.ko] undefined!
make[2]: *** [scripts/Makefile.modpost:111: Module.symvers] Error 1
make[2]: *** Deleting file 'Module.symvers'
make[1]: *** [Makefile:1705: modules] Error 2
make: *** [Makefile:9: all] Error 2
```
- 改 `Makefile`,加入 `kallsyms.o`
```makefile
MODULENAME := hideproc
obj-m += $(MODULENAME).o
$(MODULENAME)-y += main.o kallsyms.o
KERNELDIR ?= /lib/modules/`uname -r`/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
```
- 從文件 [kbuild_makefiles][kbuild_makefiles] 可以知道
- `obj-m` 是編成 module,如果把 `kallsyms.o` 加到 `obj-m` 會導致以下錯誤
```bash
make -C /lib/modules/`uname -r`/build M=`pwd` modules
CC [M] main.o
LD [M] hideproc.o
CC [M] kallsyms.o
MODPOST Module.symvers
WARNING: modpost: missing MODULE_LICENSE() in kallsyms.o
ERROR: modpost: "kallsyms_lookup_name" [hideproc.ko] undefined!
make[2]: *** [scripts/Makefile.modpost:111: Module.symvers] Error 1
make[2]: *** Deleting file 'Module.symvers'
make[1]: *** [Makefile:1705: modules] Error 2
make: *** [Makefile:9: all] Error 2
```
- make 成功後,接著執行 `sudo insmod hideproc.ko` 會導致 WSL 死掉
:::warning
什麼叫做「死掉」?用語請精準!
:::
參考 [kallsyms-mod][kallsyms-mod] 的 main.c 的寫法加入
```cpp
KSYMDEF(kvm_lock);
KSYMDEF(vm_list);
```
以及在 `_hideproc_init` 加入
```cpp
if ((err = init_kallsyms()))
return err;
KSYMINIT_FAULT(kvm_lock);
KSYMINIT_FAULT(vm_list);
if (err)
return err;
```
- 2021/07/28 修改:
- 開發環境為 WSL + vscode
- WSL 死掉是指:
1. 與 WSL 連線的 vscode 會斷線重啟,實際上 WSL 發生什麼事需要找 log 出來看
2. vscode 重新連線 WSL 後執行 `sudo lsmod` 指令,發現 module 沒有 insert 進去
- 加上之前安裝 linux header 出現問題,認為 WSL 潛在問題太多,所以放棄使用 WSL 開發,改用原生 linux 開發
- 2021/07/29 修改:
重新讀了 `kallsyms-mod` 的原始碼,前面提到 WSL 會 crash 的原因可能是少了 `init_kallsyms` 設定 `kallsyms`,讓 `init_hook` 內的 `kallsyms_lookup_name` 會對 `NULL` 進行操作,進而造成錯誤。
- 無法隱藏 pid
```shell
$ sudo insmod hideproc.ko
$ pidof htop
3453
$ echo "add 3453" | sudo tee /dev/hideproc
add 3453
$ pidof htop
3453
```
- [ ] ~~TODO: 先將 WSL 退回 kernel 4.19,回答完後面兩題再解決~~
- 改為原生 linux 開發
:::warning
與其花心思在 WSL 上,不如把原生 Linux 裝好,這樣你才能放心地開發 Linux 核心模組和相關程式
:notes: jserv
:::
- 改為原生 linux 開發
可以正常隱藏 pid
~~謎之音: WSL 肯定有偷偷做壞事(誤)~~
### 3. 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID
#### 允許給定一組 PID 列表,而非僅有單一 PID
- 要從 message 拿出多個 pid 先想到 split 後再把每個 substring 傳入 kstrtol 獲得 pid
- 但[文件][kernel_api_ch02s02]沒有提到 `strtok`,倒是有 `strsep`,主要改動如下
```c=
static ssize_t device_write(struct file *filep,
const char *buffer,
size_t len,
loff_t *offset)
{
...
int err;
char delim[] = " ,";
if (!memcmp(message, add_message, sizeof(add_message) - 1)) {
char *pid_ptr = message + sizeof(add_message);
char *found = NULL;
while ((found = strsep(&pid_ptr, delim)) != NULL) {
err = kstrtol(found, 10, &pid);
if (err != 0)
return err;
if(pid > 0)
hide_process(pid);
}
} else if (!memcmp(message, del_message, sizeof(del_message) - 1)) {
char *pid_ptr = message + sizeof(del_message);
char *found = NULL;
while ((found = strsep(&pid_ptr, delim)) != NULL) {
err = kstrtol(found, 10, &pid);
if (err != 0)
return err;
if(pid > 0)
unhide_process(pid);
}
}
...
}
```
- 執行結果
```bash
$ pidof cron
592
$ pidof htop
3534
$ echo "add 592,3534" | sudo tee /dev/hideproc
add 592,3534
$ pidof cron
# 什麼都沒發生
$ pidof htop
# 什麼都沒發生
$ echo "del 592,3534" | sudo tee /dev/hideproc
del 592,3534
$ pidof cron
592
$ pidof htop
3534
```
- 2021/07/30 修改
上課討論後發現的問題,`pid` 的範圍也要檢查
#### 允許其 PPID 也跟著隱藏
1. 本來打算在 `is_hidden_proc` 檢查 pid 或 ppid 該不該隱藏
- 這樣在 `is_hidden_proc` 反而變成要檢查傳進來的 pid 的 child 有沒有在 list 裡面
- 修改原本運作正常 `is_hidden_proc` 會增加無謂的難度跟潛在問題
- 放棄這種實作
2. 改在 `device_write` 這部分實作,隱藏 pid 的同時一起隱藏 ppid
- 把 pid 跟 ppid 一起加入 list,這樣就重用原本正常運作的 `is_hidden_proc`
- 直接在 `sched.h` 找 "ppid",果然有現成的 function `task_ppid_nr` 可以用
```c
pid_struct = find_get_pid(pid);
if (pid_struct) { // 檢查有沒有這個 process 存在
pid_task_struct = pid_task(pid_struct, PIDTYPE_PID);
ppid = task_ppid_nr(pid_task_struct);
hide_process(pid);
hide_process(ppid);
}
```
**注**: `task_ppid_nr` 的實作有用到 rcu_read_lock,剛好 jserv 老師有提到[ RCU 機制][fb_linux_summer2021_rcu]
3. 執行
- 測試用的 fork.c
```c
#include <stdio.h>
#include <unistd.h>
int main()
{
fork();
printf("Hello world!n");
sleep(600000); // 停十分鐘
return 0;
}
```
- 結果
```bash
$ pidof my_fork
362940 362939
$ echo "add 362940" | sudo tee /dev/hideproc
add 362940
$ pidof my_fork
# 什麼都沒發生
```
### 4. 指出程式碼可改進的地方,並動手實作
#### 資源釋放
1. 改寫新功能時,`rmmod` 會導致當機,看起來是這段註解寫的問題,拿了資源就要記得還
```c
static void _hideproc_exit(void)
{
printk(KERN_INFO "@ %s\n", __func__);
/* FIXME: ensure the release of all allocated resources */
}
```
- 資源獲取的順序
1. alloc_chrdev_region
2. class_create
3. cdev_add
4. device_create
5. list_add_tail
- 所以釋放順序應該是
1. list_del
2. device_destroy
3. cdev_del
4. class_destroy
5. unregister_chrdev_regio
2. `hidden_proc` 這個 doubly linked list 配置的資源也要記得還
直接把 `unhide_process` 的部分拿過來用就好
**注**: 改進的時候才發現這段程式沒有檢查要 unhide 哪個 pid,所以前面隱藏多組 pid 後解除隱藏其中一個會把全部都解除隱藏XD
3. `hook_remove` 這段原始碼被加上 `#if 0` ,非常可疑XD
`hook_install` 有用到 `register_ftrace_function`,`hook_remove` 有用到 `unregister_ftrace_function`,看起來應該是一組的。把 `#if 0` 拿掉後在 `_hideproc_exit` 加上 `hook_remove`。
4. 最終結果
```c
static void _hideproc_exit(void)
{
printk(KERN_INFO "@ %s\n", __func__);
/* FIXME: ensure the release of all allocated resources */
pid_node_t *proc, *tmp_proc;
list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) {
list_del(&proc->list_node);
kvfree(proc);
}
hook_remove(&hook);
device_destroy(hideporc_device.class,
MKDEV(MAJOR(hideporc_device.devt), MINOR_VERSION));
cdev_del(&hideporc_device.cdev);
class_destroy(hideporc_device.class);
unregister_chrdev_region(hideporc_device.devt, 1);
}
```
- `hideporc_device` 是額外寫的 struct
參考 [vas-api.c][vas-api.c] 的寫法 ~~(搜尋原始碼的時候剛好跳第一個,覺得寫法不錯所以有樣學樣弄一個)~~
```c
static struct hideporc_dev {
struct cdev cdev;
struct device *device;
dev_t devt;
struct class *class;
} hideporc_device;
```
#### 解除隱藏
```c=
list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) {
list_del(&proc->list_node);
kvfree(proc);
}
```
這寫法會解除隱藏所有 pid。如果有多組隱藏的 pid,而且只想解除隱藏一個 pid,就要檢查
```c=
list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) {
if (proc->id == pid) {
list_del(&proc->list_node);
kvfree(proc);
break;
}
}
```
#### 重複隱藏 pid
`sudo cat /dev/hideproc` 可以列出 list 存了哪些隱藏的 pid,如果重複輸入 pid,例如:
```bash
$ echo "add 594 594 594" | sudo tee /dev/hideproc
add 594 594 594
$ sudo cat /dev/hideproc
pid: 594
pid: 1
pid: 594
pid: 1
pid: 594
pid: 1
```
1. 防呆做得太爛了,不能忍XD
2. 新增檢查
```c
static int hide_process(pid_t pid)
{
pid_node_t *proc, *tmp_proc;
bool pid_exist = false;
list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) {
if (proc->id == pid) {
pid_exist = true;
break;
}
}
if (!pid_exist) {
proc = kvmalloc(sizeof(pid_node_t), GFP_KERNEL);
proc->id = pid;
list_add_tail(&proc->list_node, &hidden_proc);
}
return SUCCESS;
}
```
3. 結果
```bash
$ echo "add 594 594 594" | sudo tee /dev/hideproc
add 594 594 594
$ sudo cat /dev/hideproc
pid: 594
pid: 1
```
## 2021/07/30 上課討論啟發
- [u1f383](https://hackmd.io/@u1f383/hideproc#4%E6%8C%87%E5%87%BA%E7%A8%8B%E5%BC%8F%E7%A2%BC%E5%8F%AF%E6%94%B9%E9%80%B2%E7%9A%84%E5%9C%B0%E6%96%B9%EF%BC%8C%E4%B8%A6%E5%8B%95%E6%89%8B%E5%AF%A6%E4%BD%9C) 實作了 pid 及 module 的隱藏
討論: 如果有人這樣用同樣的方法對 kernel 幹壞事怎麼辦?
可以在開機的時候把 symbol dump 下來,之後比對有沒有變更
## 後記與心得
- 找資料過程中一直跳 programmersought 的內容,有附出處的文章 87% 轉自中國文章,沒附出處的文章拿其中一段原始碼搜尋後會找到其他網站的文章,覺得這網站跟內容農場高度相似。已向[終結內容農場][content-farm-terminator]回報網域及加入黑名單
- 偷偷在作業結尾宣傳這個專案,找資料可以避掉很多垃圾文章
- 文件會有人類迷惑行為(通常是來不及更新導致),但原始碼不會,總之先 `git clone https://github.com/torvalds/linux.git` 壓壓驚
## 參考資訊
1. [kbuild_makefiles][kbuild_makefiles]
2. [st9540808 開發記錄][st9540808 開發記錄]
3. [kernel-api - Basic C Library Functions][kernel_api_ch02s02]
4. [Linux Kernel Repo][linux_kernel_repo]
5. [The Linux Kernel API][doc_kernel_api]
[linux2021-summer-quiz1]: https://hackmd.io/@sysprog/linux2021-summer-quiz1
[where_is_linux_header]: http://gundambox.github.io/2021/07/22/Linux-header%E5%9C%A8%E5%93%AA%E8%A3%A1%EF%BC%9F%E7%B5%95%E5%B0%8D%E9%9B%A3%E4%B8%8D%E5%80%92%E4%BD%A0
[install_ubuntu_20]: http://gundambox.github.io/2021/07/28/%E5%AE%89%E8%A3%9D-Ubuntu-20-04-%E9%81%8E%E7%A8%8B%E9%81%87%E5%88%B0%E7%9A%84%E5%B0%8F%E5%95%8F%E9%A1%8C
[ftrace.txt]: https://www.kernel.org/doc/Documentation/trace/ftrace.txt
[kallsyms_lookup_name]: https://lwn.net/Articles/813350/
[kallsyms-mod]: https://github.com/h33p/kallsyms-mod
[kbuild_makefiles]: https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt
[st9540808 開發記錄]: https://hackmd.io/@st9540808/hideproc
[kernel_api_ch02s02]: https://www.kernel.org/doc/htmldocs/kernel-api/ch02s02.html
[doc_kernel_api]: https://www.kernel.org/doc/html/latest/core-api/kernel-api.html
[content-farm-terminator]: https://github.com/danny0838/content-farm-terminator
[linux_kernel_repo]: https://github.com/torvalds/linux.git
[fb_linux_summer2021_rcu]: https://www.facebook.com/groups/system.software2021/posts/951980355645021/
[vas-api.c]: https://github.com/torvalds/linux/blob/v5.8/arch/powerpc/platforms/powernv/vas-api.c