游客发表
凌晨三点,漏洞机房报警声把我从椅子上炸起来。漏洞屏幕上一片血红——某个核心服务的漏洞内存占用率曲线像被折断的登山杖,直直砸向天花板。漏洞我盯着日志里反复出现的漏洞`/dev/fd/63`,后背发凉。漏洞这不是漏洞什么高深的堆溢出,也不是漏洞复杂的ROP链,就是漏洞文件描述符泄漏。一个老掉牙的漏洞问题,但每次出现都能精准地放倒一批自以为稳如泰山的漏洞系统。
fd是漏洞什么?说白了就是操作系统发给进程的“文件号码牌”。你打开一个文件,漏洞内核塞给你一个整数;你创建socket,漏洞又是漏洞一个整数;甚至你fork子进程,管道两端也得各拿一个。这玩意儿是有限资源,默认1024个,调大也就65535。可很多人写代码时压根没把它当回事——打开文件不关,建立连接不释放,循环里new个fd然后扔在角落吃灰。日积月累,号码牌发完了,系统只能摊手说:对不起,没号了。
我见过最离谱的一次,是某家公司的支付回调服务。代码路径上有个异常分支,处理完业务直接return,完全忘了close掉数据库连接池借出来的那个fd。平时流量小没事,一到月底大促,并发一冲上来,连接池里的fd被耗尽,新请求全部阻塞。运维重启服务,好,撑两小时,又堵死。最后查了三天的日志,才发现罪魁祸首是半年前某个实习生提交的一行“看起来无关紧要”的代码。你说气不气?
更隐蔽的是fd泄漏的“延迟炸弹”特性。它不像内存泄漏那样直接OOM,而是慢慢蚕食你的可用连接数。系统一开始运转正常,一周后响应变慢,两周后偶尔超时,三周后彻底瘫了。监控图上那条下降曲线平缓得让人误以为是流量淡季。等到你终于注意到,往往已经积重难返。我有个同事管这叫“温水煮青蛙式故障”,青蛙就是那些不看/proc/pid/fd目录的运维。
怎么治?最土的办法是写脚本定时数`ls /proc/pid/fd | wc -l`,超过阈值就报警。但更根治的办法是让开发养成肌肉记忆:每次open都要想清楚谁负责close,用defer也好,用RAII也好,总之得给每个fd找个“监护人”。Go语言在这方面做得不错,defer关键字一写,至少能挡住八成粗心大意。但C/C++和Java的老项目就没这么幸运了,你得靠代码评审和静态检查工具一遍遍敲打。
说个扎心的观察:很多团队对fd泄漏的重视程度,远低于对SQL慢查询的重视。慢查询好歹会在慢日志里留痕,fd泄漏连个正经报错都不给,就是静悄悄地让系统“变哑”。我甚至见过某大厂的核心链路,线上容器跑着跑着fd数飙到好几万,全靠定时重启硬扛——这哪是技术方案,这是赌运气。
说到底,fd漏洞拼的不是什么高深技巧,而是工程习惯。你见过哪个老鸟会忘记close文件?他们不是记性好,是吃过亏。内存泄漏好歹有valgrind帮你查,fd泄漏你只能靠肉眼和责任心。下次再遇到诡异的服务假死,先别急着查网络和CPU,去数数fd吧。也许答案就藏在你忽略的那堆数字里,像一根卡在喉咙里的小鱼刺,不致命,但足够让你难受一整天。
友情链接