[转]来聊聊条件竞争
条件竞争,无非就是多个线程(action)同时访问一个数据(data)没有进行加锁操作,而产生非预期结果的行为。
开发者在进行代码开发时常常倾向于认为代码会以线性的方式执行,但他们忽视了并行服务器会并发执行多个线程,这就会导致意想不到的结果。
存在以下条件的事务,可能存在条件竞争:
-
并发。至少得存在两个并发执行流。
-
共享对象。即多个并行任务访问同一个数据。
-
改变数据(写操作)。至少有一个进程对数据进行了改变。如果所有进程执行的都是读操作,那就不会有条件竞争错误了。
举个最简单的条件竞争例子:
$cnt=file_get_contents('count.txt');
//count.txt 初始值为0
$cnt+=1;
echo "这个页面已经被访问".$cnt."次了";
file_put_contents('count.txt',$cnt);
在这个代码里,php使用file_get_contents读取count.txt文本,将文本的值进行“+1”操作并进行输出。最后将这个“+1”的值写回这个文本中。
我们在访问这个页面的时候,页面就可以统计这个网页被访问的次数了。
我们有些时候会为了某些目的,去写一个脚本疯狂访问这个页面。但是当我们去让脚本替我们访问1000次的时候,可能最后输出也就几百次(甚至几次)......
怎么回事呢?条件竞争了呗。
可能,在线程0执行到代码第七行的时候($cnt=2),同时有300个进程也在尝试写访问“count.txt”这个文档,它们的$cnt可能不尽相同,也可能都相同。存入的值也就五花八门了。
在存入时,可能会出现互相争抢资源的情况,
比如0号线程存入了个1,
同时4号线程存入了个3,
19号线程执行的稍微慢些,存入了个1......
或者
在2号线程对文件写入时,文件的值刚被删除,59号线程就读取到了这个空文件的值......然后一切又从零开始了
来看看真实场景下出现的条件竞争吧
header("Content-Type:text/html;charset=utf-8");
$filename = $_FILES['file']['name'];
$ext = substr($filename,strrpos($filename,'.') + 1); #后缀
$path = 'uploads/' . $filename;
$tmp = $_FILES['file']['tmp_name'];
if(move_uploaded_file($tmp, $path)){
if(!preg_match('/php/i', $ext)){ #判断后缀是否为php
echo 'upload success,file in '.$path;
}else{
unlink($path); #已经上传后判断若是PHP则删除
die("can't upload php file!");
}
}else{
die('upload error');
}
这道题,曾是一道17年的CTF题,用户上传文件到服务器上,如果检测到已经上传成功的文件扩展名是.php,那就unlink(删除)它。
在执行完move_uploaded_file之后,执行unlink之前,此时这个php文件是已经保存到了web服务器上的,并且我们能够访问。
就这样,我们弄出了这样的一个php脚本,并打算把它上传到服务器上......
$content='<?php system($_GET["c"]);?>';
file_put_contents('test.php',$content);
然后一头一遍又一遍的往这个接口去上传这个脚本,另一头去尝试访问服务器上这个脚本的名称(假设上传成功了的话)和访问这个脚本所生成的一句话木马文件(test.php),如果访问后两个脚本服务端都返回脚本存在,那......
咱们就卡bug成功了呗
总之,我个人认为,条件竞争就是在卡bug。
黑客们在赌,服务器是否会有充分的时间来处理每一条指令。会不会被我们钻到空子
正如我在烤盘饭打工的时候,当前台同时取餐过多时,我们可能会因为没有充分的反应时间,导致忘记回收号牌,出错餐,牌子和夹子对不上等等问题。
这,就是条件竞争。
条件竞争如何防御?
-
给服务器足够的时间处理这些请求。(不妨强迫客户端慢一点请求借口......)
-
加锁,在向数据库做写入操作的同时,给这行数据表加写锁(别人都不能读取和写入这行数据)
-
调优代码逻辑,对不稳登的文件,一定要检查好再存储在服务器上。
反正,应对条件竞争,我们能做的,核心就是:
慢慢来,慢慢处理
“慢慢来”,不仅仅只对条件竞争生效,对感情一样有效。
祝愿(000 0000 01 10)有(1011 001 01 10)情(1011 001)人(1001 00 10)终(1010 0000 0 10)成(1100 10 111)眷(1101 00 01 10)属(000 0000 111 011)(说谁俩谁俩心里清楚)