系統調用在內核中的入口都是sys_xxx,但其實Linux的系統調用都改為SYSCALL_DEFINE定義的。本文以socket系統調用為例來詳解。
1 首先看一下SYSCALL_DEFINE的定義,如下:
1 #define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void) 2 #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) 3 #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) 4 #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) 5 #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) 6 #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) 7 #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
2 宏SYSCALL_DEFINEx的定義:
1 #define SYSCALL_DEFINEx(x, name, ...) \ 2 asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \ 3 static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \ 4 asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \ 5 { \ 6 __SC_TEST##x(__VA_ARGS__); \ 7 return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \ 8 } \ 9 SYSCALL_ALIAS(sys##name, SyS##name); \ 10 static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))
3 下面以socket系統調用為實例來分析,其定義:
1 SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 2 { 3 int retval; 4 struct socket *sock; 5 int flags; 6 7 /* Check the SOCK_* constants for consistency. */ 8 BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC); 9 BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK); 10 BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK); 11 BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK); 12 13 flags = type & ~SOCK_TYPE_MASK; 14 if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) 15 return -EINVAL; 16 type &= SOCK_TYPE_MASK; 17 18 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) 19 flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; 20 21 retval = sock_create(family, type, protocol, &sock); 22 if (retval < 0) 23 goto out; 24 25 retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); 26 if (retval < 0) 27 goto out_release; 28 29 out: 30 /* It may be already another descriptor 8) Not kernel problem. */ 31 return retval; 32 33 out_release: 34 sock_release(sock); 35 return retval; 36 }
3.1 ##和__VA_ARGS__
其中##是連接符,__VA_ARGS__代表前面...里面的可變參數。
3.2 socket系統調用對應的就是SYSCALL_DEFINE3
socket -> SYSCALL_DEFINE3 -> SYSCALL_DEFINEx 展開就是:
SYSCALL_DEFINEx(3, _socket, int, family, int, type, int, protocol)
再繼續展開如下:
1 asmlinkage long sys_socket(__SC_DECL3(int, family, int, type, int, protocol)); \ ---- 詳解1 2 static inline long SYSC_socket(__SC_DECL3(int, family, int, type, int, protocol)); \ --- 詳解2 3 asmlinkage long SyS_socket(__SC_LONG3(int, family, int, type, int, protocol)) \ --- 詳解3 4 { \ 5 __SC_TEST3(int, family, int, type, int, protocol); \ 6 return (long) SYSC_socket(__SC_CAST3(int, family, int, type, int, protocol)); \ 7 } \ 8 SYSCALL_ALIAS(sys_socket, SyS_socket); \ ------ 詳解4 9 static inline long SYSC_sockt(__SC_DECL3(int, family, int, type, int, protocol)) 詳解5
詳解1: 函數sys_socket的聲明
詳解2 :函數SYSC_socket聲明
詳解3:函數SYSC_socket定義
詳解4:SYSCALL_ALIAS,根據名字就可以知道,這個宏定義的意思其實就是將SyS_socket的別名設為sys_socket,也就是說調用sys_socket其實就是在調用SyS_sockt。
1 #define SYSCALL_ALIAS(alias, name) \ 2 asm ("\t.globl " #alias "\n\t.set " #alias ", " #name "\n" \ 3 "\t.globl ." #alias "\n\t.set ." #alias ", ." #name)
3.3 宏__SC_DECL3,__SC_LONG3,__SC_CAST3
1 /*宏__SC_DECLx*/ 2 #define __SC_DECL1(t1, a1) t1 a1 3 #define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__) 4 #define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__) 5 #define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__) 6 #define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__) 7 #define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__) 8 /*宏__SC_LONGx*/ 9 #define __SC_LONG1(t1, a1) long a1 10 #define __SC_LONG2(t2, a2, ...) long a2, __SC_LONG1(__VA_ARGS__) 11 #define __SC_LONG3(t3, a3, ...) long a3, __SC_LONG2(__VA_ARGS__) 12 #define __SC_LONG4(t4, a4, ...) long a4, __SC_LONG3(__VA_ARGS__) 13 #define __SC_LONG5(t5, a5, ...) long a5, __SC_LONG4(__VA_ARGS__) 14 #define __SC_LONG6(t6, a6, ...) long a6, __SC_LONG5(__VA_ARGS__) 15 /*宏__SC_CASTx*/ 16 #define __SC_CAST1(t1, a1) (t1) a1 17 #define __SC_CAST2(t2, a2, ...) (t2) a2, __SC_CAST1(__VA_ARGS__) 18 #define __SC_CAST3(t3, a3, ...) (t3) a3, __SC_CAST2(__VA_ARGS__) 19 #define __SC_CAST4(t4, a4, ...) (t4) a4, __SC_CAST3(__VA_ARGS__) 20 #define __SC_CAST5(t5, a5, ...) (t5) a5, __SC_CAST4(__VA_ARGS__) 21 #define __SC_CAST6(t6, a6, ...) (t6) a6, __SC_CAST5(__VA_ARGS__) 22 /*宏__SC_TESTx*/ 23 #define __SC_TEST(type) BUILD_BUG_ON(sizeof(type) > sizeof(long)) 24 #define __SC_TEST1(t1, a1) __SC_TEST(t1) 25 #define __SC_TEST2(t2, a2, ...) __SC_TEST(t2); __SC_TEST1(__VA_ARGS__) 26 #define __SC_TEST3(t3, a3, ...) __SC_TEST(t3); __SC_TEST2(__VA_ARGS__) 27 #define __SC_TEST4(t4, a4, ...) __SC_TEST(t4); __SC_TEST3(__VA_ARGS__) 28 #define __SC_TEST5(t5, a5, ...) __SC_TEST(t5); __SC_TEST4(__VA_ARGS__) 29 #define __SC_TEST6(t6, a6, ...) __SC_TEST(t6); __SC_TEST5(__VA_ARGS__) 30 /*宏BUILD_BUG_ON*/ 31 #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
上面__SC_DECL3
定義展開如下:
__SC_DECL3(int, family, int, type, int, protocol) -> int family, __SC_DECL2(int, type, int, protocol) -> int family, int type, __SC_DECL1(int, protocol) -> int family, int type, int protocol
上面__SC_LONG3展開如下:
__SC_LONG3(int, family, int, type, int, protocol) -> long family, __SC_LONG2(int, type, int, protocol) -> long family, long type , __SC_LONG1(int, protocol) -> long family, long type, long protocol
上面__SC_CAST3展開如下:
__SC_CAST3(int, family, int, type, int, protocol) -> (int) family, __SC_CAST2(int, type, int, protocol) -> (int) family, (int) type, __SC_CAST1(int, protocol) -> (int) family, (int) type, (int) protocol
上面__SC_TEST3展開如下:
__SC_TEST3(int, family, int, type, int, protocol) -> __SC_TEST(int); __SC_TEST2(int, type, int, protocol) -> __SC_TEST(int); __SC_TEST(int); __SC_TEST1(int, protocol) -> __SC_TEST(int); __SC_TEST(int); __SC_TEST(int); -> BUILD_BUG_ON(sizeof(int) > sizeof(long)); BUILD_BUG_ON(sizeof(int) > sizeof(long)); BUILD_BUG_ON(sizeof(int) > sizeof(long));
就是3.3節的宏是將系統調用的參數統一變為了使用long型來接收,再強轉轉為int,也就是系統調用本來傳下來的參數類型。那么強轉一下為什么呢?原因就是64位的Linux有一個名為CVE-2009-2009的漏洞,這個漏洞的具體內容如下。
3.4 漏洞 CVE-2009-0029 分析
如果我們查看2.6.28 之前的代碼,系統調用確實沒有這樣寫,但在2009年64位 Linux 內核在某些64位平台下被發現系統調用有漏洞,為了修復該漏洞系統調用才改寫成現在這樣的。該漏洞被命名為 CVE-2009-0029 ,對該漏洞的簡單描述如下:
The ABI in the Linux kernel 2.6.28 and earlier on s390, powerpc, sparc64, and mips 64-bit platforms requires that a 32-bit argument in a 64-bit register was properly sign extended when sent from a user-mode application, but cannot verify this, which allows local users to cause a denial of service (crash) or possibly gain privileges via a crafted system call.
意思是說,在Linux 2.6.28及以前版本內核中,IBM/S390、PowerPC、Sparc64以及MIPS 64位平台的ABI要求在系統調用時,用戶空間程序將系統調用中32位的參數存放在64位的寄存器中要做到正確的符號擴展,但是用戶空間程序卻不能保證做到這點,這樣就會可以通過向有漏洞的系統調用傳送特制參數便可以導致系統崩潰或獲得權限提升。
以下面的例子說明下:
1 asmlinkage long sys_example(unsigned int index) 2 { 3 if (index > 10) 4 return -EINVAL; 5 return example_array[index]; 6 }
上面系統調用的內核代碼例子,參數是32位的無符號整型,但使用的64位寄存器傳參,上面提及到平台的ABI要求32為參數存放在64位寄存器中要符號擴展,由程序的調用者來完成,在系統調用的函數中則由用戶程序來保證進行了正確的寄存器符號擴展,但用戶空間程序卻無法保證。
上面例子中,調用程序必須將索引符號擴展為64位,如傳入參數index=3,那么將寄存器的低32位賦值為3,並未修改高32位,此時該寄存器的高32位假設為0xFFFFFFFF,在進入該系統調用函數時,由於編譯器認為你已經進行符號擴展了,所以直接引用64位寄存器的值代表index,此時index=-4294967293,判斷不大於5,返回example_array[-4294967293],很可能訪問到一塊沒有權限訪問的地址空間或者其他地址異常的錯誤而導致程序崩潰。
怎么去解決這個問題呢,也許你會想既然用戶空間沒有進行寄存器的符號擴展,那么我在系統調用函數之前加入一些匯編代碼將寄存器進行符號擴展,但有個問題是,系統調用前代碼都是公共的,因此並不能將某個寄存器一定符號擴展。所以內核使用了3.3節中的宏實現修復此漏洞。
順便提下ABI的定義:
API是定義了應用程序調用庫的接口。那么ABI其實就是定義了二進制接口,全稱叫
Application Binary Interface.定義的內容大概包括:
(1)數據類型的大小、布局和對齊
(2)調用約定(控制着函數的參數如何傳送以及如何接受返回值),例如,是所有的參數都通過棧傳遞,還是部分參數通過寄存器傳遞;哪個寄存器用於哪個函數參數;通過棧傳遞的第一個函數參數是最先push到棧上還是最后
(3)系統調用 的編碼和一個應用如何向操作系統進行系統調用;
(4)在一個完整的操作系統中, 目標文件 的二進制格式、程序庫等等。
4 系統調用宏展開結果:
1 asmlinkage long sys_socket(int family, int type, int protocol); 2 static inline long SYSC_socket(int family, int type, int protocol ); 3 asmlinkage long SyS_socket(long family, long type, long protocol) 4 { 5 BUILD_BUG_ON(sizeof(int) > sizeof(long)); BUILD_BUG_ON(sizeof(int) > sizeof(long)); 6 return (long) SYSC_socket((int) family, (int) type, (int) protocol); 7 } 8 SYSCALL_ALIAS(sys_socket, SyS_socket); 9 static inline long SYSC_sockt(int family, int type, int protocol) 10 { 11 int retval; 12 struct socket *sock; 13 int flags; 14 15 /* Check the SOCK_* constants for consistency. */ 16 BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC); 17 BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK); 18 BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK); 19 BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK); 20 21 flags = type & ~SOCK_TYPE_MASK; 22 if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) 23 return -EINVAL; 24 type &= SOCK_TYPE_MASK; 25 26 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) 27 flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; 28 29 retval = sock_create(family, type, protocol, &sock); 30 if (retval < 0) 31 goto out; 32 33 retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); 34 if (retval < 0) 35 goto out_release; 36 37 out: 38 /* It may be already another descriptor 8) Not kernel problem. */ 39 return retval; 40 41 out_release: 42 sock_release(sock); 43 return retval; 44 } 45
sys_socket函數最終就是調用函數SYSC_sockt。
5 socket和sys_socket
socket是應用層的api,sys_socket是內核級的api,那么它們是怎么聯系起來調用的呢?
系統調用是有一個 CPU 運行等級的提升問題, 用戶代碼在 3 級, 操作系統代碼在 0 級。
socket是對在 Ring 3 級對系統調用的一個包裝。所有的系統函數只有一個系統調用入口,int $0x80,在這條指令之前把調用的函數對應的功能號放到 %eax 寄存器。這條指令產生一個中斷, CPU 切換到中斷處理程序,,運行等級從 Ring 3 級切換到 Ring 0 級. 開始在內核中運行.。內核再根據 %eax 中的功能號來調用不同的函數。 sys_socket 就是內核中處理 socket 對應的功能號的函數。
至此,感覺內核的宏用的太出神入化了!!!