作者: wzt
原文链接:https://mp.weixin.qq.com/s/-RyiuZIZWxUc38q7vGXY7A

1 用户层

Freebsd默认的编译器是clang,在libc的实现如下:

libc/secure/stack_protector.c:

long __stack_chk_guard[8] = {0, 0, 0, 0, 0, 0, 0, 0};[1]
static void __guard_setup(void) __attribute__((__constructor__, __used__));[2
static void
__guard_setup(void)
{
        error = _elf_aux_info(AT_CANARY, (void *)tmp_stack_chk_guard,[3]
            sizeof(tmp_stack_chk_guard));
        if (error == 0 && tmp_stack_chk_guard[0] != 0) {
                for (idx = 0; idx < nitems(__stack_chk_guard); idx++) {
                        __stack_chk_guard[idx] = tmp_stack_chk_guard[idx];
                        tmp_stack_chk_guard[idx] = 0;
                }
                return;
        }
        len = sizeof(__stack_chk_guard);

        if (__sysctl(mib, nitems(mib), __stack_chk_guard, &len, NULL, 0) ==
            -1 || len != sizeof(__stack_chk_guard)) {
                /* If sysctl was unsuccessful, use the "terminator canary". */
                ((unsigned char *)(void *)__stack_chk_guard)[0] = 0;[4]
                ((unsigned char *)(void *)__stack_chk_guard)[1] = 0;
                ((unsigned char *)(void *)__stack_chk_guard)[2] = '\n';
                ((unsigned char *)(void *)__stack_chk_guard)[3] = 255;
        }
}

[1] 处的__stack_chk_guard是个全局数组,保存的是每个进程的stack canary值,它是通过[3]处的_elf_aux_info获取, 而这个值是在内核执行execve加载二进制程序时就提前设置好的:

exec_copyout_strings:
        /*
         * Prepare the canary for SSP.
         */
        arc4rand(canary, sizeof(canary), 0);
        destp -= sizeof(canary);
        imgp->canary = destp;
        copyout(canary, (void *)destp, sizeof(canary));
        imgp->canarylen = sizeof(canary);

如果用户使用的是gcc编译器,那么stack canary的值则是在libc初始化时动态生成的:

contrib/gcclibs/libssp/ssp.c:
void *__stack_chk_guard = 0;

static void __attribute__ ((constructor))
__guard_setup (void)
{
  fd = open ("/dev/urandom", O_RDONLY);
  if (fd != -1)
    {
      ssize_t size = read (fd, &__stack_chk_guard,
                           sizeof (__stack_chk_guard));
    }

  p = (unsigned char *) &__stack_chk_guard;
  p[sizeof(__stack_chk_guard)-1] = 255;
  p[sizeof(__stack_chk_guard)-2] = '\n';
  p[0] = 0;
}

通过读取/dev/urandom来获取一个指针地址,在64位上就是8字节。

2 内核层

大家要注意一个进程有两个栈, 一个栈用于运行在用户态, 一个栈用于进程使用系统调用时的内核栈。 freebsd的所有进程的内核栈都使用的是同一个stack canary值, 而linux的每个进程内核栈stack canary值都是不一样的,这样做会带来更高的安全强度,但是对于设计来讲,则会加大了性能开销,linux为了实现这个功能,需要在进程切换的路径中增加对stack canary的切换。

kern/stack_protector.c:
long __stack_chk_guard[8] = {};
static void
__stack_chk_init(void *dummy __unused)
{
        size_t i;
        long guard[nitems(__stack_chk_guard)];

        arc4rand(guard, sizeof(guard), 0);
        for (i = 0; i < nitems(guard); i++)
                __stack_chk_guard[i] = guard[i];
}

很简单,使用arc4rand生成一个随机值,仅此而已, 非常简单。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1621/