3 多线程
3.1 【必须】变量应确保线程安全性
当一个变量可能被多个线程使用时,应当使用原子操作或加锁操作。
// Bad
char g_somechar;
void foo_thread1() {
g_somechar += 3;
}
void foo_thread2() {
g_somechar += 1;
}
对于可以使用原子操作的,应当使用一些可以确保内存安全的操作,如:
// Good
volatile char g_somechar;
void foo_thread1() {
__sync_fetch_and_add(&g_somechar, 3);
}
void foo_thread2() {
__sync_fetch_and_add(&g_somechar, 1);
}
对于 C 代码,C11
后推荐使用 atomic 标准库。
对于 C++代码,C++11
后,推荐使用 std::atomic
。
关联漏洞:
高风险-内存破坏
中风险-逻辑问题
3.2 【必须】注意signal handler导致的条件竞争
竞争条件经常出现在信号处理程序中,因为信号处理程序支持异步操作。攻击者能够利用信号处理程序争用条件导致软件状态损坏,从而可能导致拒绝服务甚至代码执行。
- 当信号处理程序中发生不可重入函数或状态敏感操作时,就会出现这些问题。因为信号处理程序中随时可以被调用。比如,当在信号处理程序中调用
free
时,通常会出现另一个信号争用条件,从而导致双重释放。即使给定指针在释放后设置为NULL
,在释放内存和将指针设置为NULL
之间仍然存在竞争的可能。 - 为多个信号设置了相同的信号处理程序,这尤其有问题——因为这意味着信号处理程序本身可能会重新进入。例如,malloc()和free()是不可重入的,因为它们可能使用全局或静态数据结构来管理内存,并且它们被syslog()等看似无害的函数间接使用;这些函数可能会导致内存损坏和代码执行。
// Bad
char *log_message;
void Handler(int signum) {
syslog(LOG_NOTICE, "%s\n", log_m_essage);
free(log_message);
sleep(10);
exit(0);
}
int main (int argc, char* argv[]) {
log_message = strdup(argv[1]);
signal(SIGHUP, Handler);
signal(SIGTERM, Handler);
sleep(10);
}
可以借由下列操作规避问题:
- 避免在多个处理函数中共享某些变量。
- 在信号处理程序中使用同步操作。
- 屏蔽不相关的信号,从而提供原子性。
- 避免在信号处理函数中调用不满足异步信号安全的函数。
关联漏洞:
高风险-内存破坏
中风险-逻辑问题
3.3 【建议】注意Time-of-check Time-of-use (TOCTOU) 条件竞争
TOCTOU: 软件在使用某个资源之前检查该资源的状态,但是该资源的状态可以在检查和使用之间更改,从而使检查结果无效。当资源处于这种意外状态时,这可能会导致软件执行错误操作。
当攻击者可以影响检查和使用之间的资源状态时,此问题可能与安全相关。这可能发生在共享资源(如文件、内存,甚至多线程程序中的变量)上。在编程时需要注意避免出现TOCTOU问题。
例如,下面的例子中,该文件可能已经在检查和lstat之间进行了更新,特别是因为printf有延迟。
struct stat *st;
lstat("...", st);
printf("foo");
if (st->st_mtimespec == ...) {
printf("Now updating things\n");
UpdateThings();
}
TOCTOU难以修复,但是有以下缓解方案:
- 限制对来自多个进程的文件的交叉操作。
- 如果必须在多个进程或线程之间共享对资源的访问,那么请尝试限制”检查“(CHECK)和”使用“(USE)资源之间的时间量,使他们相距尽量不要太远。这不会从根本上解决问题,但可能会使攻击更难成功。
- 在Use调用之后重新检查资源,以验证是否正确执行了操作。
- 确保一些环境锁定机制能够被用来有效保护资源。但要确保锁定是检查之前进行的,而不是在检查之后进行的,以便检查时的资源与使用时的资源相同。
关联漏洞:
高风险-内存破坏
中风险-逻辑问题