linux内核系统调用的SYSCALL_DEFINE分析

今天跟sys_mount系统调用发现在2.6.36内核下没有直接定义sys_mount而是用

SYSCALL_DEFINE5来定义,于是在网上搜了一下找到了下面的资料(红字部分为网上资料):

CVE-2010-3301是其中一个。这个漏洞的成因是,在64位的内核上执行32位的系统调用时,作为

传递系统调用号的%rax高32位未被清零处理,而且在进行比较的时候直接使用的%eax,导致高32

位被忽略:

        cmpl $(IA32_NR_syscalls-1),%eax
        ja ia32_badsys
ia32_do_call:
        IA32_ARG_FIXUP
        call *ia32_sys_call_table(,%rax,8)
这样以来,通过静心构造的%rax就可以跳转到它想要的位置去!在这个exploit中,它就利用

ptrace()来跟踪系统调用,并把计算好的想要跳转地址的偏移传递到%rax中,然后执行事先放置好

的代码来提升权限!

修复方法很简单,要么把%rax的高位清零,要么比较的时候使用%rax。修复这个问题的commit是:

http://git.kernel.org/linus/36d001c70d8a0144ac1d038f6876c484849a74de

http://git.kernel.org/linus/eefdca043e8391dcd719711716492063030b55ac

和这个问题类似的问题之前也曾出现过,CVE-2009-0029,问题更严重,涉及很多的系统调用。

不同的是,这个涉及64位的内核和64位的用户空间,来自用户空间的传递系统调用参数的寄存器

的高32位同样没被清零,而带32位参数(比如int)的系统调用就会有问题,内核代码只会检查对

它有意义的低32位,高32位就被忽略而直接传递到后面去了,这就会带来问题了。

问题的解决方法也很简单,就是要把这些寄存器高位清零。说起来简单,做起来难。要是和上面一

样直接用汇编处理的话,参数的类型的信息就丢失了,因为你汇编里分不清它到底是32位还是64

位;而如果用C处理的话,有那么多系统调用,一个一个处理?那不符合Linus的作风!他是怎么做

的呢?用宏!而且用强制转化,把所有的32位参数声明为long,然后再强制转化成实际的类型,比

如int。去看看__SC_CASTx()和__SC_LONGx()的定义就知道了:

PLAIN TEXT
C:
#define __SC_CAST1(t1, a1)      (t1) a1
#define __SC_LONG1(t1, a1)      long a1
 
#define __SYSCALL_DEFINEx(x, name, ...)                                 \
        asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__));           \
        static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__));       \
        asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__))            \
        {                                                               \
                __SC_TEST##x(__VA_ARGS__);                              \
                return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__));    \
        }                                                               \
        SYSCALL_ALIAS(sys##name, SyS##name);                            \
        static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))

可见Linus大神把宏用到了何等出神入化的地步。:-) 这也是为什么你在内核中看到系统调用都是用SYSCALL_DEFINEx()来定义了。

从网上找到的这些资料中的宏定义实现在内核源码的include/linux/syscalls.h中,看了一下。

我要跟踪的sys_mount的定义在fs/namespace.c中,定义如下:

SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data);

在syscalls.h中找到:

#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)

从而展开为:

SYSCALL_DEFINEx(5, _mount, __VA_ARGS__);(VA_ARGS代表mount后面的参数)

SYSCALL_DEFINEx的定义如下:

#define SYSCALL_DEFINEx(x, sname, ...)              \
    __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
接着上面展开为:

__SYSCALL_DEFINEx(5, _mount,__VA_ARGS__)

__SYSCALL_DEFINEx定义如下:

#define __SYSCALL_DEFINEx(x, name, ...)                 \
    asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

接着上面展开为:

asmlinkage long sys_mount(__SC_DECL5(__VA_ARGS__));

__SC_DECL5的定义:

#define __SC_DECL1(t1, a1)  t1 a1
#define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)
#define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)
#define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__)
#define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__)
#define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)

这样从而将上面的展开为:

asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name, char __user * type, unsigned long flags, void __user * data);


这个过程我是按照内核配置的最简单的情况,根据menuconfig选项,syscall_definex的实现会比较

复杂,这个复杂就是为了解决64位机器上32位系统调用的问题,SYSCALL_DEFINEx的x就是代表

的系统调用的参数的个数啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值