☰
  • 首页
  • 规则分类
  • 项目介绍
search
•••

访问共享数据应遵循合理的同步机制

16.1 ID_dataRaces
目录 › next › previous

如果一份数据同时被多个线程、进程或中断处理过程读写,会产生不确定的结果,这种情况称为“数据竞争(data race)”,会导致标准未定义的行为,应落实合理的同步机制来控制访问共享数据的先后顺序。

示例:

int foo() {
    static int id = 0;
    return id++;        // Data races in multithreading
}

这个函数意在每次被调用都可以返回不同的整数,但如果多个线程同时执行 id++,会使读取、计算、写入等步骤交织在一起,得到错误的结果,这是一种典型的数据竞争。

应改为:

int foo() {
    static atomic<int> id(0);
    return id.fetch_add(1);    // OK
}

其中 atomic 是 C++ 标准原子类,fetch_add 将对象持有的整数增 1 并返回之前的值,这个过程不会被多个线程同时执行,只能依次执行,从而保证了返回值的唯一性和正确性。

对共享数据访问次序的控制称为“同步(synchronization)”,可使用锁、条件变量、原子操作等方法实现对线程的同步。与共享数据相关,但未落实同步机制的函数不应在多线程环境中使用,如:

asctime         // use asctime_r or asctime_s instead
ctime           // use ctime_r or ctime_s instead
localtime       // use localtime_r or localtime_s instead
gmtime          // use gmtime_r or gmtime_s instead
strtok          // use strtok_r or strtok_s instead
strerror        // use strerror_r or strerror_s instead
tmpnam          // use tmpnam_r or tmpnam_s instead
setlocale       // use mutex to protect multithreaded access
rand, srand     // use random, srandom or BCryptGenRandom instead

与线程同步不同,中断处理过程的同步较为特殊,可参见 ID_sig_dataRaces 的进一步讨论。

考虑比数据竞争更高层面的问题,如果程序的正确性依赖进线程处理数据的特定时序,一旦这种特定时序被打破便会产生错误或漏洞,攻击者可以抢在某关键过程前后通过修改共享数据达到攻击目的,这种情况称为“竞态条件(race condition)”,如:

int* p = get_shared();   // #0, ‘p’ points to shared data
if (*p == 0) {           // #1, ‘*p’ is unreliable
    ....
}
else if (*p == 1) {      // #2, ‘*p’ is unreliable
    ....
}
else {                   // #3
    ....
}

如果 p 指向共享数据,那么攻击者可以通过修改共享数据实现对程序流程的劫持,比如在 #0 处 *p 的值本为 0,攻击者在 #1 之前改变 *p 的值,迫使流程向 #2 或 #3 处跳转。

关于竞态条件的进一步讨论可参见 ID_TOCTOU、ID_forbidSignalFunction 等规则。

相关

ID_sig_dataRaces ID_sig_nonAsyncSafeCall

依据

ISO/IEC 9899:2011 5.1.2.4(3)-undefined ISO/IEC 9899:2011 5.1.2.4(20)-undefined ISO/IEC 9899:2011 5.1.2.4(25)-undefined

参考

CWE-362 C++ Core Guidelines CP.2 SEI CERT CON33-C SEI CERT CON43-C
Copyright©2024 360 Security Technology Inc., Licensed under the Apache-2.0 license.