Linux核心模組程式設計--系統呼叫(轉)

worldblog發表於2007-08-10
Linux核心模組程式設計--系統呼叫(轉)[@more@]

  系統呼叫

  迄今為止,我們做的唯一的事就是用好已定義的核心機制去登記 /proc 檔案和裝置驅動處理程式。如果你想做核心程式設計師認為你想做的,例如寫裝置驅動程式,這就對了。但是如果你想做一些不平常的事,在某些地方改變系統的行為呢?那麼這大多取決你自己。

  這是核心程式設計中變得危險的地方。當寫下下面的例子,我殺死了 open 系統呼叫。這意味著我不能開啟任何檔案,不能執行任何程式,甚至不能 shutdown 計算機。我不得不按電源開關。幸運的,沒有檔案消失。為了確保不失去任何檔案,在你做insmod 和 rmmod之前請執行 sync 。

  忘記 /proc 檔案,忘記裝置檔案。它們只是次要的細節。 所有程式都要使用的和核心通訊的真正的方法是系統呼叫。當一個程式請求核心的服務時(例如開啟檔案,分支一個新程式,請求更多記憶體),這是被使用的機制。如果你想改變核心的行為方式,這是你要做的地方。順便說一下,如果你想看看一個程式使用了什麼系統呼叫,執行 strace 。

  通常,一個程式不能訪問核心。它不能訪問核心的記憶體和呼叫核心的函式。CPU硬體強迫這個(那就是為什麼叫‘保護模式’的原因)。系統呼叫是對這個通常的規則的例外。所發生的是程式用適當的值填充暫存器然後呼叫跳到核心中先前已定義的區域的特定的指令(當然,該區域是使用者程式可讀但不可寫的)。在 Intel CPU下,使用中斷 0x80 做這個。硬體知道一旦你跳到這個區域,你就不再是執行在嚴格的使用者模式而是作業系統核心--因此你就可以做任何你想做的。

  核心中那個程式可以跳到的區域被稱為system_call。 在該區域的程式檢查系統呼叫數,該數告訴核心程式請求什麼服務。然後,它在系統呼叫表(sys_call_table)中查詢呼叫的核心函式的地址。然後它呼叫該函式並在該函式返回後做一些系統檢查,再返回那個程式(或者如果該程式的時間執行完了就返回到一個不同的程式)。如果你想讀這個程式碼,它在原始檔arch//kernel/entry.S中的 ENTRY(system_call)行後。

  因此,如果你想改變某個系統呼叫的工作方法,我們所需要做的是寫一個自己的函式以實現它(通常是加一些我們的程式碼然後再呼叫原來的函式)並且改變 sys_call_table 中的指標指向我們的函式。因為我們可能隨後要移除它而我們不想留下一個不穩定的系統,所以在 cleanup_module 中將那個表恢復成原來的狀態是很重要的。

  這兒的原始碼是這樣一個核心模組的例子。我們想“偵察”某個使用者,並在該使用者開啟一個檔案的時候 printk 一個資訊。朝著這個目標,我們用我們自己的被稱為our_sys_open的函式代替原來的系統呼叫去開啟檔案。這個函式檢查當前程式的UID(使用者的ID)而如果它等於我們要偵察的UID,它就呼叫 printk顯示要開啟的檔名。然後,它用相同的引數呼叫原來的 open 函式做實際的開啟檔案的工作。

  init_module 代替sys_call_table 中合適的區域並且將原來的指標儲存在一個變數中。 cleanup_module 函式使用該變數將沒件事恢復成通常的狀態。這個方法是危險的,因為兩個核心模組同時改變同一個系統呼叫是可能的。想象我們有兩個核心模組 A 和 B。A 的開啟系統呼叫將是 A_open 而B 的將是 B_open 。現在,當 A 被插入核心,系統呼叫被 A_open 代替,當它完成時將呼叫原來的 sys_open 。接著,B 被插入核心,它將用 B_open 代替系統呼叫,當它完成時將呼叫它認為的原來的系統呼叫 A_open。

  現在,如果 B 被先移除,所有的事情將是好的--它將簡單的恢復系統呼叫為將恢復原始的系統呼叫的 A_open。然而,如果A 被移除然後 B 才被移除,系統將崩潰。A 的移除將恢復系統呼叫為原始的 sys_open,將 B 排除出那個環。然後,當 B 被移除,它將恢復系統呼叫為 它 認為是原始的系統呼叫的不再存在於記憶體的 A_open。咋看起來我們好象可以透過檢查系統呼叫是否等於我們的函式及是否根本不去改變它(因此 當 B 被移除時不會改變系統呼叫)來解決這個問題,但那會製造更嚴重的問題。當 A 被移除,看似系統呼叫變為 B_open ,因此它不再指向 A_open,因而在它被從記憶體移除前它不會將它恢復成 sys_open。不幸的, B_open 將仍然試圖呼叫不再存在的 A_open ,因此即使不移除 B 系統也會崩潰。 (譯者認為無論是否進行檢查,系統都會在A被先移除的情況下在B還未移除時使系統崩潰,因為從作者假設的情況看,B會呼叫“它”認為的原始的系統呼叫來完成其功能,在沒有檢查的情況下,B一樣在其儲存原系統呼叫的變數中儲存A的函式A_OPEN並進行呼叫而使系統崩潰。即使B不呼叫“它”認為的原始的系統呼叫來完成其功能,系統也會崩潰,因為它無法恢復系統呼叫。)

  我可以想到兩個預防的辦法。第一個是恢復呼叫為原始值 sys_open。不幸的, sys_open 不是核心系統表 /proc/ksyms 中的一部分,因此不能訪問它。另一個就是使用引用計數來防止一旦模組被載入就可以隨便被 rmmod。這對產品模組是一個好辦法,但不適合做教育性的範例--這也是我為什麼不在這做的原因。

  ex syscall.c

  /* syscall.c

*

* 系統呼叫“偷竊”範例

*/

/* Copyright (C) 1998-99 by Ori Pomerantz */

/* 必要標頭檔案 */

/* 標準標頭檔案 */

#include /* 核心工作 */

#include /* 明確指定是模組 */

/* 處理 CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include

#endif

#include /* 系統呼叫列表 */

/* 為了當前程式結構,我們需要這個知道當前使用者是誰 */

#include

/* 在 2.2.3 版/usr/include/linux/version.h 包含該宏但 2.0.35 不包含

* 加入以備需要 */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

#include

#endif

/* 系統呼叫表(函式表)。我們只將定義為外部的即可,當我們insmod的時候核心會為我們填充它 */

extern void *sys_call_table[];

/* 我們想偵察的UID - 將從命令列填充 */

int uid;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

MODULE_PARM(uid, "i");

#endif

/* 原始的系統呼叫的指標。我們儲存它而不是呼叫原始函式是因為其他某人可能在我們之前可能已經代替了

* 原始的系統呼叫(sys_open)。注意這並不是100%安全的,因為如果另一個模組在我們之前代替了

* sys_open,然後當我們被插入核心,我們將呼叫那個模組裡的函式-它可能在我們之前又被移除了。

*

* 另一個原因是我們不能得到 sys_open。 它是靜態變數,因此是不可匯出的。 */

asmlinkage int (*original_call)(const char *, int, int);

/* 因為某些原因,在 2.2.3 版中current->uid 為0而非真正的使用者 ID。我試圖找出什麼地方錯了,但

* 不能在短時間內完成,而且我很懶-因此我僅僅使用系統呼叫得到UID,這是程式使用的方法。

*

* 因為某些原因,在我重新編譯核心後這個問題沒有了。

*/

asmlinkage int (*getuid_call)();

/* 我們將用來代替 sys_open 的函式 (當你呼叫 open 系統呼叫時這個函式被呼叫)。為了得到精確的原型

* 連同引數的數目和型別,我們首先找到了原始的函式(在 fs/open.c 中)。

*

* 理論上,這意味著我們將侷限於核心的當前版本。實際上,系統呼叫幾乎從來沒有變化

* (這將製造一場浩劫並且需要將所有的程式重新編譯,因為系統呼叫是核心和程式的介面)。

*/

asmlinkage int our_sys_open(const char *filename,

int flags,

int mode)

{

int i = 0;

char ch;

/* 檢查是否是我們要偵察的使用者 */

if (uid == getuid_call()) {

/* getuid_call 是 getuid 系統呼叫,它給出呼叫我們的系統呼叫的程式的使用者的UID。 */

/* 如果相關則報告檔案 */

printk("Opened file by %d: ", uid);

do {

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

get_user(ch, filename+i);

#else

ch = get_user(filename+i);

#endif

i++;

printk("%c", ch);

} while (ch != 0);

printk(" ");

}

/* 呼叫原始的 sys_open - 否則,我們將失去開啟檔案的能力 */

return original_call(filename, flags, mode);

}

/* 初始化模組 - 代替系統呼叫 */

int init_module()

{

/* 警告 - 現在可能太遲了,但可能對下次... */

printk("I'm dangerous. I hope you did a ");

printk("sync before you insmod'ed me. ");

printk("My counterpart, cleanup_module(), is even");

printk("more dangerous. If ");

printk("you value your file system, it will ");

printk("be "sync; rmmod" ");

printk("when you remove this module. ");

/* 將原始的函式指標儲存在 original_call,然後用我們的our_sys_open代替系統呼叫表中的相應系統呼叫

*/

original_call = sys_call_table[__NR_open];

sys_call_table[__NR_open] = our_sys_open;

/* 為了得到系統呼叫foo的函式地址,使用 sys_call_table[__NR_foo] 。 */

printk("Spying on UID:%d ", uid);

/* 得到 getuid 系統呼叫*/

getuid_call = sys_call_table[__NR_getuid];

return 0;

}

/* 清除 - 從/proc中登出相關的檔案 */

void cleanup_module()

{

/* 將系統呼叫恢復原狀 */

if (sys_call_table[__NR_open] != our_sys_open) {

printk("Somebody else also played with the ");

printk("open system call ");

printk("The system may be left in ");

printk("an unstable state. ");

}

sys_call_table[__NR_open] = original_call;

}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-940223/,如需轉載,請註明出處,否則將追究法律責任。

相關文章