尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
nf_tables 的 verdict 被读了两次:一个十年未闭合的位域错配
2024 年 1 月 20 日Florian Westphal 提交了f342de4e2f33标题是 “netfilter: nf_tables: reject QUEUE/DROP verdict parameters”。提交信息第二行写着This reverts commit e0abdadcc6e1.被回退的提交来自 2014 年 2 月。在解释完问题之后他补了一句Its not clear to me why this commit was made.一位维护者在十年后回退一个提交并且把自己没搞清原意这件事写进了提交信息。这句话通常被当作这段历史的注脚引用但它指向一个更有用的问题一个 2014 年被接受、在十年里没有被发现的改动究竟把什么校验掉了。一个值同时装两种东西verdict 是钩子函数处理完一个包之后返回的处置决定常见的取值是放行NF_ACCEPT、丢弃NF_DROP、交给用户态队列NF_QUEUE。netfilter 的 verdict 不是一个枚举而是一个 32 位整数字段被复用/* Responses from hook functions. */#defineNF_DROP0#defineNF_ACCEPT1#defineNF_STOLEN2#defineNF_QUEUE3#defineNF_REPEAT4#defineNF_STOP5/* Deprecated, for userspace nf_queue compatibility. *//* we overload the higher bits for encoding auxiliary data such as the queue * number or errno values. Not nice, but better than additional function * arguments. */#defineNF_VERDICT_MASK0x000000ff/* extra verdict flags have mask 0x0000ff00 */#defineNF_VERDICT_FLAG_QUEUE_BYPASS0x00008000/* queue number (NF_QUEUE) or errno (NF_DROP) */#defineNF_VERDICT_QMASK0xffff0000#defineNF_VERDICT_QBITS16#defineNF_QUEUE_NR(x)((((x)16)NF_VERDICT_QMASK)|NF_QUEUE)#defineNF_DROP_ERR(x)(((-x)16)|NF_DROP)注释里那句 “Not nice, but better than additional function arguments”是设计者的自我评价它承认了这次字段复用。低 8 位放分支高 16 位放“queue number 或 errno”中间那 8 位留给 flag。两个编码宏把这件事写得更明白。NF_QUEUE_NR(x)把队列号左移 16 位再与上NF_QUEUENF_DROP_ERR(x)把 errno 取负后左移 16 位再与上NF_DROP。也就是说同一个高 16 位区域的语义取决于低 8 位是谁低 8 位是NF_QUEUE时它是队列号是NF_DROP时它是 errno。这个约定有一个正当的使用者。nft_queue是把包交给用户态队列的那个 nftables 表达式它在求值时正是这么构造 verdict 的retNF_QUEUE_NR(queue);if(priv-flagsNFT_QUEUE_FLAG_BYPASS)ret|NF_VERDICT_FLAG_QUEUE_BYPASS;regs-verdict.coderet;队列号左移 16 位与上NF_QUEUE需要时再把中间 flag 区的NF_VERDICT_FLAG_QUEUE_BYPASS或上去最后写进regs-verdict.code。高 16 位与 flag 区都有正当的生产者而生产者是表达式本身。这一点在后面的分析里会用到。于是这次的 32 位里一个值的不同部分有不同的含义所谓位域是把同一个整数的不同比特段当作不同字段来用用掩码取出低位段、用右移取出高位段读的是同一个值的不同部分。位 31 . . . . . . . . . 16 15 14 . . . . . . 8 7 . . . . . . 0 ┌───────────────────────┬──────────────┬───────────────┐ │ NF_VERDICT_QMASK │ FLAG 区 │ NF_VERDICT_ │ │ 0xffff0000 │ 0x0000ff00 │ MASK 0x000000ff│ │ queue number / errno │ │ NF_DROP 0 │ │ │ │ NF_ACCEPT 1│ │ │ │ NF_QUEUE 3 │ └───────────────────────┴──────────────┴───────────────┘ ▲ ▲ │ │ ┌─────────┴──────────┐ ┌────────────┴────────────┐ │ 消费点读这里 │ │ 准入点校验这里 │ │ nf_hook_slow() │ │ nft_verdict_init() │ │ NF_DROP_GETERR(v) │ │ v NF_VERDICT_MASK │ │ -(v 16) │ │ 2014 年之后 │ │ 定义在内部头文件 │ │ │ └────────────────────┘ └────────────────────────┘解码高 16 位的那个函数不在用户态头文件里。NF_DROP_GETERR定义在include/linux/netfilter.h而不是include/uapi/linux/netfilter.hstaticinlineintNF_DROP_GETERR(intverdict){return-(verdictNF_VERDICT_QBITS);}这一点在后面很重要。把用户态字节解码成内核信任值的解码器本身不属于用户态 ABI。解析 netlink 属性的代码要按正确方式校验得去调用一个定义在内部头文件里的辅助函数它无法像NF_VERDICT_MASK那样直接在自己的文件里用手边的宏完成判断。这个值从哪里进来在讲消费点之前先把这个值从用户态字节走到 skb verdict 的路径确认一遍。 这条路径上的四个词先交代netfilter 是内核里的包处理框架hook 是包在内核处理路径上经过的检查点skb 是内核中代表一个正在被处理的网络包的数据结构netlink 是用户态向内核下发网络配置这里是 nftables 规则的通道。nftables 规则里的“立即”动作由nft_immediate表达式承担它的初始化函数调用nft_data_init()errnft_data_init(ctx,priv-data,desc,tb[NFTA_IMMEDIATE_DATA]);nft_data_init()按数据类型分派verdict 走这一支}elseif(tb[NFTA_DATA_VERDICT]ctx!NULL){if(desc-type!NFT_DATA_VERDICT)return-EINVAL;errnft_verdict_init(ctx,data,desc,tb[NFTA_DATA_VERDICT]);}也就是说nft_verdict_init()是 netlink 属性进入内核 verdict 结构的唯一校验点它前面只有一个类型判断。再看准入点内部的校验面。它做的第一件事是从嵌套属性里取出NFTA_VERDICT_CODE按大端 32 位读data-verdict.codentohl(nla_get_be32(tb[NFTA_VERDICT_CODE]));用户态提供的就是一个完整的 32 位整数内核这边不做拆分直接放进data-verdict.code。随后是那个switch校验面就是它。这个 switch 的完整改动在下一节以 diff 给出它的判断依据只是 verdict 的低 8 位。函数最后一步记录长度desc-lensizeof(data-verdict);描述符记下的是整个 verdict 结构的长度不是“有效位”的长度。这个细节与前面的分析一致准入点把 verdict 当作整体来存储和传递而在校验那一步只看了低 8 位。存储与传递以 32 位为单位校验却是 8 位为单位。求值阶段则是简单搬运voidnft_immediate_eval(conststructnft_expr*expr,structnft_regs*regs,conststructnft_pktinfo*pkt){conststructnft_immediate_expr*privnft_expr_priv(expr);nft_data_copy(regs-data[priv-dreg],priv-data,priv-dlen);}校验过的数据被原样复制进寄存器链求值结束后作为 skb 的 verdict 返回给 hook。从nft_verdict_init()通过到nf_hook_slow()读到它中间没有第二次检查。这个结构决定了准入点的分量它不是一个可以出错的环节之一而是唯一的那个环节。消费点怎么用这个值nf_hook_slow()是决定怎么处理 skb 的地方。函数开头的注释写明了调用约定/* Returns 1 if okfn() needs to be executed by the caller, * -EPERM for NF_DROP, 0 otherwise. Caller must hold rcu_read_lock. */返回值 1 表示“继续执行调用方传入的okfn”也就是这个包还要往下走返回值 0 表示“已处理完不需要调用方做事”负值表示丢弃。核心循环是这样for(;se-num_hook_entries;s){verdictnf_hook_entry_hookfn(e-hooks[s],skb,state);switch(verdictNF_VERDICT_MASK){caseNF_ACCEPT:break;caseNF_DROP:kfree_skb_reason(skb,SKB_DROP_REASON_NETFILTER_DROP);retNF_DROP_GETERR(verdict);if(ret0)ret-EPERM;returnret;caseNF_QUEUE:retnf_queue(skb,state,s,verdict);if(ret1)continue;returnret;caseNF_STOLEN:returnNF_DROP_GETERR(verdict);default:WARN_ON_ONCE(1);return0;}}两条读取路径的差别是这段代码的关键。选分支时用的是verdict NF_VERDICT_MASK只看低 8 位进入NF_DROP分支之后交给NF_DROP_GETERR的是未掩码的完整值函数内部右移 16 位。还有一层NF_VERDICT_FLAG_QUEUE_BYPASS落在中间那 8 位的 flag 区0x0000ff00而这个区域在 2014 年之后同样不受准入校验约束。也就是说改动放开的范围不止高 16 位还包括一个本来留给标志位的区域而nft_queue会往这个区域里写字。 这个标志位的用途由nft_queue按表达式配置写入置位时表示该队列的包绕过正常的队列处理路径。公开材料没有说明用户态伪造这一位会产生什么具体后果能确认的是它也属于“被读取但不被准入校验覆盖”的区域之一。NF_DROP_GETERR在这段代码里出现了两次。一次在NF_DROP分支一次在NF_STOLEN分支。NF_STOLEN的语义是“这个包不再由 netfilter 继续处理”函数直接把高 16 位的解码结果作为返回值交出去。也就是说同一个解码器承担两个分支的返回值计算而这两个分支都不再对高 16 位做任何检查。也就是说一个值的两个部分在这里被分别消费低 8 位决定走哪条路高 16 位决定这条路的返回值。NF_DROP分支里kfree_skb_reason已经把 skb 释放掉随后NF_DROP_GETERR基于高 16 位算出一个返回值交给调用方。这个设计的前提是高 16 位受到约束。注释里那句 “-EPERM for NF_DROP, 0 otherwise”说明调用方预期拿到的只有负值或零而返回 1 是“继续处理”的信号。NF_STOLEN分支同样把返回值交给NF_DROP_GETERR同样依赖这个前提。关于这处约束缺失的具体后果只按来源陈述不做自行推演。CVE-2024-1086 的描述写的是The nft_verdict_init() function allows positive values as drop error within the hook verdict, and hence the nf_hook_slow() function can cause a double free vulnerability when NF_DROP is issued with a drop error which resembles NF_ACCEPT.修复提交的描述是core.c:nf_hook_slow assumes that the upper 16 bits of NF_DROP verdicts contain a valid errno, i.e. -EPERM, -EHOSTUNREACH or similar, or 0.Due to the reverted commit, its possible to provide a positive value, e.g. NF_ACCEPT (1), which results in use-after-free.两段放在一起可以得到一个不需要额外推演的结论返回值由高 16 位决定而取值区间不受约束时“返回值恰好落在调用方视为继续处理的取值上”就成为一种可能。至于具体哪个输入常数值会走到那一步这里不从源码反推因为这一步的算术需要连同调用方对返回值的完整处理一起验证而我没有做那一步核对。这条链子是这样接起来的包进入NF_DROP分支时已经被kfree_skb_reason释放返回值 1 在nf_hook_slow的调用约定里表示“这个包还要继续往下走”于是调用方协议栈里继续处理这个包的那一层会接着使用这块内存。这就是 use-after-free即释放后使用如果同一块内存被再次释放就是 double free。两者都属于内存破坏类缺陷攻击者可以利用它们改写内核里的数据。2014 年的那个改动e0abdadcc6e1由 Patrick McHardy 提交作者日期 2014 年 2 月 18 日Pablo Neira Ayuso 在 2 月 25 日合入。改动只落在net/netfilter/nf_tables_api.c一个文件10/−5。提交信息很短netfilter: nf_tables: accept QUEUE/DROP verdict parametersAllow userspace to specify the queue number or the errno code for QUEUE and DROP verdicts.意图写得很清楚让用户态能为 QUEUE 指定队列号、为 DROP 指定 errno 码。这是一个功能提交不是疏忽。需要说明的是这一段的意图是从提交信息与改动本身读出来的。十年后的回退者写下的原话是我不清楚这个提交当初为什么这么做这里据此引用不代他解释动机也不主张当年的作者存在过失。相应的 hunk 是在当年的内核里这个值直接存放在data-verdict这个整数字段里后来的版本把它改成带code成员的结构所以下面这段与当前代码的写法不同。switch(data-verdict){-caseNF_ACCEPT:-caseNF_DROP:-caseNF_QUEUE:default:switch(data-verdictNF_VERDICT_MASK){caseNF_ACCEPT:caseNF_DROP:caseNF_QUEUE:break;default:return-EINVAL;}/* fall through */caseNFT_CONTINUE:caseNFT_BREAK:caseNFT_RETURN:改动前switch直接比较完整值三个 case 分别对应0、1、3。只有高 16 位全为零的那三个具体值能匹配上任何带高位的值都落不到 case 里走到后面的default被拒。改动之后校验换成了data-verdict NF_VERDICT_MASK只看低 8 位。带任意高位的值只要低 8 位是0、1、3就能通过然后/* fall through */落进原来的分支继续处理。准入点从“校验整个值”变成了“校验低 8 位”。而消费点读的是高 16 位。两个位域不相交中间那 8 位的 flag 区也没有被约束。这个改动在当时看起来是加固。它不是把校验删掉而是把校验重写成“先区分内部值和外部值再对外部值校验低 8 位”还补了一个return -EINVAL。从代码形状上看它比原来更完整原来只有一个扁平的switch改后有了嵌套结构和一个显式的拒绝分支。功能实现了代码看起来更严谨了而校验对象从“整个值”悄悄变成了“值的一部分”。2024 年的修复把功能删掉f342de4e2f33的选择是把 2014 年的改动整体回退恢复完整值比较。提交信息的推理链条完整This reverts commit e0abdadcc6e1.core.c:nf_hook_slow assumes that the upper 16 bits of NF_DROP verdicts contain a valid errno, i.e. -EPERM, -EHOSTUNREACH or similar, or 0.Due to the reverted commit, its possible to provide a positive value, e.g. NF_ACCEPT (1), which results in use-after-free.Its not clear to me why this commit was made.NF_QUEUE is not used by nftables; “queue” rules in nftables will result in use of “nft_queue” expression.紧跟着的那一段说明了取舍If we later need to allow specifiying errno values from userspace (do not know why), this has to call NF_DROP_GETERR and check that “err 0” holds true.这句话描述的是“如果将来要实现这个功能准入点应该怎么校验”。对照前面那张图NF_DROP_GETERR正是消费点使用的解码器check that err 0正是“返回值必须是负值或零”这个调用约定的准入版本。作者把正确做法写清楚了而当时选择的方案是把功能整体删掉。这是一次明确的工程取舍。删掉功能让修复变小、风险变低代价是 2014 年那个提交想加的能力不复存在。按提交信息里那句 “do not know why”作者判断这个能力没有实际需求。修复的第二处 hunk 把default: return -EINVAL;重新加回switch末尾于是结构回到 2014 年之前的样子。恢复后的白名单是三个具体整数NF_ACCEPT、NF_DROP、NF_QUEUE。f342de4e2f33的标签一并列出Fixes: e0abdadcc6e1、Cc: stablevger.kernel.org、Reported-by: Notselwyn。有明确的Cc: stable这是它进入多个 stable 分支的直接原因。三次改动都没有带测试在使用这些源码做结论之前有一件容易漏掉的事把三次提交的改动文件列出来。e0abdadcc6e1只动了一个文件net/netfilter/nf_tables_api.c10/−5。f342de4e2f33只动了一个文件同一个文件6/−10。da107398cbd4只动了一个文件还是同一个文件5/−2。三次提交都没有改动测试文件也没有新增测试。仓库里没有检索是否存在事后补的测试因此不能断言“这套判断从未被测试覆盖”能确认的是这三次改动本身没有带来任何可执行的验证。这个观察在这条时间线上的位置很具体。2014 年的改动确立了“低 8 位校验”这条规则2024 年的改动把它推翻2026 年的改动又收窄了 2024 年留下的白名单——三次都是对同一处判断的修改而三次的验证方式都只有人眼阅读。缺陷在十年里没被发现与这一点是相关的。保留下来的那个值NF_QUEUE留在了白名单里。同一份提交信息给出了理由NF_QUEUE is not used by nftables; “queue” rules in nftables will result in use of “nft_queue” expression.nftables 里表达“把包交给队列”这件事用的是nft_queue表达式而不是一个立即的NF_QUEUEverdict。既然没有合法生产者会发出这个值作者把它留在白名单里。判断里其实包含两个都正确的观察。第一个nft_queue是NF_QUEUEverdict 的合法生产者它用NF_QUEUE_NR(queue)构造这个值。第二个走nft_queue时队列号来自表达式的配置不是由用户态直接提供的 32 位整数。这两条都对。缺的是第三条表达式是合法的生产者不等于表达式是唯一可能的来源。通过nft_immediate提交一个立即 verdict走的是同一个nft_verdict_init()只是那条路上没有任何东西保证高 16 位是被NF_QUEUE_NR那样算出来的。这句话在当时的语境下是合理的它说明的是“不存在合法来源”。但准入点的职责不是判断“有没有合法来源”而是判断“这个值交到下游会不会出问题”。这两件事在大多数情况下结论一致在NF_QUEUE这个具体值上不一致而 2026 年的修复把不一致暴露了出来。这里有一处容易混淆的地方NF_QUEUE在消费点那边的处理是完整的nf_hook_slow有case NF_QUEUE:会调用nf_queue()。所以它不是一个“下游不认识”的值恰恰相反下游对它有一整套处理。问题出在谁能触发这条路径走nft_queue表达式时队列号由表达式提供低 8 位与高 16 位的组合是表达式自己算出来的而通过 netlink 直接提交一个立即 verdict 时这个组合由用户态提供中间没有表达式的约束。2026 年同一个判据相反的结论da107398cbd4由 Pablo Neira Ayuso 提交作者日期 2026 年 3 月 31 日提交信息带Fixes: f342de4e2f33也就是它修的是 2024 年那次修复本身nft_queue is always used from userspace nftables to deliver the NF_QUEUE verdict. Immediately emitting an NF_QUEUE verdict is never used by the userspace nft tools, so reject immediate NF_QUEUE verdicts.The arp family does not provide queue support, but such an immediate verdict is still reachable. Globally reject NF_QUEUE immediate verdicts to address this issue.前两句和 2024 年的理由几乎同构立即的NF_QUEUE从不被用户态工具使用所以拒绝它。差别落在第三句arp family 不提供 queue 支持但这个立即 verdict 仍然可达。nft_queue表达式在 arp family 下没有对应的处理路径可是通过 netlink 提交一个立即NF_QUEUEverdict 的路径仍然存在。没有合法生产者路径却通着。补丁与 CVE 记录都没有说明在 arp family 下走到nf_queue()会产生什么具体后果这次拒绝的依据是“路径可达”本身。2024 年把“没有被使用”读成了“不会出现”这一次把它读成了“没有正当来源因此应当封掉”。修复本身很小两个 hunkswitch(data-verdict.code){caseNF_ACCEPT:caseNF_DROP:-caseNF_QUEUE:-break;caseNFT_CONTINUE:data-verdict.chainchain;break;caseNF_QUEUE:/* The nft_queue expression is used for this purpose, an * immediate NF_QUEUE verdict should not ever be seen here. */fallthrough;default:return-EINVAL;}NF_QUEUE从白名单移出与default合流返回-EINVAL注释写明立即NF_QUEUE不应出现在这个位置。主线当前的nft_verdict_init()就停在这个状态白名单是两个值NF_ACCEPT与NF_DROP。两次修复的白名单变化可以并排看2014 之前 case NF_ACCEPT: case NF_DROP: case NF_QUEUE:(加 NFT_* 内部值) 2014 之后 default: switch (data-verdict NF_VERDICT_MASK) ← 只校验低 8 位 case NF_ACCEPT: case NF_DROP: case NF_QUEUE: break; default: return -EINVAL; /* fall through */ 2024 之后 case NF_ACCEPT: case NF_DROP: case NF_QUEUE: break; ← 恢复完整值比较 2026 之后 case NF_ACCEPT: case NF_DROP: case NF_QUEUE: fallthrough; default: return -EINVAL;顺带一个记录上的细节da107398cbd4的提交信息里没有Cc: stablevger.kernel.org也没有 Reported-by。它仍然进了多个 stable 分支但提交本身没有请求 backport。 stable 分支是仍在接收修复的旧版本维护线例如 6.1.x把主线的修复移植到这些分支上就叫 backport通常靠提交里带Cc: stable或由维护者在主线落地后按Fixes:标签回溯。回退而不是修正2024 年的修复选择的是回退而不是实现正确的校验。回退把 2014 年加的能力整体去掉。按提交信息这个能力是“让用户态能为 QUEUE 指定队列号、为 DROP 指定 errno 码”。回退之后nft_verdict_init()只接受高 16 位为零的三个具体整数。实现正确校验的路线也在同一段提交信息里写了调用NF_DROP_GETERR检查返回值不大于零。如果走这条路线2014 年那个能力可以保留——用户态仍然能指定 errno只是准入点会用消费点的解码器把值解一遍确认它解出来的语义合法。两条路线的代价差别很清楚。回退改得更少、风险更低不需要新逻辑也便于 backport实现正确校验则要在nft_verdict_init()里引入对内部解码器的调用而这个函数如前面所说定义在内部头文件里调用它会把这个解析层与 netfilter core 的内部实现绑得更紧。回退还有一个附带好处它让状态回到“曾经正常工作过的那个版本”。代价同样清楚。回退之后准入点的校验方式是“完整值白名单”这在当时是充分的——因为下游对高 16 位的处理只对全零的几个值有定义。但它没有把“准入点应当按消费方式校验”这件事变成一条显式的规则。留在这个状态下的代码仍然是正确的只是它的正确性依赖于白名单恰好足够窄而不是依赖于校验方式本身正确。2026 年的第二次修复正是在这个基础上发生的。它没有改变校验方式仍然是白名单它只是把白名单再收窄一格把NF_QUEUE排除掉。经过这次收窄白名单是两个值两个值的高 16 位都是零。校验方式依旧是“列举完整值”而这一次列举的结果恰好覆盖了下游有定义的全部情况。不变量把三次提交并排看能得到一个具体的判据准入点应当按“下游能否安全消费”来定义而不是按“是否存在合法生产者”来定义。三次提交分别是这个判据的三次应用结论各不相同。2014 年准入校验的是分支位消费读的是 errno 位两个位域不相交。改动引入时并没有人认为自己在放松校验相反它看起来像是把校验写得更完整了。2024 年恢复了完整值比较等于按消费方式校验了因为下游只对高 16 位全零的那几个整数有定义好的行为。但作者同时把“是否存在合法生产者”用在了另一个决定上保留了NF_QUEUE。2026 年同一个判据用在同一个值上因为掌握了“路径可达”这一信息结论反过来。2024 年那次提交里有一段自述作者写下了正确的准入方式调用消费点的解码器、检查返回值符号却没有实现它而是删掉了需要它的功能。这在当时是合理取舍。它的后果是“准入点的校验方式”这件事没有被真正建立起来于是留下了一个没有被按消费方式校验的值。一次用删功能替代修校验的修复会把同一个判断留给下一次。把这条判据说得更一般一点当一个值的不同位域由不同代码路径读取时生成这个值的那一方必须覆盖所有会被读取的位域而不是覆盖“看起来需要校验”的那几个。这里“看起来需要校验”的直觉通常指向承载分类的那几位——因为分类决定了后续走哪条路看起来最像安全关键的部分。而承载参数的高位看起来只是数值容易被当作无害。这类错位都来自同一个直觉偏差分类位与参数位分开存放而安全影响可能落在参数位上。这条判据也有它的限度需要说清楚。它要求先知道下游读了哪些位域而这件事在大型代码库里并不总是容易回答——一个结构体的字段可能被多个子系统读取读取方式也可能分布在不同的函数里。本篇能给出确定结论是因为这个值只有一条消费路径nf_hook_slow而它的读取方式在同一段代码里就能看全。换成一个字段被十几个调用点各自解读的结构体同样的检查会困难得多也可能得不出确定的覆盖结论。还有一个次级的观察。三次改动里真正起作用的都不是代码本身2014 年的改动只有十行左右2026 年的只有五行。“加校验”和“改校验”在 diff 尺度上很接近区分它们需要看校验的对象是什么而这一点在 diff 里看不出来只能对照消费点才能确定。这也解释了为什么这类问题能通过 review审查者看到的是一段结构更完整的校验代码而“校验对象变了”这件事不在 diff 里。两轮 backport 与版本核查同一个判断留给下一次的代价在 stable 分支上看得出来。两次修复分别进了两轮不同的版本。第一次2024 年修复主线 6.8进入 4.19.307、5.4.269、5.10.210、5.15.149、6.1.76、6.6.15、6.7.3。第二次2026 年修复进入 5.10.253、5.15.203、6.1.168、6.6.134、6.12.81另有 6.18.22 与 6.19.12 仅见于 CVE 记录。以 6.1 分支为例两轮分别是 6.1.76 与 6.1.168以 5.10 分支为例是 5.10.210 与 5.10.253。第二次修复进入哪个位点取决于改动落地时该分支还接受哪些补丁因此没有任何一条“大于等于某个版本即安全”的规则可以同时覆盖两轮。做版本核查时需要按两轮分别对照第一次修复的版本与第二次修复的版本。第二轮为什么进得晚有一部分原因写在提交本身这次提交没有Cc: stable。补丁进入 stable 的主流路径是提交里带Cc: stable或者在主线落地后由 stable 维护者按Fixes:标签回溯。这次少了前一条回溯就依赖后一条的流程。这就带来一个核对上的限制文中的版本号并非全部来自逐份 ChangeLog 的独立核对。5.10.210 之后那批与 6.1.168 之后的版本可以逐份检索 ChangeLog 确认而 4.19.307 与 5.4.269 这两个第一次修复的首发版本只由 CVE-2026-43024 记录给出的区间推得6.18.22 与 6.19.12 同样只见于 CVE 记录。做资产核查时这四个版本号需要另行对照 ChangeLog 才能作为依据。这不是缺陷但它解释了为什么同一个逻辑问题在两个时间点各进一轮。CVE 记录在这里给了一个可用的形式。CVE-2026-43024 的记录把 introduced 与 fixed 配对列出例如f342de4e2f33对应da107398cbd4、f710691be163、42a47f4b1b76、17dc5d5a935c四个修复提交也就是“2024 年那个提交进入的分支在 2026 年各自需要哪一个提交来补”。按这种配对关系做清单比按版本号做清单可靠得多它直接把“第一轮修过、第二轮还没修”这个状态表达出来了。还有一个元数据层面的差异会影响排序。CVE-2024-1086 的记录带 CVSS 3.1 评分 7.8并被收录进 CISA 的已知被利用漏洞目录CVSS 是 0 到 10 的漏洞严重性评分体系7.8 属于偏高的区间CISA 是美国网络安全和基础设施安全局它的“已知被利用漏洞”目录简称 KEV收录的是有实际利用证据的漏洞CVE-2026-43024 的记录里没有 CVSS 评分也没有 problem type。也就是说只看 CVE 元数据做严重性排序时第二轮修复在榜单上几乎是隐形的而它修的是一处已经被实际利用过的缺陷类型。对以 CVE 评分驱动修复排期的流程来说这一类缺口不会自己浮现第一轮的条目评分高、被收录、被反复引用第二轮的条目只有一段提交信息。要发现它需要按Fixes:链条把两轮的 CVE 关联起来而不是各自独立打分。关于时间关系还有一处细节。2024 年的修复在 1 月 24 日进入主线CVE-2024-1086 在 1 月 31 日公布CISA 目录在 5 月 30 日收录。修复先于 CVE 公布说明这轮修复走的是先修后公布的流程从修复到被列入已知被利用目录之间约四个月。为什么这处缺陷不会留下任何告警CVE-2024-1086 的受影响范围写作3.15 6.8CVSS 3.1 评分 7.8CISA 的 KEV 目录在 2024 年 5 月 30 日收录CVE-2026-43024 的记录里没有 CVSS 评分也没有 problem type。严重性只能按这两个数字来读不再往下推。从提交时间看缺陷引入到第一次修复之间约十年但“存在十年”不等于“十年里都能被利用”可利用性还取决于非特权用户能否访问 nf_tables。CVE 记录描述的利用前提是本地权限提升与这一点相符再往下就超出记录范围了。观测这一层能说的有限而这个限制本身也是结论的一部分。这处缺陷不发日志、不产生可见异常。nf_hook_slow()里那句WARN_ON_ONCE(1)只覆盖default分支也就是低 8 位不是任何已知分支的情况本处的值是低 8 位合法、高 16 位不受约束不会走到那个分支。因此依赖内核告警或内核日志发现这处问题的路径不存在。能发现它的入口是源码对照与产物核查不是运行时观测。低 8 位承载分类这件事在代码里还有一个副作用只要分类正确nf_hook_slow就会选到看起来正确的那条分支代码在功能上是正常的。NF_DROP分支会释放 skbNF_QUEUE分支会去排队行为都发生了。缺陷不会表现为“分支走错了”只会表现为“这条路返回的东西不对”。功能的表面正确性是这处问题能存活十年的一个具体原因也是它不需要任何异常输入就能被触发的原因触发所需的只是高 16 位取某个不受约束的值。对一个以检测能力为目标的评估来说这意味着这一类的补丁不能靠“有没有告警”来判断是否命中也不能靠“行为是否异常”来判断。它只能从“准入点校验的对象与消费点读取的对象是否是同一个”这个结构问题上查出来。把这个结构问题写成可执行的检查形态大致是这样对每一个“解析用户输入并生成一个会被下游按位域解读的值”的函数找出下游解读该值的全部位置确认解析函数的校验覆盖了下游实际使用的每一个位域。以这个值本身为例下游用了低 8 位选分支、用了高 16 位算返回值、flag 区被nft_queue写过因此完整的准入校验必须对这三个区域都有交代。2014 年之后的校验只交代了第一个。这个检查不需要运行任何东西也不需要构造输入。它的成本在于要同时读两侧的代码。而两侧相隔的调用距离正是这类问题容易存活的原因从nft_verdict_init()到nf_hook_slow()之间隔着数据结构的存储、链求值、hook 调用好几层只看其中一侧不会发现任何异常。如果反过来考虑运行时信号需要的形态和现有的不一样。WARN_ON_ONCE(1)出现在nf_hook_slow的default分支判据是“低 8 位不是已知分支”而本处的值是低 8 位完全合法。要在这条路径上产生信号判据必须改成“低 8 位是NF_DROP或NF_QUEUE时高 16 位是否符合该分支的约定”也就是把准入点的检查在消费点复制一份作为断言。这类断言在内核里通常以WARN_ON_ONCE的形式出现且只在开发配置下启用因为它的判据本身就是准入点的职责。这构成一种常见的分工失效当准入点与消费点相隔足够远时消费点上的断言会被视为冗余准入点应该已经保证了而准入点的校验会被视为充分消费点的约定看起来只是实现细节。两侧各自都合理合起来就留下了一个谁都不检查的区域。
RELATED

相关推荐

智能体技能体系全解析:定义、编排与工程落地

智能体技能体系全解析:定义、编排与工程落地

1. 技能体系:Agent 的“能力肌肉”从哪来这两年做智能体(Agent)开发的人越来越多,但我观察到一个很普遍的现象:很多人把大模型接上了几个API,就以为 Agent 已经完成了。实际上,模型能“对话”和…

📅 2026/10/21 6:21:01
knowledge-work-plugins 插件化架构设计与知识工作流落地实践

knowledge-work-plugins 插件化架构设计与知识工作流落地实践

1. 从"知识工作插件"这个命名说起:它到底想解决什么问题第一次看到knowledge-work-plugins这个命名,我的直觉是:这不是一个普通的工具库,而是一套面向"知识工作者"的扩展机制。所谓知识工作者,说白…

📅 2026/10/21 6:21:01
技能不是学出来的:从刻意练习到变现的系统化养成指南

技能不是学出来的:从刻意练习到变现的系统化养成指南

很多人对“skills”这个标题的第一反应是:这也太宽泛了,能写出什么名堂?但我恰恰想从这种宽泛里挖出点实际的东西。过去几年我接触过大量开发者、设计师、运营和市场岗的朋友,发现一个共性现象:大家不是不努力&#xf…

📅 2026/10/21 6:21:01
MORE NEWS

更多资讯

📰

Agent评测中“超时算失败吗”?关键看分母口径与终态设计

1. 做Agent评测久了,你会发现“超时算不算失败”是个假问题做Agent效果评估这几年,我遇到最能引发争论的一句话,就是“Agent 超时算失败吗?”直接回答“算”或“不算”都太轻了,因为真正的问题不在分子,而在…

📰

IDEA Generate All Setter加强版:批量生成Setter的实战与避坑指南

1. 从一个让人抓狂的DTO赋值场景说起做后端开发的人,几乎都经历过这样的时刻:接口文档刚定稿,数据库表结构刚评审完,你打开IDE准备写业务代码,结果发现要面对一个拥有三十多个字段的DTO。更让人崩溃的是,这…

📰

repo文件baseurl三种形式详解:绝对路径、变量模板与文件路径

1. 从一个让人抓狂的报错说起如果你维护过基于 repo 文件做批量仓库管理的环境,大概率见过这个场景:明明昨天还能正常同步的几十台机器,今天突然集体报错,提示找不到某个仓库地址,或者更诡异的是,一部分机器…

📰

从零构建轻量级数据读取与响应框架rea:设计、实现与避坑指南

1. 从“rea”这个标题说起:一个极简命名背后的完整项目思维第一次看到“rea”这个标题的时候,我脑子里蹦出来的第一反应是——这大概率又是一个被随手命名的项目。做技术的人都有这个毛病,项目文件夹建好的那一刻,名字往往是拍脑袋…

📰

Spring Boot课堂点名系统:从开发到部署的完整实战解析

点名这件事,在大学里几乎是每天都要发生一遍的日常。但真正做过相关开发的人会知道,一套看似简单的课堂点名系统,背后涉及的东西远比想象中多:学生端怎么签到、教师端怎么发起点名、考勤数据怎么统计、请假和补签怎么处理、异常请…

📰

SVM面试全攻略:从最大间隔到核技巧的核心考点解析

我去年集中刷算法岗面试题那段时间,最头疼的就是SVM。网上的总结要么是纯公式堆砌,要么是"最大间隔核函数"一句话带过,背完过两天就忘,一到手撕推导就露馅。后来我把市面上能见到的SVM面经问题全部梳理了一遍&#xff0…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬