CodeQL C#污点分析无法识别类非const实例字段的污点流路径
CodeQL C# 污点追踪无法识别实例readonly字段流路径问题
复现环境与代码
测试类代码
设定Message1为污点源(source),Console.WriteLine(Message1)为污点汇聚点(sink),测试类定义如下:
class Test { private const string Message1 = "Test Message 1"; private readonly string Message2 = "Test Message 2"; public void Run() { Console.WriteLine(Message1); Console.WriteLine(Message2); } }
污点追踪查询代码
import csharp import DataFlow::PathGraph class Source extends DataFlow::Node { Source() { this.asExpr() instanceof StringLiteral } } class WriteLineMethod extends Method { WriteLineMethod() { this.hasQualifiedName("System.Console.WriteLine") } } class Sink extends DataFlow::Node { Sink() { exists(MethodCall m | m.getTarget() instanceof WriteLineMethod and this.asExpr() = m.getArgument(0) ) } } class SimpleConfiguration extends TaintTracking::Configuration { SimpleConfiguration() { this = "Simple configuration" } override predicate isSource(DataFlow::Node source) { source instanceof Source } override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } } from DataFlow::PathNode source, DataFlow::PathNode sink, SimpleConfiguration cfg where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "$@ is used in WriteLine method.", source.getNode(), "String"
异常现象
执行上述查询时结果不符合预期:
- 常量字段
private const string Message1 = "Test Message 1";到对应Console.WriteLine(Message1);汇聚点的流路径可被正常检出 - 实例只读字段
private readonly string Message2 = "Test Message 2";对应的流路径始终无法识别 - 静态类中的静态字段流路径可正常检出
原因说明
该现象不是查询配置疏漏,是CodeQL C#默认污点跟踪规则的设计逻辑导致:
const字段为编译期常量,编译器会直接将常量值内联到所有调用位置,Console.WriteLine(Message1)本质等价于直接传入字符串字面量,源和汇聚点直接连通,因此可被正常识别。- 静态字段为类型级全局唯一成员,不存在多实例值歧义,默认污点流规则已覆盖静态字段读写传播逻辑,因此识别正常。
- 实例字段归属于具体对象实例,默认污点跟踪规则为控制误报率、降低分析性能开销,默认不会自动追踪跨实例字段的污点传播:如果无差别追踪所有实例字段,会因不同实例同名字段值混淆产生大量误报,同时大幅提升分析计算量。即使是
readonly实例字段(初始化后不可修改),默认规则也未对该特殊场景做适配,因此流路径会被截断。
修复方案
在自定义的SimpleConfiguration类中重写isAdditionalTaintStep谓词,手动补充实例字段从初始化/写入位置到读取位置的污点传播规则即可,示例补充代码如下:
override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { // 补充实例字段从写入点到读取点的污点传播 exists(FieldAccess fa | // n2对应实例字段的读取表达式 n2.asExpr() = fa and not fa.getTarget().isStatic() and ( // 场景1:字段在声明时直接初始化,n1对应字段初始化值 exists(Field f | f = fa.getTarget() and n1.asExpr() = f.getInitializer() ) // 场景2:字段在构造函数内赋值,可按需放开下方注释覆盖该场景 // or exists(AssignExpr ae | ae.getTarget() = fa and n1.asExpr() = ae.getRight()) ) ) }
添加上述规则后,实例字段的初始化值到字段读取位置的流路径就会被正常识别。如果需要覆盖更复杂的实例字段传播场景,可以结合指向分析(Points-To Analysis)进一步优化规则精度。
参考说明
- 测试结果:执行原始查询时仅能检出Message1对应的流路径,添加补充规则后可同时检出两个字段的流路径
- 复现方式:可基于上述Test类自行构建C# CodeQL数据库验证问题与修复效果
- 官方反馈:可在CodeQL官方仓库查询对应问题的官方回复
内容的提问来源于stack exchange,提问作者Manu Nair
相关产品推荐
相关产品推荐

