linux的线程私有数据以及编译器stack_protector选项

fs和gs提供per-task数据

这两个寄存器被用来访问一些线程私有数据, 其中fs被用户态程序使用, gs被内核态使用。应该知道, 在使用pthread时, 可以通过pthread_self()拿到当前线程的线程id, 其实就是从fs里面拿数据。至于为什么能这么做, 可以参考这篇文章, 详细介绍了linux实现per-task数据的原理。作为应用, 我们只需通过glibc间接的访问errbopthread_self()即可。例如看glibc的代码:

1
2
3
4
5
6
7
8
9
10
11
#  define THREAD_SELF \
({ struct pthread *__self; \
asm ("mov %%fs:%c1,%0" : "=r" (__self) \
: "i" (offsetof (struct pthread, header.self))); \
__self;})

pthread_t
__pthread_self (void)
{
return (pthread_t) THREAD_SELF;
}

看不懂没关系, 这个属于系统内核细节, 只需要知道内核私有数据通过fs寄存器访问就行了, 至于我们怎么拿这些私有数据, 那是glibc该考虑的事情, 作为用户态程序, 只管调别人写好的函数就完了。另外, errno也属于线程私有数据, 知道了上面的这些事情, 很容易理解为啥errno是线程独享的了。

栈溢出攻击保护机制

介绍另一个奇妙的东西: stack canary, 这是一个安全策略。这个安全选项专门用于预防栈溢出攻击, 每个线程(在内核中对应一个task_struct)都有它自己的一个stack canary, 它相当于一个随机数。在函数入口需要向函数栈push一个canary, 函数出口需要将函数栈中的canary和原始值做对比。这个stack canary其实就是存储在fs中的, 它也是线程私有数据!当我们开启gcc的fstack-protector时, 编译器就自己打桩, 做相关检查。我们可以通过fs:40拿到这个随机数。