# 2021q3 Homework1 (quiz1)
contributed by < [vectorr](https://github.com/vectorr) >
## 解釋上述程式碼運作原理,包含 ftrace 的使用
```c
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);
}
```
從程式碼看,是 `find_ge_pid` 被自訂的 `hook_find_ge_pid` 所取代。
**TODO**: 了解使用 [ftrace hook function](https://www.kernel.org/doc/html/v4.17/trace/ftrace-uses.html) 的機制。
以下是推導如何使用修改 `find_ge_pid` 來隱藏 cron 的 pid 的過程:
1. 先 `strace` 觀察 `pidof cron` 使用了哪些系統呼叫。
```clike
chdir("/proc") = 0
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
fstat(3, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
brk(NULL) = 0x55be244e8000
brk(0x55be24509000) = 0x55be24509000
getdents64(3, /* 364 entries */, 32768) = 9568
openat(AT_FDCWD, "1/stat", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(4, "1 (systemd) S 0 1 1 0 -1 4194560"..., 4096) = 195
read(4, "", 3072) = 0
close(4) = 0
openat(AT_FDCWD, "1/cmdline", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(4, "/sbin/init\0splash\0", 1024) = 18
close(4) = 0
stat("/proc/1/exe", 0x7ffead440920) = -1 EACCES (Permission denied)
readlink("/proc/1/exe", 0x7ffead4419d0, 4096) = -1 EACCES (Permission denied)
openat(AT_FDCWD, "2/stat", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(4, "2 (kthreadd) S 0 0 0 0 -1 212998"..., 4096) = 150
read(4, "", 3072) = 0
close(4) = 0
openat(AT_FDCWD, "2/cmdline", O_RDONLY) = 4
```
在進入 `/proc` ( `chdir("/proc")` ) 後,呼叫 `getdents64`,然後就開始依序地進入每個 pid 的目錄,讀取 `stat`, `cmdline`, `exe` 的內容,這邊推測應該是在比對有沒有 cron 字串。
[`getdents64`](https://man7.org/linux/man-pages/man2/getdents.2.html) 系統呼叫,通常是透過 C 執行時期函式庫的 [`readdir`](https://man7.org/linux/man-pages/man3/readdir.3.html) 去呼叫,是要取得這個目錄內的子目錄列表。
:::warning
:warning: 留意用語:
* directory 的翻譯是「目錄」,這從 1960 年代就使用
* folder 的翻譯才是「檔案夾」,這是 MS-Windows 引入的詞彙
Linux 沿襲 UNIX 傳統,使用 "directory"
:notes: jserv
:::
> 好的,已更改,謝謝~[name=Vectorr]
推測 `pidof` 是取得 `/proc` 下的所有子目錄名稱,然後分別輪詢每個目錄內的相關檔案是否有 cron 的字串,因為有相關檔案的子目錄的名稱即是此 process 的 pid,藉此取得 cron 的 pid。
從完整的 `strace` 輸出看出,當使用這個 driver 隱藏 cron 的 pid 後,不會去輪詢到 cron pid 的那個子目錄,推測 `getdents64` 在當下不會回傳 cron 的 pid 那個子目錄。
1. 接下來要尋找 `find_ge_pid` 跟 `getdents64` 有什麼關係, trace 一下 kernel source code 得到下面的呼叫關係。
```
find_ge_pid <- next_tgid <- proc_pid_readdir <- proc_root_readdir
```
再用 `ftrace` 來追蹤 `pidof cron` 使用到的 function call,也可以看到 `proc_root_readdir` 跟 `proc_pid_readdir`。
```
pidof-54198 [002] .... 184843.742896: ksys_getdents64 <-__x64_sys_getdents64
pidof-54198 [002] .... 184843.742896: __fdget_pos <-ksys_getdents64
pidof-54198 [002] .... 184843.742896: __fget_light <-__fdget_pos
pidof-54198 [002] .... 184843.742896: iterate_dir <-ksys_getdents64
pidof-54198 [002] .... 184843.742896: security_file_permission <-iterate_dir
pidof-54198 [002] .... 184843.742896: apparmor_file_permission <-security_file_permission
pidof-54198 [002] .... 184843.742896: common_file_perm <-apparmor_file_permission
pidof-54198 [002] .... 184843.742896: aa_file_perm <-common_file_perm
pidof-54198 [002] .... 184843.742897: __fsnotify_parent <-security_file_permission
pidof-54198 [002] .... 184843.742897: fsnotify <-security_file_permission
pidof-54198 [002] .... 184843.742897: down_read_killable <-iterate_dir
pidof-54198 [002] .... 184843.742897: _cond_resched <-down_read_killable
pidof-54198 [002] .... 184843.742897: rcu_all_qs <-_cond_resched
pidof-54198 [002] .... 184843.742897: proc_root_readdir <-iterate_dir
pidof-54198 [002] .... 184843.742897: proc_pid_readdir <-proc_root_readdir
```
kernel裡的 x86_64 的 system call table `arch/x86/entry/syscalls/syscall_64.tbl` 可以看到 `217 common getdents64 __x64_sys_getdents64` ,即 `getdents64` 會觸發 `__x64_sys_getdents64` 被呼叫。
再搭配上面 ftrace `pidof cron` 的紀錄可以看到,`__x64_sys_getdents64` 會一路呼叫到 `proc_pid_readdir` 。
再根據更早之前得到的呼叫關係可以得到 `find_ge_pid` 是一路從 user space 呼叫 `getdents64` system call,然後跳到 kernel space 後觸發 `__x64_sys_getdents64` 被呼叫,然後一路被呼叫到的。
**TODO**: 不知道為什麼有的 function 不會出現在 ftrace 的輸出,像 `find_ge_pid`,應該是有什麼規則,需要再去了解。
1. 從上面的追蹤推論,`pidof cron` 會去取得 `/proc` 的所有子目錄列表,根據檢查 cron 是在哪個 pid 的子目錄中,來判斷 cron 的 pid 為何。在取得 `/proc` 的子目錄的路徑中,有一個地方會呼叫到 `find_ge_pid` ,我們透過 ftrace hook function 的機制, 用 `hook_find_ge_pid` 去取代原本 `find_ge_pid` 的行為。透過回傳 pid 時,把我們要隱藏的pid跳掉,使得回傳的子目錄列表缺少了被我們隱藏的 pid 所屬的目錄。
```c
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;
}
```
## 擴充允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID
## 指出程式碼可改進的地方,並動手實作
1. 資源釋放,包含下面各項目 ([commit 1df7d1d](https://github.com/vectorr/linux2021q3_q1_hideproc/commit/1df7d1dc76d9ba738983503f3f2aeb9d75645e47))
1. character device 相關資源。
2. class (/sys) 相關資源。
3. ftrace hook function 的恢復。
4. driver 內建 hidden pid list 的釋放。
1. 修正 unhide_process 會把目前所有放在 list 中要被隱藏的所有 pid 都刪除的問題。([commit 847450a](https://github.com/vectorr/linux2021q3_q1_hideproc/commit/847450a4d3ea00687b2b23e143620e2b678e324d))
1. 將 global variables (hidden_proc, hook, cdev, hideproc_class, dev) 包裝在一個屬於這個 module 的自訂 struct 裡。([commit 6387153](https://github.com/vectorr/linux2021q3_q1_hideproc/commit/6387153d15132730b860a9671eb75db572bc6453))