


LINUX的系統內核空間的保護
添加時間:2011-5-15
添加:
admin
看了LINUX代碼,感 覺其對內核內存的保護做得不是很好,還有感覺大家有些地方理解不對(主要是LINUX的代碼看起來的樣子和實際的樣子不太一樣),所以談談我對LINUX 系統內核空間的保護和用戶空間與系統空間數據傳遞的代碼看法。注意我說的都是I386體系結構,別的體系結構可以看相應的代碼,不敢保證結果是否是如我所說。 推薦閱讀:《linux 內核管理概述》
LINUX建立進程的時候建立了兩套段描述符,在文件Segment.h有說明
#ifndef _ASM_SEGMENT_H
#define _ASM_SEGMENT_H
#define __KERNEL_CS 0x10
#define __KERNEL_DS 0x18
#define __USER_CS 0x23
#define __USER_DS 0x2B
#endif
一個用于內核代碼,一個用于用戶代碼。運行內核代碼的時候用內核的段描述符號就可以直接訪問用戶空間,但運行用戶代碼的時候用戶段描述符不能訪問內核空間,這是用的保護模式一些機制,具體代碼不再介紹。不懂的就得看看介紹保護模式的一些書籍了。
在用戶代碼調用系統函數的時候,程序進入了系統內核代碼,描述符也已經切換到了內核的描述符,這時可以直接訪問用戶空間或者內核空間,兩者的參數數據傳遞也很簡單,可以直接拷貝等。但看了LINUX代碼的都知道,系統函數代碼里面的用戶空間與內核空間參數傳遞是沒有這么直接拷貝的,那是為什么呢?大家想一想,用戶調用的一些指針參數等,可以指向內核空間,如果不加以檢測直接拷貝,那么用戶空間代碼就可以通過系統調用讀寫內核空間了,這顯然是不準許的。所以 內核代碼里面就采用了統一的一些函數:
copy_from_user/copy_to_user和__generic_copy_from_user/__gerneric_copy_to_user等,而在這些函數里面實現用戶調用傳遞的指針合法性檢測,用戶參數提供的指針等不能指向系統空間,這樣編寫內核代碼的時候只要調用這些函數就能實現了對內核空間的保護,編寫也比較方便。這就提醒大家自己編寫內核代碼的時候,千萬不要圖方便直接用戶空間與內核空間的參數拷貝,其實那些COPY函數并不是說用戶空間與內核空間要怎么切換才能拷貝,這點我看很多人都沒有真正的理解
我們再仔細看看那些COPY函數是怎么實現的內核空間保護呢。原來是在每個進程的進程數據結構里面保存了一個用戶空間范圍, current->addr_limit,因為內核空間在用戶空間上面,所以只要簡單檢測用戶傳遞參數訪問的空間是不是小于等于這個范圍就是了。下面是相關的幾個文件的相關內容:
文件 uaccess.h:
#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })
#define KERNEL_DS MAKE_MM_SEG(0xFFFFFFFF)
#define USER_DS MAKE_MM_SEG(PAGE_OFFSET)
#define get_ds() (KERNEL_DS)
// 取得內核空間范圍 YRG
#define get_fs() (current->addr_limit)
// 取得用戶空間范圍 YRG
#define set_fs(x) (current->addr_limit = (x))
// 設置用戶空間范圍 YRG
文件 processor.h :
typedef struct {
unsigned long seg;
} mm_segment_t;
文件 page.h :
include
#define __PAGE_OFFSET (PAGE_OFFSET_RAW)
#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
文件page_offset.h:
#include
#ifdef CONFIG_1GB
#define PAGE_OFFSET_RAW 0xC0000000
#elif defined(CONFIG_2GB)
#define PAGE_OFFSET_RAW 0x80000000
#elif defined(CONFIG_3GB)
#define PAGE_OFFSET_RAW 0x40000000
#endif
// 這個顯然可以配置用戶空間與系統空間大小 YRG
大家看那get_ds()、get_fs()、set_fs()等函數可能不是你剛看到時想的那么一回事吧?他們看來好象是訪問或者設置段,其實只是訪問或者設置一個進程變量罷了。
你看那些COPY函數使得傳遞的一些參數只能是指向用戶空間,那么內核代碼對系統一些函數的調用怎么辦呢,因為那時的參數都在內核空間里面呀。你仔細看看上面不是有個set_fs(x)調用嗎,那就是設置這個用戶空間限制的調用,只要臨時設置用戶空間限制為內核空間的范圍,調用完了過后恢復就是了。你再看下面代碼就對那幾個set_fs()的作用清楚了吧。
->filename is in our kernel space
unsigned long old_fs_value = get_fs();
set_fs(get_ds()); /* after this we can access the user space data */
open(filename, O_CREAT|O_RDWR|o_EXCL, 0640);
set_fs(old_fs_value); /* restore fs... */
好了,原理講完了,代碼大家也看明白了,我們再看看這種編程好不好呢?其實這種編程顯然不好。缺點如下:
還沒有形成統一的保護方式,對內核空間的保護不好。內核代碼編程不注意的話就可能使得用戶程序突破內核空間的保護。這點編寫內核代碼的同志一定得注意用戶空間與系統空間的拷貝一定得用那些COPY函數,不要自己簡單的直接拷貝。
內核代碼對系統函數調用時設置用戶空間限制不好。這點如果設置了哪個進程的用戶空間限制還沒有設置回去的時候如果進程用戶代碼得到運行(應該不太可能吧)那么就突破了內核空間限制。還有如果內核代碼不注意忘了恢復用戶空間限制那么也使得內核空間保護失效。
其實有好的辦法,實現也很簡單。就是用一個內核全局變量保存用戶空間范圍,這個啟動的時候根據配置計算好,內核調用的時候可以很方便的引用這個變量對用戶代碼的調用參數實現檢測,用另一個內核全局變量來區分程序是在內核態還是用戶態運行,如果在內核態再調用系統函數就可以不用檢測系統調用的參數是在內核空 間還是用戶空間。這樣就可以避免修改那個全局變量的麻煩。
關鍵字:系統、內核、編程
LINUX建立進程的時候建立了兩套段描述符,在文件Segment.h有說明
#ifndef _ASM_SEGMENT_H
#define _ASM_SEGMENT_H
#define __KERNEL_CS 0x10
#define __KERNEL_DS 0x18
#define __USER_CS 0x23
#define __USER_DS 0x2B
#endif
一個用于內核代碼,一個用于用戶代碼。運行內核代碼的時候用內核的段描述符號就可以直接訪問用戶空間,但運行用戶代碼的時候用戶段描述符不能訪問內核空間,這是用的保護模式一些機制,具體代碼不再介紹。不懂的就得看看介紹保護模式的一些書籍了。
在用戶代碼調用系統函數的時候,程序進入了系統內核代碼,描述符也已經切換到了內核的描述符,這時可以直接訪問用戶空間或者內核空間,兩者的參數數據傳遞也很簡單,可以直接拷貝等。但看了LINUX代碼的都知道,系統函數代碼里面的用戶空間與內核空間參數傳遞是沒有這么直接拷貝的,那是為什么呢?大家想一想,用戶調用的一些指針參數等,可以指向內核空間,如果不加以檢測直接拷貝,那么用戶空間代碼就可以通過系統調用讀寫內核空間了,這顯然是不準許的。所以 內核代碼里面就采用了統一的一些函數:
copy_from_user/copy_to_user和__generic_copy_from_user/__gerneric_copy_to_user等,而在這些函數里面實現用戶調用傳遞的指針合法性檢測,用戶參數提供的指針等不能指向系統空間,這樣編寫內核代碼的時候只要調用這些函數就能實現了對內核空間的保護,編寫也比較方便。這就提醒大家自己編寫內核代碼的時候,千萬不要圖方便直接用戶空間與內核空間的參數拷貝,其實那些COPY函數并不是說用戶空間與內核空間要怎么切換才能拷貝,這點我看很多人都沒有真正的理解
我們再仔細看看那些COPY函數是怎么實現的內核空間保護呢。原來是在每個進程的進程數據結構里面保存了一個用戶空間范圍, current->addr_limit,因為內核空間在用戶空間上面,所以只要簡單檢測用戶傳遞參數訪問的空間是不是小于等于這個范圍就是了。下面是相關的幾個文件的相關內容:
文件 uaccess.h:
#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })
#define KERNEL_DS MAKE_MM_SEG(0xFFFFFFFF)
#define USER_DS MAKE_MM_SEG(PAGE_OFFSET)
#define get_ds() (KERNEL_DS)
// 取得內核空間范圍 YRG
#define get_fs() (current->addr_limit)
// 取得用戶空間范圍 YRG
#define set_fs(x) (current->addr_limit = (x))
// 設置用戶空間范圍 YRG
文件 processor.h :
typedef struct {
unsigned long seg;
} mm_segment_t;
文件 page.h :
include
#define __PAGE_OFFSET (PAGE_OFFSET_RAW)
#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
文件page_offset.h:
#include
#ifdef CONFIG_1GB
#define PAGE_OFFSET_RAW 0xC0000000
#elif defined(CONFIG_2GB)
#define PAGE_OFFSET_RAW 0x80000000
#elif defined(CONFIG_3GB)
#define PAGE_OFFSET_RAW 0x40000000
#endif
// 這個顯然可以配置用戶空間與系統空間大小 YRG
大家看那get_ds()、get_fs()、set_fs()等函數可能不是你剛看到時想的那么一回事吧?他們看來好象是訪問或者設置段,其實只是訪問或者設置一個進程變量罷了。
你看那些COPY函數使得傳遞的一些參數只能是指向用戶空間,那么內核代碼對系統一些函數的調用怎么辦呢,因為那時的參數都在內核空間里面呀。你仔細看看上面不是有個set_fs(x)調用嗎,那就是設置這個用戶空間限制的調用,只要臨時設置用戶空間限制為內核空間的范圍,調用完了過后恢復就是了。你再看下面代碼就對那幾個set_fs()的作用清楚了吧。
->filename is in our kernel space
unsigned long old_fs_value = get_fs();
set_fs(get_ds()); /* after this we can access the user space data */
open(filename, O_CREAT|O_RDWR|o_EXCL, 0640);
set_fs(old_fs_value); /* restore fs... */
好了,原理講完了,代碼大家也看明白了,我們再看看這種編程好不好呢?其實這種編程顯然不好。缺點如下:
還沒有形成統一的保護方式,對內核空間的保護不好。內核代碼編程不注意的話就可能使得用戶程序突破內核空間的保護。這點編寫內核代碼的同志一定得注意用戶空間與系統空間的拷貝一定得用那些COPY函數,不要自己簡單的直接拷貝。
內核代碼對系統函數調用時設置用戶空間限制不好。這點如果設置了哪個進程的用戶空間限制還沒有設置回去的時候如果進程用戶代碼得到運行(應該不太可能吧)那么就突破了內核空間限制。還有如果內核代碼不注意忘了恢復用戶空間限制那么也使得內核空間保護失效。
其實有好的辦法,實現也很簡單。就是用一個內核全局變量保存用戶空間范圍,這個啟動的時候根據配置計算好,內核調用的時候可以很方便的引用這個變量對用戶代碼的調用參數實現檢測,用另一個內核全局變量來區分程序是在內核態還是用戶態運行,如果在內核態再調用系統函數就可以不用檢測系統調用的參數是在內核空 間還是用戶空間。這樣就可以避免修改那個全局變量的麻煩。
關鍵字:系統、內核、編程
新文章:
- CentOS7下圖形配置網絡的方法
- CentOS 7如何添加刪除用戶
- 如何解決centos7雙系統后丟失windows啟動項
- CentOS單網卡如何批量添加不同IP段
- CentOS下iconv命令的介紹
- Centos7 SSH密鑰登陸及密碼密鑰雙重驗證詳解
- CentOS 7.1添加刪除用戶的方法
- CentOS查找/掃描局域網打印機IP講解
- CentOS7使用hostapd實現無AP模式的詳解
- su命令不能切換root的解決方法
- 解決VMware下CentOS7網絡重啟出錯
- 解決Centos7雙系統后丟失windows啟動項
- CentOS下如何避免文件覆蓋
- CentOS7和CentOS6系統有什么不同呢
- Centos 6.6默認iptable規則詳解