系统调用在内核中的入口都是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 longsys##name(__SC_DECL##x(__VA_ARGS__)); \3 static inline longSYSC##name(__SC_DECL##x(__VA_ARGS__)); \4 asmlinkage longSyS##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 intretval;4 struct socket *sock;5 intflags;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 gotoout_release;28
29 out:30 /*It may be already another descriptor 8) Not kernel problem.*/
31 returnretval;32
33 out_release:34 sock_release(sock);35 returnretval;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)); \ ---- 详解12 static inline long SYSC_socket(__SC_DECL3(int, family, int, type, int, protocol)); \ --- 详解23 asmlinkage long SyS_socket(__SC_LONG3(int, family, int, type, int, protocol)) \ --- 详解34 { \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); \ ------ 详解49 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.28and earlier on s390, powerpc,
sparc64, and mips64-bit platforms requires that a 32-bit argumentin a 64-bit register was properly sign extended when sent froma
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 intindex)2 {3 if (index > 10)4 return -EINVAL;5 returnexample_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, intprotocol);2 static inline long SYSC_socket(int family, int type, intprotocol );3 asmlinkage long SyS_socket(long family, long type, longprotocol)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, intprotocol)10 {11 intretval;12 struct socket *sock;13 intflags;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 gotoout_release;36
37 out:38 /*It may be already another descriptor 8) Not kernel problem.*/
39 returnretval;40
41 out_release:42 sock_release(sock);43 returnretval;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 对应的功能号的函数。
至此,感觉内核的宏用的太出神入化了!!!