CodeQL C++ 安全查询解读:循环条件中的宽类型比较检测(cpp/comparison-with-wider-type)

发布时间:2026/9/25 10:46:26
CodeQL C++ 安全查询解读:循环条件中的宽类型比较检测(cpp/comparison-with-wider-type) 静态分析SAST应用安全漏洞扫描代码质量【免费下载链接】codeqlCodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security项目地址https://gitcode.com/gh_mirrors/co/codeql点击查看免费下载在 C/C 代码中当循环条件把一个窄类型如char、short与一个宽类型如int直接比较时窄一侧的值可能在到达目标值之前发生回绕溢出导致循环永远无法终止。CodeQL 的cpp/comparison-with-wider-type查询专门检测这一类由“与更宽类型比较”引发的隐患其 2021 年 5 月的官方变更说明明确指出该查询经过改进能够产生更少的误报见 变更说明。本文以这条变更为主线结合仓库中的查询源码、示例代码与测试用例完整拆解该查询的判定逻辑、过滤条件与误报抑制手段读完你可以理解 CodeQL 是如何用 SSA 与范围分析精确定位“窄值溢出型”循环比较问题的。问题背景窄类型在循环比较中溢出查询配套的说明文档 ComparisonWithWiderType.qhelp 给出了问题的标准描述在循环条件中窄类型值与宽类型值比较时如果宽类型的取值足够大或小窄类型的值会先发生溢出典型后果是死循环。官方示例代码 ComparisonWithWiderType.c 展示了一个典型场景int16_t bytes_received 0; int max_get INT16_MAX 1; // BAD: bytes_received 与更宽类型的值比较 // 在达到 max_get 之前就会溢出造成死循环 while (bytes_received max_get) bytes_received get_from_input(buf, bytes_received); uint32_t bytes_received2 0; // GOOD: bytes_received2 的类型与 max_get 一样宽 while (bytes_received2 max_get) { bytes_received2 get_from_input(buf, bytes_received2); }由于max_get恰好等于INT16_MAX 1int16_t的bytes_received从 0 开始自增永远无法等于它——一旦回绕到负数就再也小于不了max_get循环永不结束。修复方式如 qhelp 中的建议让比较中较窄一侧的值至少与另一侧一样宽。该问题被映射到 CWE-190整数溢出或回绕、CWE-197数值下溢与 CWE-835死循环。查询元数据与定位查询主文件位于 ComparisonWithWiderType.ql其头部元数据定义了它在扫描体系中的角色元数据值含义idcpp/comparison-with-wider-type查询在规则库中的唯一标识kindproblem以问题而非路径为粒度报告problem.severitywarning警告级严重度security-severity7.8安全评分precisionhigh高精度规则误报少tagsreliability/security/external/cwe/cwe-190/external/cwe/cwe-197/external/cwe/cwe-835归类为可靠性 安全双标签映射三个 CWE查询名被限定为“Comparison of narrow type with wide typein loop condition”——它只关心出现在循环条件里的宽窄比较而非任意比较表达式这一点直接体现在查询主体中rel l.getCondition().getAChild*()这一约束上。核心判定逻辑从宽侧取值到窄侧能否容纳查询导入了三个关键库见 ComparisonWithWiderType.qlimport cpp import semmle.code.cpp.controlflow.Dominance import semmle.code.cpp.controlflow.SSA import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysisSSA构建静态单赋值形式用于追溯变量在循环中的最终定义点Dominance支配关系辅助控制流分析SimpleRangeAnalysis提供upperBound()等值域上界分析是判断“宽侧取值是否大到能撑爆窄侧”的关键。比较宽度的计算参考类型要取基类型C 引用reference本身是指针宽度但比较发生在被引用的值上因此查询不能简单取getType().getSize()int getComparisonSize(Expr e) { if e.getType() instanceof ReferenceType then result e.getType().(ReferenceType).getBaseType().getSize() else result e.getType().getSize() }这个细节保证int这类引用参与比较时按int的 4 字节而非指针宽度计算。有符号类型的“符号位修正”判断窄侧类型是否“装不下”宽侧的取值时还要考虑符号位int getComparisonSizeAdjustment(Expr e) { if e.getType().(IntegralType).isSigned() then result 1 else result 0 }对有符号类型可用数值位要扣除符号位修正量为 1因此窄侧“可表示范围”按size * 8 - 1位计算。主查询中这一步与范围分析结合forall(Expr conv | conv large.getConversion*() | upperBound(conv).log2() (getComparisonSize(small) * 8 - getComparisonSizeAdjustment(small)) )含义是取宽侧表达式的所有隐式转换链large.getConversion*()用SimpleRangeAnalysis的upperBound(conv).log2()估计其取值上界的位数只要存在一条转换链的上界超过窄侧类型的修正后位宽就认为窄侧可能装不下宽侧值从而构成风险。这就是“基于取值范围而非只看类型宽度”的判定——宽侧即使类型更宽若范围分析证明其取值实际落在窄侧表示范围内也不会告警这是抑制误报的核心机制之一。只报告“循环变元”排除循环不变的小变量predicate loopVariant(VariableAccess e, Loop loop) { exists(SsaDefinition d | d.getAUse(e.getTarget()) e | d.getAnUltimateDefiningValue(e.getTarget()) loop.getCondition().getAChild*() or d.getAnUltimateDefiningValue(e.getTarget()).getEnclosingStmt().getParent*() loop.getStmt() or d.getAnUltimateDefiningValue(e.getTarget()) loop.(ForStmt).getUpdate().getAChild*() ) }loopVariant利用 SSA 定义点判定窄侧变量small是否真的是“随循环变化”的变量其最终定义值要么来自循环条件本身、要么位于循环体内、要么是for语句的更新子句。主查询末尾的loopVariant(small, l)注释明确写着ignore loop-invariant smaller variables——如果窄侧变量在循环中从不变化比较不构成死循环风险直接忽略。三道额外的误报抑制过滤除上述两条主判定外主查询还有三组显式排除条件见 ComparisonWithWiderType.ql窄侧类型小于 4 字节才报告small.getExplicitlyConverted().getType().getSize() 4源码注释解释了原因当窄侧本身是int或更宽时确实仍可能溢出但需要“非常大的字符串或数组”才能触发对于从 32 位时代遗留下来的代码库这类报告会非常嘈杂因此有意放弃。注意这里用getExplicitlyConverted()取的是显式转换链末端的类型即变量声明的实际类型。排除整型提升造成的假宽侧not getComparisonSize(large.(DivExpr).getLeftOperand().getExplicitlyConverted()) getComparisonSize(small) and not getComparisonSize(large.(SubExpr).getLeftOperand().getExplicitlyConverted()) getComparisonSize(small) and not getComparisonSize(large.(RShiftExpr).getLeftOperand().getExplicitlyConverted()) getComparisonSize(small)当宽侧是除法、减法或右移表达式时其操作数会先发生整型提升integer promotion提升后的运算可能只是把窄值放大到int并不代表宽侧取值真的超出了窄侧范围。这三条not ... ...条件专门拦截这类“表面上宽、实际值域没变”的场景。报告位置的友好化Element friendlyLoc(Expr e) { result e.(Access).getTarget() or result e.(Call).getTarget() or not e instanceof Access and not e instanceof Call and result e }报告定位优先指向Access/Call的getTarget()即变量声明或函数定义而不是临时的转换节点使告警消息更易读。最终select输出形如Comparison between 小值 of type char and 宽值 of wider type int.$标记同时高亮比较表达式与两个操作数。测试用例与预期结果测试位于 ComparisonWithWiderType 测试目录由 ComparisonWithWiderType.qlref 引用查询本身并通过InlineExpectationsTestQuery.ql做内联期望校验test.c与 ComparisonWithWiderType.expected 一一对应。测试用例覆盖了各种变体char/short与int比较test1–test3、test6、long long参与比较test7、在for更新子句中递减宽侧变量test8、在循环体内递减宽侧变量test9、以及do-while嵌套等结构test10。预期结果共 17 条告警例如test.c:4Comparison between c of type char and x of wider type int.——char c与函数参数int x比较test.c:42Comparison between s1 of type short and 65535 of wider type int.—— 常量上界0x0000ffff超出了short正范围test.c:91/93/95unsigned char与取值分别为65280、16711680、4278190080的常量比较覆盖范围分析对不同上界位数的判定。而test4short s1 short s2同为窄类型与test5比较不在循环中均不出现在预期结果中正好验证了“类型宽度相同不告警”“非循环条件不告警”两条边界。本次改进的意义面向 32 位遗留代码库的降噪2021-05-10 变更说明 只有一句话却指向一个很具体的工程决策The Comparison with wider type (cpp/comparison-with-wider-type) query has been improved to produce fewer false positives.结合查询源码可以看到降噪手段正是上面分析的几层过滤协同作用的结果——用SimpleRangeAnalysis的upperBound估计宽侧实际取值范围而不是仅凭类型宽度下结论让“类型更宽但值域没超出窄侧表示范围”的比较不再误报loopVariant剔除循环不变量避免对不参与循环推进的比较报警对int及以上宽度的窄侧类型整体豁免源码注释直言这类情况“在从 32 位开始的代码库中非常嘈杂”very noisy on codebases that started as 32-bit属于有意牺牲极端场景的召回换取可用性的取舍整型提升过滤避免把/、-、运算中因操作数提升而变宽的表达误判为真实宽值。这套“类型宽度 范围分析 控制流角色”的三重约束使该查询保持了元数据中precision high的承诺报告出的每条告警都意味着窄侧变量在循环推进过程中确实可能在与明显更宽的取值比较时越过自身表示范围。小结cpp/comparison-with-wider-type是一个以“循环条件中的宽窄类型比较”为切入点、以 SSA 与简单范围分析为引擎的可靠性/安全查询它先通过 getComparisonSize 正确解析引用类型宽度再用upperBound(conv).log2()与窄侧位宽扣除符号位比较判定取值溢出风险最后经循环变元、int以上窄侧豁免、整型提升过滤三道关抑制误报。2021 年 5 月的改进正是围绕这套误报抑制机制展开的。若你在维护 C/C 规则集或想理解 CodeQL 高精度规则的设计模式这条查询及其 测试目录 是值得精读的样本。赞分享静态分析SAST应用安全漏洞扫描代码质量【免费下载链接】codeqlCodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security项目地址https://gitcode.com/gh_mirrors/co/codeql点击查看免费下载相关推荐CodeQL C 安全查询解析检测无符号减法与 0 的关系比较cpp/unsigned-difference-expression-compared-zeroCodeQL C 安全查询解析检测无符号减法与 0 的关系比较cpp/unsigned difference expression compared静态分析SAST应用安全漏洞扫描代码质量CodeQL C 静态分析「赋值误作比较」查询cpp/assign-where-compare-meant如何降低条件表达式中的误报CodeQL C 静态分析「赋值误作比较」查询cpp/assign where compare meant如何降低条件表达式中的误报 本文围绕 Cod静态分析SAST应用安全漏洞扫描代码质量Ruff 之 ty 类型检查器的循环类型别名检测cyclic-type-alias-definition 规则全解析Ruff 之 ty 类型检查器的循环类型别名检测cyclic type alias definition 规则全解析 本篇文章围绕 Ruff 仓库中内置类型检开发工具Lint格式化静态分析CLI上一篇EIP-858 详解将以太坊区块奖励降至 1 ETH 并推迟难度炸弹的 PoW 减排提案下一篇如何永久保存微信聊天记录用WeChatMsg打造你的个人数字记忆库创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询