
凌晨两点被监控叫醒的经历不知道各位运维有没有同感。那次是一台跑了三年的内网文件服务器突然报内存异常紧接着一个挂载在Windows共享目录上的进程直接core dump。日志翻了大半夜最后定位到Linux内核的CIFS/SMB客户端模块——具体来说是一个编号为CVE-2023-52751的漏洞。后来我越查越觉得有意思一次普通的共享文件夹访问内核里居然藏了一条从字符串截断到内存悬垂的完整事故链。这篇文章就把这条链路完整拆开。无论你是在服务器上挂载过SMB共享的运维还是研究Linux内核安全的爱好者或者只是好奇访问一个共享文件夹怎么会动到内存都可以顺着这篇文章走一遍。我会从漏洞本身讲起带着你沿着mount到DFS解析的路径看到那一行代码把内存释放掉的全过程最后给出自查和加固的方法。1. CVE-2023-52751是什么一个藏在CIFS客户端里的释放后使用1.1 漏洞档案性质、危害与影响范围CVE-2023-52751是Linux内核CIFS/SMB客户端实现中的一个Use-After-Free释放后使用漏洞具体出在fs/cifs/dfs_cache.c文件的smb2_dfs_query_nameinfo函数里。简单对比一下危害等级项目详情漏洞编号CVE-2023-52751影响模块fs/cifs/dfs_cache.ccifs.ko漏洞类型Use-After-Free释放后使用触发前提客户端挂载SMB共享且服务器返回超长DFS路径潜在危害内核崩溃拒绝服务特定条件下可能提权或代码执行修复状态2023年下半年合入主线各发行版已陆续backport这里有个容易混淆的点要先说清楚标题里的SMB和内核模块名里的CIFS经常让人懵。CIFS是SMB1协议的旧称Linux内核的cifs.ko模块虽然名字带cifs但早已实现了SMB2和SMB3协议。所以你在Linux上mount -t cifs挂载Windows共享或者Samba共享走的就是这个模块。CVE-2023-52751影响的是这个模块作为客户端时的解析逻辑不是服务器端。这很重要因为很多人一看到SMB漏洞就以为是服务器端口暴露的问题其实这次翻车的是访问别人的那一边。1.2 释放后使用到底是个什么状态要理解这个漏洞的杀伤力得先理解UAF。给你打个比方你把一把钥匙寄存在前台后来前台通知你房间退了钥匙已经回收但你的通讯录里还记着这把钥匙能开3号房。某天夜里你拿着过期的钥匙卡去开3号房门门是打开了但里面的东西早就不是原来那批了——可能是空房也可能住进了别人。内核里的UAF就是这样的一段内存被kfree或kvfree释放归还给系统但某个指针还指向它。后续再用这个指针读写小则读到脏数据大则直接让内核崩溃更坏的情况是内存已经被重新分配给其他对象你一写就把别人的数据踩坏了。具体到CVE-2023-52751被释放的是一段路径字符串的内存而踩坑的是一个字符串比较操作。有意思的是这个触发过程非常拧巴——路径太长了会出事但太长了本身是个错误条件这就有意思了。2. 从mount共享文件夹到内存崩溃SMB访问的完整内核链路2.1 一条SMB挂载命令背后的内核路径先在终端里敲一条最常见的挂载命令mount -t cifs //192.168.1.100/share /mnt/share -o usernameuser,passwordpass这条命令发出后用户态的mount.cifs工具会把参数传给内核接下来发生的事情就没用户态什么事了。内核CIFS客户端模块接管经过TCP连接445端口、SMB协商negotiate、会话建立session setup、树连接tree connect几个阶段最终把这个网络共享变成一个VFS挂载点。挂载成功之后你在/mnt/share目录下的每一次ls、cat、open都会通过VFS层进入CIFS文件系统的回调函数。也就是说对于Linux内核而言一个远端SMB共享和本地ext4分区没有本质区别——只不过每一次inode操作背后都跟着一次网络请求。这个抽象很方便但也意味着内核里负责处理路径解析、目录缓存、文件句柄的代码都要面对一个比本地文件系统更复杂的网络环境。2.2 DFS重解析共享文件夹里的文件夹跳转到这里为止都还只是常规流程。真正让CVE-2023-52751有机会发作的是DFS分布式文件系统机制。DFS是Windows服务器用来做共享文件夹跳转的用户访问的是\\server\share\folder但share这个共享名在服务器上其实是一个DFS入口它会把请求重定向到另一台服务器上的真实路径。你可能访问了一个入口实际数据却从三台不同的机器上取回来。这个机制在企业内网很常见管理员用它可以隐藏后端文件服务器的拓扑。Linux内核CIFS客户端要支持DFS就得在路径解析时做一次额外工作向服务器发送DFS查询请求拿回这个路径真正指向哪里的referral信息。这个逻辑就在fs/cifs/dfs_cache.c里。为了提高效率内核不会每次访问都去网上查一遍而是把查到的结果缓存起来。这个缓存由一系列cache entry组成每个entry包含一条路径字符串和对应的target列表。2.3 MAX_NAME_SIZE截断345字符的边界问题问题出在这个缓存机制的边界处理上。在dfs_cache.c里有一个路径上限MAX_NAME_SIZE数值是345。当服务器返回的DFS路径长度超过345字节时代码不会返回错误而是静默截断。先停一下想想服务器为什么会返回超长路径。正常场景下DFS路径不会那么长但谁能保证网络上碰到的每台SMB服务器都是正常的一台被病毒感染的服务器、一个写错了配置的Samba、一个故意构造恶意报文的攻击者都可能让路径长度失控。对一个客户端来说它不能假设对端永远是善意的——这正是网络安全里信任边界的概念。截断本身并不是漏洞截断之后还继续用截断结果做后续逻辑才是。这就好比你在银行填单子填到一半笔画太多写不下了柜员直接把你写了一半的单子拿去当全款凭证处理——后面的流程全都建立在一个残缺的数据上。3. 漏洞触发链路逐行拆解smb2_dfs_query_nameinfo里的现场还原3.1 内存分配的所有权转移现在进入正题。smb2_dfs_query_nameinfo这个函数的工作是根据客户端传入的路径向服务器要DFS referral信息然后把返回的路径整理成缓存entry。函数内部有一个data结构体其中>static int smb2_dfs_query_nameinfo(...) { ... data kvzalloc(sizeof(*data) namelen 1, GFP_KERNEL); ... >[ 1234.567890] BUG: unable to handle page fault for address: ffff888123456789 [ 1234.567890] #PF: supervisor read access in kernel mode [ 1234.567890] #PF: error_code(0x0000) - not-present page [ 1234.567890] PGD 800000012345067 P4D 800000012345067 PUD 800000012345067 PMD 0 [ 1234.567890] Oops: 0000 [#1] PREEMPT SMP PTI [ 1234.567890] CPU: 2 PID: 12345 Comm: kworker/u8:2 Tainted: G OE [ 1234.567890] RIP: 0010:strcasecmp0x12/0x40 [ 1234.567890] Call Trace: [ 1234.567890] ? smb2_dfs_query_nameinfo0x1df/0x3b0 [cifs] [ 1234.567890] ? cifs_reconnect_tcon0x2a/0x1d0 [cifs] [ 1234.567890] ? process_one_work0x1c8/0x360重点是Call Trace里出现smb2_dfs_query_nameinfo0x1df和strcasecmp这两个栈帧基本就能锁定是这个漏洞在作妖。如果内核开启了KASAN内核地址消毒器日志会更明确会直接报告BUG: KASAN: use-after-free in strcasecmp。这里有个实际的排查经验不是每次触发都会崩溃。UAF的特点是看运气——如果释放后的内存块还没被系统重新分配读出来的内容还是原来的字符串程序就能侥幸跑过去如果内存被重新分配并写入了其他数据比较结果就会错乱如果内存块被归还给物理内存管理并取消映射就会立刻触发缺页异常。所以你在生产环境里可能看到的不是稳定的crash而是偶发的、间歇性的系统异常。4. 官方补丁与受影响版本修复到底改了什么4.1 补丁diff阅读从共享指针到独立副本这个漏洞的修复提交核心思路是改变路径字符串的所有权归属。如果不好确定真实补丁的每一行细节我可以描述一下修复的本质把原来直接引用/截断复制的方式改成在需要保存路径时用kstrdup做一次独立的字符串拷贝同时在释放路径时先确认没有其他引用方避免悬垂。简单说补丁要解决的就是我在上一节说的一个内存、两个所有者问题。修复后的行为是函数从任何地方拿到路径都先做一次独立拷贝放进自己管理的内存区域。后续不管是缓存查找还是字符串比较操作的都是自己的副本。refresh worker释放它自己的那份内存时再怎么崩也不会波及正在执行中的线程。这个修复思路值得在代码评审里记一笔跨线程共享指针时要么加引用计数要么用副本。很多UAF都是因为在这里应该拷贝一份和这里应该持引用之间选了直接传指针不管了。4.2 版本影响与排查方法因为CVE-2023-52751的修复是在2023年下半年合入内核主线的所以内核主线6.6及之后版本包含修复。2023年第三季度之前的发行版内核如果没有额外backport大概率存在风险。Ubuntu、Debian、RHEL、SUSE、Rocky Linux等发行版的安全公告里都能搜到对应修复。最稳的做法不是看文章里的版本列表而是直接在目标机器上验证。这是我实际用过的一套排查顺序第一步确认内核版本uname -r第二步在Debian/Ubuntu系查当前内核的更新日志里有没有修复记录apt-get changelog linux-image-$(uname -r) 2/dev/null | grep -i CVE-2023-52751第三步在RHEL/Rocky/Alma系查内核包的changelogrpm -q --changelog kernel | grep -i CVE-2023-52751如果changelog里能搜到说明这个漏洞的修复已经进入了你当前安装或已安装的内核版本搜不到则说明需要更新内核。需要留意的是有些发行版的changelog里可能打的是cifs: fix use-after-free in DFS cache这类描述性文本而没有直接写CVE编号所以查的时候最好两个关键词都试试。4.3 无法立刻打补丁时的缓解措施现实里很多服务器不是说升级就能升级的。如果暂时不能重启、不能换内核可以从操作层面降低风险不挂载不受信任的SMB共享。漏洞要触发必须由一个恶意或异常的SMB服务器返回超长路径。客户端软件是没法自己凭空变出一条超长路径的。所以最直接的办法是管住访问谁。在防火墙上限制445和139端口的出站连接。如果业务上根本不需要访问外部SMB服务直接断掉出站SMB流量是最干净的缓解。配置挂载时使用noauto避免开机自动挂载。这样即使某个不安全的共享被写进了/etc/fstab重启后也不会自动连上。临时卸载cifs模块前提是当前没有任何SMB挂载在使用# 先确认没有正在使用的cifs挂载 mount | grep cifs # 再尝试卸载模块 modprobe -r cifs如果modprobe -r cifs返回Module cifs is in use说明还有挂载点占着模块不能硬来否则会直接影响到正在跑的业务。这里多说一句缓解措施永远只是缓冲不是终点。UAF类漏洞一旦被攻击者盯上很可能从内核崩溃演变成权限提升所以补丁一定要尽快打上。5. 实战自查你的Linux服务器有没有踩坑风险5.1 检查当前系统是否在使用CIFS/SMB打开终端执行mount | grep -i cifs mount | grep -E // lsmod | grep cifs如果输出里有//192.168.x.x/share这类挂载点或者lsmod结果里有cifs模块说明这台机器确实在用内核CIFS客户端。如果没有任何输出风险就小很多——漏洞代码路径没有被激活。值得注意的一个场景很多虚拟机里用共享文件夹功能比如VMware、VirtualBox的共享文件夹如果配置的是SMB/CIFS类型同样会走内核cifs模块。这个漏洞的触发面不只是传统意义上的挂载Windows共享任何经过cifs.ko共享的路径都可能受影响。5.2 排查历史日志中的可疑崩溃记录如果系统之前触发过UAFdmesg或journalctl里通常会留下线索。建议按关键词搜索journalctl -k | grep -iE cifs|dfs_cache|use-after-free|general protection fault dmesg | grep -iE smb2_dfs_query_nameinfo|strcasecmp|KASAN如果你能翻到Call Trace里出现了smb2_dfs_query_nameinfo基本就能确认这台机器曾踩进过这个漏洞的路径。即使没有崩溃只要日志里有cifs相关的异常路径解析记录也值得进一步追查。这里要说一个排查技巧UAF漏洞的偶发性会误导人。如果系统几周才崩一次而且崩溃前后都伴随SMB共享访问很多运维第一反应是网络不稳定或磁盘IO问题。建议把内核Oops日志当成侦探现场来看不要只盯着最终崩溃的应用进程多看看Call Trace里的内核模块归属。5.3 从服务器端看攻击面CVE-2023-52751是客户端漏洞但攻击者要利用它往往先得有一台恶意SMB服务器。所以自查时也别忘了从攻击者视角检查你的网络环境你的内网里有没有非法的SMB服务在监听445端口这可能是恶意设备在等别人连接。你的服务器有没有在fstab里配置了指向不可信IP的SMB挂载开机后会不会自动连上恶意服务器你有没有对外暴露SMB服务端口结合热词里的smb暴力破解如果你的Linux服务器自己开着Samba或ksmbd还要注意弱口令爆破问题——CVE-2023-52751解决的是访问共享时被打的问题被人暴力破解进共享是另一个攻击面。所以这里给一个完整的加固思路客户端及时打补丁服务端禁用不必要的SMB协议版本尤其是SMB1认证使用强密码并且通过防火墙限制445端口的访问来源。热词里提到win11访问win7共享文件夹找不到网络路径这类日常问题很多就是因为SMB版本不匹配——Windows 11默认禁用SMB1而老系统还在用SMB1。这种兼容性问题和CVE是两码事但提醒了我们一个点SMB协议族版本众多配置和使用时要搞清楚你实际用的是哪个版本。5.4 推荐修复顺序与验证最后给一个我实践过的修复顺序先拍快照或做系统备份虚拟机可以做快照物理机可以用发行版自带的系统备份工具。测试环境先升级内核跑一遍现有的SMB挂载和文件读写用例重点验证ls、cp、find这类会触发路径遍历的操作。确认测试环境无问题后再升级生产环境。升级后重启确认uname -r显示的目标版本。重启后重新挂载SMB共享并观察dmesg | grep -i cifs确认没有新的异常。升级后保留旧的/vmlinuz或/boot下的旧内核运行观察一周确认无异常再清理。这套顺序看起来朴素但真能避免很多补丁打上去业务挂了的坑。内核升级不像应用升级它直接替换了系统最底层的运行逻辑再小的改动都可能在特定硬件或特定文件系统组合下翻车。6. 从这次漏洞我们能带走什么SMB客户端的安全性再审视6.1 客户端侧的攻击面长期被低估我在文章开头就提到这次漏洞翻车的不是服务器端而是客户端。这个方向值得展开很多人的安全思路是我要守好我的服务端口——防火墙封445、关SMB1、改默认口令——但很少考虑我的机器作为客户端去访问别人的SMB服务时会被对方用畸形响应攻击。CVE-2023-52751其实就是这个问题的一个样本。恶意SMB服务器返回超长DFS路径客户端解析时崩掉。这提醒我们协议客户端的解析逻辑同样是要被审查的攻击面。你对端不可信时你的客户端解析器就是第一道防线。不仅仅是SMBHTTP客户端、DNS客户端、SSH客户端都存在类似情况——解析器的健壮性决定了你被恶意服务器坑害的概率。6.2 内核补丁管理不是能拖则拖每次写这类漏洞分析我都会强调补丁管理但这里想从另一个角度说Linux发行版都有LTS支持周期内核安全修复会通过backport的方式进入旧版本。所以内核版本旧不等于一定不安全关键看你用的发行版是否把安全修复同步到了你的内核版本里。这也是为什么我在前面反复强调用rpm -q --changelog或apt-get changelog去验证而不是只盯uname -r。定期阅读发行版的安全公告把内核安全更新纳入例行维护计划而不是等出事了再急急忙忙找补丁。经历过凌晨被报警叫醒的人都会理解这句话。6.3 我对这个漏洞的一点个人体会写到最后说点实在的。这个漏洞从技术上看不算复杂没有花哨的堆布局操作没有复杂的条件竞争窗口——它就是一个典型的内存所有权管理失误截断、缓存、后台释放三个看起来各自合理的行为叠加在一起变成了一个安全漏洞。几乎所有复杂系统的安全事件追到根上都是类似的小失误。我在排查过程中最有收获的一步不是最后看到崩溃栈的那一刻而是把DFS缓存和refresh worker的工作机制完整梳理清楚的过程。你只有理解了内存的来龙去脉才能理解漏洞为什么会在那个位置炸开。如果你今天读完了这篇文章至少记住一句当一个指针被传递到不同的执行上下文特别是后台线程时要想清楚这块内存到底是谁的。最后再分享一个小习惯我现在会在每台需要挂载SMB共享的服务器上把journalctl -k的记录单独转存一份到日志中心。内核日志平时没人看一旦出事它是还原现场的唯一线索。哪怕补丁打得再全保留好案发现场对后续排查和复盘都价值巨大。