什么是TOC TOU
TOCTOU 是计算机安全和操作系统领域的一个术语,全称是 Time-Of-Check to Time-Of-Use,翻译过来就是“检查到使用的时间差问题”。
它是一种竞态条件(race condition)漏洞,指程序在检查某个状态和实际使用该状态之间存在时间间隙,攻击者可能利用这个间隙改变状态,从而导致安全问题。
- Time-Of-Check (TOC):程序检查某个条件或状态,比如检查文件权限、检查内存是否可用。
- Time-Of-Use (TOU):程序使用这个状态或资源,比如打开文件、写入数据。
- TOCTOU问题:如果在TOC和TOU之间,资源状态被其他进程或线程修改,就可能引发错误或安全漏洞。
比如这段代码:
public void unlock(String key) {
RLock lock = redissonClient.getLock(key);
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
代码的逻辑很简单,就是检查当前线程是否持有锁,如果持有就释放锁。但是业务上原先加的锁是有设置超时时间的,这时候有时候会偶发出现:
attempt to unlock lock, not locked by current thread by node id: 64677d94-657d-4e01-928e-3b198e7b15bc thread-id: 348
就是因为在 unlock 的时候,锁已经过期了,就抛出异常。
解决思路
- 原子操作:尽量让检查和使用在同一原子操作中完成,比如操作系统提供的原子文件打开接口 open(O_CREAT | O_EXCL)。
- 锁机制:多线程环境用锁防止并发修改。
- 验证输入和状态:在使用前再验证,减少漏洞窗口。
- 避免依赖外部可修改状态:比如符号链接、共享文件、临时文件等。
比如当前可以使用 Redis 的 Lua 脚本来实现原子操作:
public void unlockAtomic(String key, String lockValue) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redissonClient.getScript(StringCodec.INSTANCE)
.eval(RScript.Mode.READ_WRITE, luaScript, RScript.ReturnType.INTEGER,
Collections.singletonList(key), lockValue);
}
或者有一个更简单的处理方案,直接捕获 IllegalMonitorStateException 异常不处理:
public void unlock(String key) {
RLock lock = redissonClient.getLock(key);
try {
lock.unlock();
} catch (IllegalMonitorStateException e) {
// 当前线程未持有锁,安全忽略或记录日志
System.out.println("尝试释放锁失败,锁不是当前线程持有,忽略: " + key);
}
}






