内核代码中常常在查看系统调用宏时常常会看到使用SYSCALL_DEFINEX一系列宏定义来定义内核针对系统调用接口的定义
该宏位于includelinuxsyscalls.h文件中,针对系统调用的参数不同,使用了一系列的宏进行方便定义:
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
针对不同的参数个数,一般会使用不同的宏定义,例如open系统调用总共由三个参数,内核中针对open的系统调用为:
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
return ksys_open(filename, flags, mode);
}
上述几个宏定义,最终都是调用SYSCALL_DEFINEx,
SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
在该宏中x参数代表的是系统调用参数个数,name为系统调用名称,例如open,被_##name扩展为_open,__VA_ARGS__代表的是参数个数
__SYSCALL_DEFINEx宏定义稍微有点复杂,同样是位于syscall.h文件中:
#define __SYSCALL_DEFINEx(x, name, ...)
__diag_push();
__diag_ignore(GCC, 8, "-Wattribute-alias",
"Type aliasing is used to sanitize syscall arguments");
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
__attribute__((alias(__stringify(__se_sys##name))));
ALLOW_ERROR_INJECTION(sys##name, ERRNO);
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));
asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__));
asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__))
{
long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));
__MAP(x,__SC_TEST,__VA_ARGS__);
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));
return ret;
}
__diag_pop();
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
该宏定义稍微有点复杂:
- __diag_push,__diag_ignore,__diag_pop这一系列宏主要是针对GCC编译器用来控制编译告警,其修改来源于:https://patchwork.kernel.org/project/linux-kbuild/patch/CAK7LNASvCha27kU4ipn23uOpNuxkzJrNzWBwYcxN4n=3xtv8SA@mail.gmail.com/
- smlinkage long sys##name:用于对系统函数进行声明,open被扩展为sys_open,同时__SC_DECL和__VA_ARGS__用于对系统调用参数进行处理
- asmlinkage long __se_sys##name: 用于定义 __se_sys前缀为名称的函数,该函数主要作用是对系统调用进行了封装:
long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__)); __MAP(x,__SC_TEST,__VA_ARGS__); __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); return ret;
- 其函数最终是调用__do_sys##name 函数,例如open 系统调用最终调用的是__do_sys_open
- __do_sys##name 为最终的系统调用实现
系统调用为什么设计这么复杂,主要原因是bug CVE-2009-0029,CVE-2010-3301的存在: Linux 2.6.28及以前版本的内核中,将系统调用中32位参数传入64位的寄存器时无法作符号扩展,可能导致系统崩溃或提权漏洞。
内核开发者通过将系统调用的所有输入参数都先转化成long类型(64位),再强制转化到相应的类型来规避这个漏洞
Linux CVE-2009-0029 漏洞解析_麦兜布达拉的博客-CSDN博客
Diagnostic Pragmas (Using the GNU Compiler Collection (GCC))
Anatomy of a system call, additional content [LWN.net]



