竞态条件(race condition)是最常见的一类软件错误,只可能出现在多线程或者进程同时发生并有潜在交互的环境下(或其他异步处理,如UNIX信号量)
控制机器资源的额攻击者可以通过降低机器的运行速度来增加利用竞争条件的机会
以下给出一个java servlet程序:
import java.io.*
import java.servlet.*
import java.servlet.http.*
public class Counter extends HttpServlet{
intcount = 0;
publiccoid doGet(HttpServletRequest in,HttpServletResponseout)
throwsServletException{
out.setContentType("text/plain");
Printwriterp = out.getWriter();
count++;
p.println(count+ "hits so far");
}
}
包含了竞争条件,因为java servlet是多线程的。
假设A、B几乎同时访问,A在前,此时count是1,A在运行println之前B使count变为2,则A打印出了2
这个漏洞窗口不大,最多是几分之一秒
即使将计数器的增加语句放进打印语句中如:
p.println(++count+”hits si far”);
也无济于事,因为调用println以及给参数赋值都要花费时间
要想保证可靠,实现的主要策略是使相关的代码和数据“原子化”:原子化,就是指关联代码的执行是一个整体,操作执行时不会有其他事情发生。竞态条件发生的情况是程序员假设某些操作是原子的方式发生的。
将一个操作原子化,我们通常使用加锁原语,尤其是在多线程环境。
例如,修复前面代码的方法是 ,通过synchronized keyword使用servlet上的对象锁。Synchronized关键字阻止其控制的对象同时被多线程调用。例如,在一个java类中有10个synchronized方法,任何时候都只有一个线程能运行其中的一个方法。JVM的实现负责实施synchronized关键字的语义。
修改如下:
import java.io.*
import java.servlet.*
import java.servlet.http.*
public class Counter extends HttpServlet{
intcount = 0;
publicsynchronized void doGet(HttpServletRequest in,HttpServletResponse out) throwsServletException,
IOException{
out.setContentType("text/plain");
Printwriterp = out.getWriter();
count++;
p.println(count+"hitsso far");
}
}
本例中一个时间段只能有一个线程运行servlet,以为doGet是程序的入口。这种方案影响效率。所以我们要使原子化的代码(通常称为临界区(criticalsection))尽可能小
可以看到,当两个或多个事件发生时,并且后一个操作依赖于前一个操作,竞争条件的情况就有可能出现
当涉及可利用的文件系统的竞争条件时,一下几个条件必须要成立:
首先,攻击者必须能访问本地机器,无论是否合法,其次,有竞争条件的程序必须以root的EUID运行,第三,程序必须在竞争条件存在的时间内保持这个EUID
避免TOCTOU的一个方法是,避免直接使用文件名的系统调用,而是代之以使用文件句柄或文件描述符的调用。使用文件描述符或文件指针,可以保证对文件的操作在第一次处理它之后不会被暗中改变