使用标准的 IBM AIX 公共接口实现系统调用拦截

从 AIX 6.1 TL06 和 AIX 7.1 起,IBM AIX® 提供了一个标准的公共接口 kmod_util,用于支持系统调用拦截。在本文中,我们将详细说明如何在各种情形下使用 kmod_util 实现系统调用拦截。

简介

系统调用拦截被广泛应用于各种用途,比如安全性、调试等。自 AIX 6.1 TL06 和 AIX 7.1 起,AIX 提供了一个名为 kmod_util 的标准公共接口,用于支持系统调用拦截。本文将介绍如何在不同情形下使用 kmod_util 接口实现系统调用拦截。


了解系统调用指令的运行原理

系统调用是一个日常例程,它允许用户应用程序请求需要特殊权限的操作。系统调用由运行系统调用指令的用户代码发出,该指令使得用户可以进入系统调用处理程序。

某个进程的所有系统调用都将被解析为伪(dummy)函数描述符,以便调用 svc_instr。当连接到一个用户级程序,binder 会对每个未解析的外部调用插入粘合代码(glink.s)。在加载程序时,加载器将解析这些调用,它将在用户 TOC 中设置一个条目,指出系统调用(svc)表中的用户空间部分的某个条目在什么时候是一个系统调用。这个 svc 表条目包含一个伪函数描述符,其中 svc_instr 是它的 IAR,而 svc 编号是它的 TOC。因此,当用户发出系统调用时,代码将被转至粘合代码,后者会加载描述符的地址(svc 表条目),获得 iar(svc_instr)和 toc 值(svc 编号),将 svc 编号加载到寄存器 2 并转到 svc_instr。在拦截系统调用时,我们必须先获得它的 svc 编号。


基础系统调用

为了更好地理解系统调用的运行方式,我们编写了一个样例程序 testchmod(参见 下载 部分的 sc_intercept.zip)来调用基础系统调用 chmod

清单 1. 样例程序:testchmod.c
int main(void)
{
    printf("0x%llx\n", chmod);
    chmod("./testchmod.c", S_IRUSR|S_IWUSR|S_IXUSR);
    return 0;
}

这个样例程序在输出中显示了 chmod 的函数指针:

# xlc -q64 -o testchmod testchmod.c
# ./testchmod
0x90000000048f3e0

现在,让我们使用 dbx 来了解在调用 chmod 期间发生了什么。

首先,我们可以获得如图 1 所示的粘合代码。

图 1. 显示粘结代码
显示粘结代码

然后,查看寄存器中的值:r0,r2 和 r12,如图 2 所示。

图 2. 获得 svc 编号
获得 svc 编号

显然,函数指针 chmod (0x090000000048f3e0) 指向伪函数描述符。伪函数描述符的第一个双字(double-word)包含系统调用入口点的地址(0x3700),而第二个双字包含 chmod 的 svc 编号(0x13e)。

我们还可以使用 kdb 来查看 0x90000000048f3e0 的内容,如图 3 所示。

图 3. 显示 svc 编号
显示 svc 编号

入口点 0x03700 中存储的下一个指令是系统调用指令 sc


内核扩展添加的系统调用

添加系统调用是扩展内核功能的途径之一。出于演示目的,我们编写了一个内核扩展,它向内核添加了一个名为 demo_syscall 的系统调用(参见 下载 部分的 demo_syscall.zip)。调用 demo_syscall 的进程将被置于睡眠状态,直到该内核扩展终止。

清单 2. 样例内核扩展:demo_syscall.c
int demo_syscall_init(int cmd, struct uio *uio)
{
    if (cmd == CFG_INIT) {
    scwait = EVENT_NULL; 
    } else if (cmd == CFG_TERM) {
    e_wakeup(&scwait); 
    } else {
    return -1; 
}
return 0; 
}
int demo_syscall(int arg) 
{
    int rc; 
    bsdlog(LOG_DEBUG | LOG_KERN, "demo_syscall %llx\n", arg); 
    rc = e_sleep_thread(&scwait, NULL, INTERRUPTIBLE); 
    return(rc); 
}

我们还编写了一个样例程序来调用 demo_syscall。

清单 3. 样例程序:invoke_syscall.c
int main()
{
    if (demo_syscall(99) < 0){
    perror("demo_syscall error");
    exit(1); 
    }
    printf("demo_syscall 0x%llx\n", demo_syscall); 
    return 0; 
}

下面的样例程序在输出中显示了 demo_syscall 的函数指针:

nimtb157:/home/ext>./invoke_syscall
demo_syscall 0x90000000086fe90

使用 dbx,我们可以获得粘合代码并查看伪函数描述符,如图 4 所示。

图 4. 显示粘合代码和 svc 编号
显示粘合代码和 svc 编号

点击查看大图

图 4. 显示粘合代码和 svc 编号

显示粘合代码和 svc 编号

我们可以看到,内核扩展添加的系统调用的执行与基础系统调用是一样的。伪函数描述符的第一个双字包含系统调用(0x3700)的入口点的地址,第二个双字包含 demo_syscall (0x3e9) 的 svc 编号。


kmod_util

从 AIX 6.1 TL06 和 AIX 7.1 起,AIX 提供了 kmod_util 内核服务,它可以实现系统调用拦截。实现系统调用拦截后,所有对系统调用的调用都将被拦截,即使是针对现有进程的调用。kmod_util 的声明位于 <sys/sysconfig.h> 中:

int kmod_util(
    int flags,  /* cmd : KU_INTERCEPT, KU_INTERCEPT_STOP, KU_INTERCEPT_CANCEL */
    void *buffer,  /* cmd buffer */
    int length     /* buffer length */
);

pre-sc 和 post-sc 函数

名为 pre-sc 函数的例程将在被拦截的系统调用之前调用。名为 post-sc 函数的例程将在被拦截的系统调用之后进行调用。此外,pre-sc 函数将允许终止系统调用,提供自己的返回值并阻止调用后续的 pre-sc 函数和系统调用。类似地,每个 post-sc 函数都可以查看和修改返回值。如果一个系统调用没有返回给调用方(例如,thread_terminate),那么 post-sc 函数将不会被调用。

对于每个被拦截的系统调用,必须指定一个 pre-sc 函数或一个 post-sc 函数,或同时指定两者。如果在同一个 kmod_util 调用中,对相同的系统调用注册了 pre-sc 函数和 post-sc 函数,那么将认为它们是成对出现的。kmod_util 调用中指定的所有 pre-sc 和 post-sc 函数都必须在 kmod_util 内核服务调用方所在的相同内核扩展中定义。然而,其他内核扩展可以拦截同一个系统调用。最新注册的 pre-sc 函数被称为 first,与其配对的 post-sc 函数被称为 last。

pre-sc 函数的原型是:

int pre_sc(uintptr_t *rc, void *parms, uintptr_t cookie, void *buffer);

其中,parms 是指向系统调用参数的指针,cookiekmod_util 的调用方指定的一个模糊值,buffer 是一个 scratch 128-字节缓冲器(buffer),供 pre-sc 以及与其配对的 post-sc 函数使用。

如果 pre-sc 函数返回非零值,那么所有后续的 pre-sc 函数和系统调用都将被终止。rc 参数是一个地址,在该地址可以指定可替换的返回值。对于已经调用的 pre-sc 函数,将会调用与其成对的 post-sc 函数。

例如,假设内核扩展 A 请求拦截系统调用 foo(),提供 pre-sc 和 post-sc 函数 A-pre 和 A-post。然后,内核扩展 B 和 C 请求拦截系统调用 foo(),提供 pre-sc 和 post-sc 函数 B-pre、C-pre、B-post 和 C-post。

当系统调用 foo() 被调用时,将依次发出以下调用(假设所有 pre-sc 函数都将返回 0):

  • C-pre
  • B-pre
  • A-pre
  • foo
  • A-post
  • B-post
  • C-post

现在假设 B-pre 返回一个非零值,那么这种情况下将按以下顺序发出调用:

  • C-pre
  • B-pre
  • C-post

post-sc 函数的原型为:

void post_sc(uintptr_t *rc, void *parms, uintptr_t cookie, void *buffer);

post-sc 函数的参数与 pre-sc 函数的参数相同。特别要指出的是,该 buffer 参数正是传递给成对的 pre-sc 函数的那个 buffer 参数。post-sc 函数可以修改返回值。

拦截系统调用

在调用 kmod_util() 时指定 KU_INTERCEPT 标记将会发起系统调用拦截。

停止系统调用拦截

在调用 kmod_util() 时指定 KU_INTERCEPT_STOP 标记可以暂停拦截指定的系统调用。如果已经对某个特定系统调用调用了 pre-sc 函数,那么与之配对的 post-sc 函数仍将被调用,但是此后对该系统调用的调用不会调用 pre-sc 或 post-sc 函数。如果系统调用最初不是由调用内核扩展拦截的,那么停止对该系统调用的拦截将会是无效的。


取消系统调用拦截

指定 KU_INTERCEPT_CANCEL 标记可以取消系统调用拦截。当拦截被取消时,post-sc 函数不会被调用,即使与之配对的 pre-sc 函数已被调用。如果系统调用最初不是由调用内核扩展拦截,那么停止对该系统调用的拦截将会是无效的,但是可以在拦截被停止之前先行取消拦截。

缓冲布局

对于 kmod_util 内核服务的调用,buffer 包含一个标题和一组与被拦截的系统调用有关的元素。这些结构的布局在 <sys/sysconfig.h> 中定义:

struct ku_element {
      void            *ku_svc;     /* function descriptor of system call */
      int              (*ku_pre)();   /* pre-sc function */
      void             (*ku_post)();  /* post-sc function */
      void            *ku_cookie;   /* cookie passed to pre-sc and post-sc function */
      int              ku_bsize;     /* intercept_stack size */
      unsigned short    ku_iflags;
      unsigned short    ku_oflags;
      };
      struct ku_intercept {
      int                 ku_version;  /* KU_VERSION */
      int                 ku_hdr_len;  /* sizeof(struct ku_intercept) */
      int                 ku_element_len; /* sizeof(struct ku_element) */
      int                 ku_num_elements; /* how many system calls to be 
      intercepted */
};

样例内核扩展

为了演示 kmod_util 的用法,我们编写了一个样例内核扩展,它添加了一个名为 sc_intercept 的系统调用(参见 下载 部分中的 sc_intercept.zip)。sc_intercept 系统调用包含一个 cmd 参数,它选择了调用方希望执行的操作(拦截、停止或取消),以及一个 svc 参数,它指定了被拦截的系统调用的函数描述符。

清单 4. 样例内核扩展:sc_intercept.c
int myprec(uintptr_t *rc, void *parms, uintptr_t cookie, void *buffer) 
{
    bsdlog(LOG_DEBUG | LOG_KERN, "pre_sc: parms %llx, %s, %llx, %llx, %s, buffer 
    %llx\n",
    parms, *(long *)parms, ((char *)parms+8), *(long *)((char *)parms+8), cookie, 
    buffer);
    return 0; 
}

void mypost(uintptr_t *rc, void *parms, uintptr_t cookie, void *buffer)
{
   bsdlog(LOG_DEBUG | LOG_KERN, "post_sc: parms %llx, %s, %llx, %s, buffer %llx\n",
   parms, *(long *)parms, (long *)(parms+8), cookie, buffer); 
}

int sc_intercept(unsigned long long svc, int cmd) 
{
    struct ku_intercept *buffer; 
    struct ku_element *kue; 
    int buflen, rc; 
    
    /* valid cmd: KU_INTERCEPT, KU_INTERCEPT_STOP, KU_INTERCEPT_CANCEL */
    if(cmd<1 || cmd>3) 
    return -1; 
    buflen = sizeof(struct ku_intercept) + sizeof(struct ku_element); 
    buffer = xmalloc(buflen, 0, kernel_heap); 
    if(buffer==NULL) 
    return -1; 
    
    buffer->ku_version = KU_VERSION; 
    buffer->ku_hdr_len = sizeof(struct ku_intercept); 
    buffer->ku_element_len = sizeof(struct ku_element); 
    buffer->ku_num_elements = 1; 
    
    kue = (struct ku_element *)((char *)buffer + buffer->ku_hdr_len); 
    kue->ku_svc = (void *)svc; 
    kue->ku_pre = myprec; 
    kue->ku_post = mypost; 
    kue->ku_cookie = "This is cookie!"; 
    kue->ku_bsize = 0; 
    kue->ku_iflags = 0; 
    kue->ku_oflags = 0; 
    
    rc = kmod_util(cmd, buffer, buflen); 
    bsdlog(LOG_DEBUG | LOG_KERN, "rc: %d\n", rc); 
    xmfree(buffer, kernel_heap); 
    return rc; 
}

获得系统调用的函数描述符

要使用 kmod_util 拦截系统调用,必须知道它的函数描述符。本节将演示如何在不同情形下获得系统调用的函数描述符。


内核存储保护键的状态

内核的存储保护键为内核及其扩展提供了一种存储-保护机制。不同的内核存储保护键状态对应获得系统调用函数描述符的不同方法。我们可以使用 skeyctl 查看和修改内核存储保护键的状态,如图 5 所示。

图 5. 显示和修改内核键状态
显示和修改内核键状态

启用内核键时的基础系统调用

我们使用同一个样例程序 testchmod 来演示如何获得 chmod 的函数描述符。我们可以使用 kdb 获得 svc 表基地址。在 系统调用指令运行原理 部分,我们已经获知 chmod 的 svc 编号为 0x13e。因此,我们可以计算 chmod 的 svc 表条目并获得函数描述符,如图 6 所示。

图 6. 获取函数描述符
获取函数描述符

在本例中,svc 表基地址为 le_svc64 (0x04CB0000),而 chmod 的 svc 表条目位于 0x04CB09F0。但是,0x04CB09F0 中保存的数据并不是函数描述符,因为启用了内核键。相反,它指向的是函数描述符地址前面的两个双字。因此,函数描述符应为 0x2B8F930。

为了对拦截进行测试,我们编写了一个样例程序 demo_intercept,它将调用 sc_intercept

清单 5. 样例程序:demo_intercept.c
x = strtoull(argv[optind], 0, 16); 
rc = sc_intercept(x, cmd);
图 7. 测试系统调用拦截
测试系统调用拦截

要查看拦截结果,可向系统控制台发送 syslogd 的输出:

nimtb157:/home/ext/sc> more /etc/syslog.conf
*.debug               /dev/console
图 8. 拦截的测试结果
拦截的测试结果

禁用内核键时的基础系统调用

在内核键被禁用的情况下,系统调用的函数描述符被保存到它的 svc 表条目中,如图 9 所示。

图 9. 获取函数描述符
获取函数描述符

我们采用相同的方法对拦截进行测试,如图 10 所示。

图 10. 测试系统调用拦截
测试系统调用拦截

我们可以在系统控制台中查看拦截结果,如图 11 所示。

图 11. 拦截的测试结果
拦截的测试结果

启用内核键时内核扩展添加的系统调用

我们可以采用与基础系统调用相同的方法来获取由内核扩展添加的系统调用的函数描述符,如图 12 所示。有趣的是,我们会发现 svc 表条目不仅包含函数描述符,还指向函数描述符地址前面的两个双字。

图 12. 获取函数描述符
获取函数描述符

我们使用相同的方法进行测试,如图 13 所示。

图 13. 测试系统调用拦截
测试系统调用拦截

可以在系统控制台查看拦截结果,如图 14 所示。

图 14. 拦截的测试结果
拦截的测试结果

禁用内核键时内核扩展添加的系统调用

与基础系统调用类似,函数描述符被保存到 svc 表条目中,如图 15 所示。

图 15. 获取函数描述符
获取函数描述符

使用相同的方法进行测试,如图 16 所示。

图 16. 测试系统拦截
测试系统拦截

我们可以在系统控制台查看拦截结果,如图 17 所示。

图 17. 拦截的测试结果
拦截的测试结果

参考资料

学习

  • 参考 The secret of skeyctl and bosboot 一文,了解有关 skeyctlbosboot 的更多信息。
  • 要了解如何编写 AIX 内核扩展,阅读 Writing AIX kernel extensions
  • 阅读 Kernel Extensions and Device Support Programming Concepts,获得有关编写 AIX 内核扩展的更多信息。
  • 阅读 Technical Reference: Kernel and Subsystems, Volume 1,获得有关 kmod_util 内核服务的更多信息。
  • AIX and UNIX 专区:developerWorks 的“AIX and UNIX 专区”提供了大量与 AIX 系统管理的所有方面相关的信息,您可以利用它们来扩展自己的 UNIX 技能。
  • AIX and UNIX 新手入门:访问“AIX and UNIX 新手入门”页面可了解更多关于 AIX 和 UNIX 的内容。
  • AIX and UNIX 专题汇总:AIX and UNIX 专区已经为您推出了很多的技术专题,为您总结了很多热门的知识点。我们在后面还会继续推出很多相关的热门专题给您,为了方便您的访问,我们在这里为您把本专区的所有专题进行汇总,让您更方便的找到您需要的内容。
  • AIX and UNIX 下载中心:在这里你可以下载到可以运行在 AIX 或者是 UNIX 系统上的 IBM 服务器软件以及工具,让您可以提前免费试用他们的强大功能。
  • IBM Systems Magazine for AIX 中文版:本杂志的内容更加关注于趋势和企业级架构应用方面的内容,同时对于新兴的技术、产品、应用方式等也有很深入的探讨。IBM Systems Magazine 的内容都是由十分资深的业内人士撰写的,包括 IBM 的合作伙伴、IBM 的主机工程师以及高级管理人员。所以,从这些内容中,您可以了解到更高层次的应用理念,让您在选择和应用 IBM 系统时有一个更好的认识。

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=958216
ArticleTitle=使用标准的 IBM AIX 公共接口实现系统调用拦截
publish-date=12232013