原理
条件竞争就是两个或者多个进程或者线程同时处理一个资源(全局变量,文件)产生非预想的执行效果,从而产生程序执行流的改变,从而达到攻击的目的。条件竞争需要如下的条件:
并发
即至少存在两个并发执行流。这里的执行流包括线程,进程,任务等级别的执行流。
共享对象
即多个并发流会访问同一对象。常见的共享对象有共享内存,文件系统,信号。一般来说,这些共享对象是用来使得多个程序执行流相互交流。此外,我们称访问共享对象的代码为临界区。在正常写代码时,这部分应该加锁。
改变对象
即至少有一个控制流会改变竞争对象的状态。因为如果程序只是对对象进行读操作,那么并不会产生条件竞争。
线程、进程访问同一资源
写一个脚本,两个进程,同时访问一个文件进行读取,写入加一操作,从1开始输入,各自输出10次,那么输出的文件就很难预料,可能就不是1-20的顺序。这就是条件竞争
Race Condition Enabling Link Following(比赛条件启用链接跟踪)
文件路径名文件描述符但是,将这两种命名解析到相应对象上的方式有所不同
文件路径名在解析的时候是通过传入的路径(文件名,硬链接,软连接)间接解析的,其传入的参数并不是相应文件的真实地址 (inode)。文件描述符通过访问直接指向文件的指针来解析。由于这种间接性,产生了时间竞争窗口race window,程序在访问某个文件之前,会检查是否存在,之后会打开文件然后执行操作。但是如果在检查之后,真正使用文件之前,攻击者将文件修改为某个符号链接,那么程序将访问错误的文件。如果我们在程序检查完对应的文件大小后,将对应的文件删除,并符号链接到另外一个更大的文件,那么程序所读入的内容就会更多,从而就会产生栈溢出。
栈和堆(两个理论,混合理解)
一.堆栈空间分配区别:
1.栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
2.堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
二.堆栈缓存方式区别:
1.栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;
2.堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
三.堆栈数据结构区别:
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构。
一、预备知识―程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)― 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) ― 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)―,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 ―常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区―存放函数体的二进制代码。
栈溢出(根据语言特性不同溢出点也有差异,原理通用)
栈溢出是由于C语言系列没有内置检查机制来确保复制到缓冲区的数据不得大于缓冲区的大小,因此当这个数据足够大的时候,将会溢出缓冲区的范围。
在Python中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
Signal Handler Race Condition(信号处理器条件竞争)
条件竞争经常会发生在信号处理程序中,这是因为信号处理程序支持异步操作。尤其是当信号处理程序是不可重入的或者状态敏感的时候,攻击者可能通过利用信号处理程序中的条件竞争,可能可以达到拒绝服务攻击和代码执行的效果。比如说,如果在信号处理程序中执行了 free 操作,此时又来了一个信号,然后信号处理程序就会再次执行 free 操作,这时候就会出现 double free 的情况,再稍微操作一下,就可能可以达到任意地址写的效果了。
一般来说,与信号处理程序有关的常见的条件竞争情况有:
信号处理程序和普通的代码段共享全局变量和数据段。在不同的信号处理程序中共享状态。信号处理程序本身使用不可重入的函数,比如 malloc 和 free 。一个信号处理函数处理多个信号,这可能会进而导致 use after free 和 double free 漏洞。使用 setjmp 或者 longjmp 等机制来使得信号处理程序不能够返回原来的程序执行流。
总结:不可重入函数可能导致条件竞争,可重入函数一定是线程安全的
线程安全
即该函数可以被多个线程调用,而不会出现任何问题。
条件:
本身没有任何共享资源
有共享资源,需要加锁。
可重用
一个函数可以被多个实例可以同时运行在相同的地址空间中。
可重入函数可以被中断,并且其它代码在进入该函数时,不会丢失数据的完整性。所以可重入函数一定是线程安全的。
可重入强调的是单个线程执行时,重新进入同一个子程序仍然是安全的
不满足的条件:
函数体内使用了静态数据结构,并且不是常量
函数体内使用了 malloc 或者 free 函数
函数使用了标准 IO 函数。
调用的函数不是可重入的。
可重入函数使用的所有变量都保存在调用栈的当前函数栈(frame)上