条件竞争漏洞
条件竞争:
系统中,最小的运算调度单位是线程,而每个线程又依附于一个进程,条件竞争则是多进程或多线程对一个共享资源操作,因为操作顺序不受控的时候所产生的问题。
进程:
在计算机操作系统中,进程是指一个正在运行的程序的实例。它是操作系统进行资源分配和调度的基本单位。
进程的内存空间布局:
当操作系统启动一个进程时,会为它分配独立的虚拟内存空间。这个空间通常被划分为以下几个主要部分:
- 代码段(Text/Code Segment): 存储程序执行的机器指令。这部分通常是只读的,以防止程序意外修改自己的指令。
- 数据段(Data Segment): 存储程序中已经初始化的全局变量和静态变量。
- BSS 段(BSS Segment): 存储程序中未初始化的全局变量和静态变量。在程序开始执行前,操作系统会将这块区域清零。
- 堆(Heap): 用于程序运行时的动态内存分配堆的大小不固定,通常向高地址方向增长。
- 栈(Stack): 用于存储局部变量、函数参数、局部代码块的上下文以及函数的返回地址。栈通常向低地址方向增长,由编译器自动分配和释放。
操作系统如何管理进程?
为了管理数量繁多的进程,操作系统会为每一个进程创建一个数据结构,称为 进程控制块 PCB。在 Linux 系统中,这个结构被称为 task_struct。PCB 包含了操作系统管理该进程所需的所有信息,主要包括:
- 进程标识符(PID, Process ID): 每个进程独一无二的数字编号。
- 进程状态(State): 记录进程当前处于什么阶段(如:运行中、就绪、阻塞/等待、终止)。
- CPU 寄存器状态: 当进程被暂停执行,发生上下文切换时,它的 CPU 寄存器(包括程序计数器 PC、栈指针 SP 等)的值会被保存在这里,以便下次恢复运行时能接着断点继续执行。
- 内存管理信息: 页表指针等,用于将进程的虚拟地址映射到物理内存。
- 资源清单: 进程打开的文件描述符(File Descriptors)、网络连接等。
线程
线程(Thread)是操作系统调度和分配 CPU 执行时间的最小单位。你可以把它理解为进程内部的一条独立执行流,可称为“轻量级进程(Lightweight Process)”
- 线程的是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能;
- 线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位;
- 线程自己基本上不拥有系统资源,只拥有在运行中必不可少的资源 (如程序计数器、一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源;
- 线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据;
协程
协程(Coroutine),又称为微线程,是一种用户态的轻量级线程。
如果说进程和线程的调度都是由操作系统(OS)内核强制接管的,那么协程的调度则是完全交由程序代码本身来控制的。
让我们继续用“工厂”来打比方:
- 进程(Process):工厂。
- 线程(Thread):工厂里的工人。操作系统负责分配工人去哪个流水线。
- 协程(Coroutine):当一个工人在烤箱前等待零件加热(I/O 阻塞)时,他不会傻站着,而是主动放下当前的活,转头去另一条流水线上拧螺丝;等烤箱“叮”了一声(I/O 完成),他再回来接着处理加热好的零件。
- 协程通过在线程中实现调度,避免了陷入内核级别的上下文切换造成的性能损失,进而突破了线程在 IO 上的性能瓶颈;
- 协程拥有自己的寄存器上下文和栈;
- 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销;
并发编程
- 目的:并发编程在实际情况是为了提高执行效率,提高系统的利用率;
- CPU 的速度远快于各种 IO,而这也导致了 CPU 在很多时候,都是在持续等待中;
- 然而如果利用分时的机制,这样可以让 CPU 将不同时间片分发给不同的 task,这样就能大大的提示 CPU 的利用率;
而且这种机制也可以让多个程序分开执行不同任务;
- 从整个程序执行角度来看,程序执行时可以看作是对输入的数据进行计算处理然后输出到特定的设备中
- 当其中的一个环节正在执行的时候其他环节就会挂起;
- 一旦输入阻塞,即 IO 等待读入数据,那么已读入的数据也不能得到处理,已处理的数据也不能输出,这就造成了 CPU 的闲置;
- 如果这三个步骤可以并发执行的话,即使 IO 在等待输入,CPU 仍然可以对已在内存中的数据做计算处理,结果也可以正常输出;
- 这就提高了 CPU 的利用率,不会因为输入输出的阻塞,导致 CPU 的计算能力被浪费;
- 当有多种类型的任务执行时,为每种任务单独编写程序,比编写混杂在一起的所有任务的程序,要简单的多;
pthread_create 与 pthread_join
这两个函数在多线程编程中是形影不离的搭档。简单来说,它们的关联就像是”分配任务”与”等待汇报并清理工位”
我们可以从以下三个核心维度来理解它们之间密不可分的关联:
1. 生命周期管理:创建与回收
- pthread_create (产生):它的作用是向操作系统申请资源(比如分配线程栈、TLS 线程局部存储等),并启动一个新的线程去独立执行指定的函数。
- pthread_join (回收):当子线程执行完毕退出后,它其实并没有彻底消失,而是会进入一种类似进程中的“僵尸 Zombie 状态”,保留着它的退出状态码和部分系统资源。pthread_join 的核心作用之一就是去收集这个退出状态,并彻底释放该线程占用的所有残留系统资源。
- 关联:就像
malloc对应free,或者是多进程里的fork对应waitpid一样。如果一个线程被 pthread_create 出来,主线程就必须调用 pthread_join,否则会导致内存和系统资源泄漏。
2. 执行流程的同步:异步并发与强制等待
- pthread_create 调用后会立刻返回,主线程和新创建的子线程会开始并发且异步执行,各跑各的。
- 但是在很多业务场景下,主线程需要等待子线程把活干完,才能继续往下走。这时候主线程调用 pthread_join (目标线程ID),主线程就会阻塞,暂停执行在这个函数上。直到目标线程彻底运行结束,pthread_join 才会返回,主线程才会继续执行下一行代码。
- 关联:pthread_join 是协调多个并发线程执行顺序的最基础手段,把并行的乱序执行强行拉回到同步轨道上
3. 数据的传递:获取子线程的执行结果
- pthread_create 可以通过最后一个参数向子线程传递参数。
- 子线程执行完毕时,可以通过 return 或者 pthread_exit() 返回一个指针(通常指向运算结果)。
- pthread_join 的第二个参数(一个二级指针)就是用来接收子线程返回的这个结果指针的。
例举一个有关两个函数的例子:
1 | if ( pthread_create(&newthread, 0, start_routine, 0) < 0 ) |
- 分配任务:主线程通过
pthread_create创建了newthread,让它去执行start_routine函数 - 强制等待:紧接着主线程立刻调用了
pthread_join(newthread, 0)。这意味着主线程啥也不干了,就死死卡在这里,等待函数操作完毕并退出。 - 防止程序提前崩溃:如果这里没有
pthread_join,主线程在pthread_create后会直接执行 return 0,导致整个进程直接退出。进程一旦退出,里面的所有子线程不管有没有执行完,都会被操作系统直接强行杀死。所以这里的join是为了保证程序的菜单能够持续运行并接收用户输入。
条件竞争产生的条件
并发,即至少存在两个并发执行流,这里的执行流包括线程、进程、任务等级别的执行流;
共享对象,即多个并发流会访问同一对象
常见的共享对象有共享内存、文件系统、信号,这些共享对象是用来使得多个程序执行流相互交流
改变对象,即至少有一个控制流会改变竞争对象的状态;如果程序只是对对象进行读操作,那么并不会产生条件竞争;
这里例举一个例子:
1 | /* 引入 POSIX 线程(Pthreads)库的头文件。 |
一般来说,我们可能希望按如下方式输出:
1 | ➜ 005race_condition ./example1 |
但是,由于条件竞争的存在,最后输出的结果往往不尽人意:
1 | ➜ 005race_condition ./example1 |
1 | 之所以会出现这么多 8, |
仔细思考一下条件竞争为什么可能会发生呢?以下面的为具体的例子
- 程序首先执行了 action1,然后执行了 action2。其中 action 可能是应用级别的,也可能是操作系统级别的。正常来说,我们希望程序在执行 action2 时,action1 所产生的条件仍然是满足的。
- 但是由于程序的并发性,攻击者很有可能可以在 action2 执行之前的这个短暂的时间窗口中破坏 action1 所产生的条件。这时候攻击者的操作与 action2 产生了条件竞争,所以可能会影响程序的执行效果。

所以我认为问题的根源在于程序员虽然假设某个条件在相应时间段应该是满足的,但是往往条件可能会在这个很小的时间窗口中被修改。虽然这个时间的间隔可能非常小,但是攻击者仍然可能可以通过执行某些操作(如计算密集型操作,Dos 攻击)使得受害机器的处理速度变得相对慢一些。
程序详细运行过程:
1 | 数据竞争(Data Race) |
互斥锁保护:
1 |
|
更新: 2026-04-19 11:32:54
原文: https://www.yuque.com/idcm/wnemg9/fk8msxuai0u2ggno