7.2 尾调用子程序与有界循环


7.2 尾调用、子程序与有界循环

本节摘要:验证器给单程序的指令数、栈和路径数都有上限。复杂协议解析和多级策略不能塞进一个函数硬展开。尾调用把控制转给另一个程序并放弃返回;子程序允许受控调用;循环必须让上界可见。组合是为了可证明,不是为了把用户态状态机原样搬进内核。

学习目标

阅读完本节,你应当能够:

  1. 对比尾调用与子程序在栈和返回上的差异
  2. 说明有界循环怎样写才能让验证器看见上界
  3. 按“解析 / 判定 / 输出”拆程序,而不是按文件名拆
  4. 避免尾调用深度和 Map 跳转表把自己绕晕

第 5 章说路径爆炸时要拆程序。拆法有三种常用:尾调用、子程序、把活送回用户态。前两种仍在内核里,第三种往往最正确。

一、尾调用:跳走,不回来

尾调用通过程序数组 Map,跳到另一个同类程序。当前栈帧结束,像汇编里的跳转,不是 call。适合流水线:第一段解析包头,第二段查策略,第三段出事件。每段自己通过验证,比一段巨无霸容易。

代价:不回来,难共享大量栈上临时量;有深度上限;跳转键写错会静默少跑一段。调试时必须给每段独立计数,否则你不知道卡在哪一段。

子程序则是真的 call,可以返回,受指令与栈限制更严。适合短的公共读字段函数。把整份策略引擎放进子程序,会把上限问题换个房间继续爆炸。

上图若用尾调用实现,箭头是跳转;若用子程序,解析段还想回到入口就得是 call。不要混用到谁也说不清寿命。

二、循环:上界要写在脸上

验证器不接受“直到包结束”这种开放条件,除非它能从比较中推出次数上限。实践是:

  • 用编译期常量做最大迭代,例如最多扫 8 个扩展头
  • 每次迭代显式递减剩余预算
  • 超预算就放弃解析,走用户态或丢给下一段
/* 概念性:上界写死,验证器才肯认 */ #pragma unroll for (int i = 0; i < 8; i++) { if (off >= data_end) break; off += hdr_len_at(off); }

展开会增加指令数,可能撞另一道上限。于是出现讨价还价:少扫几层,或拆到下一段尾调用继续扫。这很丑,但比假装能解析任意嵌套更诚实。HTTP/2、Protobuf 变长,这类东西多数不该在钩子里完整解析。

组合手段 返回吗 典型用途 主要风险
有界循环 在本程序 扫固定多层头 展开后指令爆炸
子程序 返回 短公共逻辑 栈与指令仍计入限制
尾调用 不返回 流水线分段 深度、静默跳错、状态靠 Map
送回用户态 不在内核继续 复杂协议与策略树 失去瞬间拦截能力

⚠️ 常见坑:用尾调用模拟函数调用,把返回值塞进 Map 再跳回来。状态时序难推理,验证未必更简单,现场更不可读。
💡 关键直觉:内核侧只留“有界的识别 + O(1) 判定”。识别不完就放弃或抽样,不要追求解析完备。

三、按职责拆,按 Map 交接

推荐三段:

  1. 识别:从上下文拷出固定大小的键,失败则计数并退出
  2. 判定:lookup 策略表,得到允许/丢弃/采样
  3. 输出:只有判定要求时才提交事件

段之间只传小键:五元组哈希、cgroup id、cookie。大块数据不要指望栈,也不要为了传数据把 Map 当堆。

尾调用表用枚举当索引,不要用运行时算出来的随意整数,除非那整数已被卡住范围。跳转表本身也是 Map,要纳入第 5.2 节的预算和权限。

问题:将来验证器更聪明了,还要不要拆?

要。拆开是为了人能测、能关、能单独灰度。验证器变强不会自动给你可运维性。

问题:有界循环上限取多少?

取你真正需要的最浅深度,而不是协议理论上的最大嵌套。攻击者会构造深嵌套打你的最坏路径。上限就是延迟预算。

四、流水线每段的测试与灰度

拆成三段之后,测试也要按段。识别段用合成包或合成系统调用,断言键正确、失败桶在畸形输入时增加。判定段用注入策略 Map,断言命中与未命中。输出段用假判定结果,断言事件形状和丢失计数。三段一起测只作为最后集成,日常回归按段跑,才能在验证失败时知道是哪一段的路径爆炸。

灰度也可以按段。先上识别和计数,判定永远放行,输出关闭。稳定后再打开判定的“只计数不拦截”,再打开输出抽样,最后才打开拦截。顺序和 8.3 的双开关一致,只是粒度更细。某段出问题,只回滚该段的尾调用槽,不必整包卸载——前提是跳转表设计允许把槽指回“空操作程序”。空操作程序是值得常驻的保险。

尾调用槽的版本要和制品版本一起走。老程序跳到新段、或反过来,键布局可能不兼容。加载时先放新段、再切入口,回滚时先切入口到空操作或旧入口、再卸新段。顺序反了会出现短暂的跳向空洞。空洞不一定崩溃,但会让计数停,表现为假恢复。

有界循环的上限要随压测调整,不要随协议热情调整。压测显示 8 层扩展头已经把 XDP 预算打满,就降到 2 层,超了的包走慢路径或丢掉。把上限写进指标,便于以后的人知道这不是魔法数字。注释里写清:上限来自延迟预算,不是来自 RFC 的最大值。

文档用 ASCII 画出跳转,而不是只在脑子里。现场排障时能对照图看每段计数,比读源码快。图过期比没图更糟,所以跳转表改动必须改图。这是流水线的运维税,比维护一个巨无霸函数便宜。

现场笔记:跳转槽指空的十分钟

灰度切入口到新段时,新段还没加载完,跳转落空,计数停了十分钟,像故障自愈。其实是流水线空洞。后来规定:先放段,再切入口;回滚先切到空操作程序,再卸段。空操作程序常驻当保险。顺序是正确性,不是风格。

有界循环上限取了协议理论上的最大值,攻击构造深嵌套把 XDP 预算打穿。上限改成延迟预算允许的最小值,超了走慢路径。注释写明数字来自预算不是来自 RFC。后来的人不再把上限当魔法。

测试按段拆开之后,验证失败能定位到识别段的路径爆炸,而不是对着整包发呆。巨无霸的失败日志是小说,分段的失败日志是病历。病历才能治。

延伸讨论:跳转顺序是正确性

先放段再切入口,回滚先切空操作再卸段。顺序反了会出现空洞,计数停看起来像自愈。空操作程序值得常驻当保险。上限来自延迟预算不是 RFC 最大值,攻击会打最坏路径。测试按段:识别、判定、输出分开回归,集成放最后。跳转图表必须随表改,过期图比没图糟。段间只传小键。用 Map 模拟返回值跳回来,时序难推理,现场不可读,不要当模式。可运维性不会随着验证器变强自动出现。拆开是为了人能关能测能单独灰度。人不能关的流水线,和巨无霸程序同样危险。危险在于你不知道哪一段在干活。不知道就无法回滚其中一段。无法分段回滚,灰度就是整包赌博。赌博赢过一次,不能写进平台规范。规范写顺序和空操作。写了才有下次。

对照清单

  1. 按识别判定输出拆段,巨无霸会在路径或指令上限上死。
  2. 尾调用不返回,跳错靠每段独立计数发现,空洞看起来像自愈。
  3. 先放段再切入口,回滚先切空操作再卸段,顺序是正确性。
  4. 循环上界写在脸上,上限来自延迟预算不是 RFC 最大值。
  5. 子程序适合短公共逻辑,不是策略引擎容器。
  6. 段间只传小键,不要用 Map 模拟返回值跳回来。
  7. 测试按段回归,集成放最后,失败才能定位哪一段爆炸。
  8. 跳转图表必须随改随新,过期图比没图糟。
  9. 空操作程序值得常驻当保险,允许把槽指回去。
  10. 变长协议多数不该在钩子里完整解析,识别不完就放弃或抽样。
  11. 验证器变强不会自动给你可运维性,拆开是为了人能关。
  12. 不能分段回滚的流水线,灰度就是整包赌博。

流水线让人上瘾,因为看起来很内核。内核并不奖励看起来。内核奖励可证明、可关掉、可单独测的一段。一段里既解析又拦截又出栈,灰度只能整包赌。整包赌赢过,会成为团队神话。神话会阻止下一次把空操作程序做进去。没有空操作,跳转空洞就会在某次发布的十分钟里假装自愈。假装自愈会让人停止查找。停止查找时,策略段可能根本没在跑。没在跑的策略段,比跑错更安静。安静不是安全。安全是每段都有计数,计数能告诉你安静来自空操作还是来自空洞。

\n\n## 课堂补充\n\n一段一个职责。跳错靠计数。先放后切。上限来自预算。子程序要短。不要模拟返回跳回。按段测试。图表随改。空操作当保险。解析不完就放弃。可关比可炫重要。不能分段回滚就是整包赌。\n\n\n\n## 生产验收条\n\n1. 围绕「尾调用子程序与有界循环」,生产验收只认能关掉、能计数、能对账,不认口头保证。\n2. 围绕「尾调用子程序与有界循环」,把所有者、版本、卸载方式写成清单三件套,缺一视为幽灵。\n3. 围绕「尾调用子程序与有界循环」,对照实验必须能回答开关前后业务指标动了没有。\n4. 围绕「尾调用子程序与有界循环」,失败要分类到验证、额度、挂载、未触发、丢失,禁止只丢一句笼统错误。\n5. 围绕「尾调用子程序与有界循环」,热路径默认克制,阈值之后才出栈,出栈之前先证明钩子活着。\n6. 围绕「尾调用子程序与有界循环」,和平台已有挂钩点冲突时先登记再加载,禁止手工抢挂。\n7. 围绕「尾调用子程序与有界循环」,内核版本矩阵没跑绿就不能把功能写成必成功路径。\n8. 围绕「尾调用子程序与有界循环」,回滚必须碰到内核对象,心跳停止才算撤回成功。\n\n## 温故知新

  • 巨无霸程序会在路径或指令上死;按识别、判定、输出拆。
  • 尾调用不返回,适合流水线;跳错要靠每段计数发现。
  • 子程序适合短公共代码,不是策略引擎容器。
  • 循环上界必须可见;解析完备不是目标。
  • 段间只传小键,Map 不当堆。
  • 能回用户态的复杂逻辑就回去,尤其是变长协议。

下一节把镜头拉开:内核还在给 eBPF 加哪些能力,哪些配写进设计,哪些只配当风险备注。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U