- 静态分析
- SAST
- 应用安全
- 漏洞扫描
- 代码质量
【免费下载链接】codeql
CodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security
本文围绕 CodeQL 仓库中 C++ 查询套件的两条关键缺陷检测规则——cpp/allocation-too-small(为指针类型分配的内存不足)与cpp/suspicious-allocation-size(为指针数组分配的内存不足)——展开。你将了解这两条查询各自的判定逻辑、二者如何做到"同一分配点不再重复告警",以及其背后可模型化的分配函数体系(AllocationFunction)是如何扩展识别范围的。读完后,你可以结合仓库中的源码与测试用例,理解这两条 CWE-131/CWE-122 检查从建模到执行的完整机制,并学会用同样的方式为自己的库函数建立分配模型。
一、变更背景:一次针对重叠告警与覆盖面的改进
该改进对应仓库中的变更记录 size-check-queries.md,原文包含两点:
cpp/allocation-too-small(Not enough memory allocated for pointer type)与cpp/suspicious-allocation-size(Not enough memory allocated for array of pointer type)两条查询得到改进。此前同一处内存分配会同时被两条查询报告,改进后不再发生;- 两条查询现在能理解更多的分配函数(more allocation functions are now understood by both queries)。
这两点分别对应"报告去重"与"模型覆盖"两个问题。下文结合仓库中的查询源码与模型库,逐一给出实现层面的证据。
二、查询一:cpp/allocation-too-small 的判定逻辑
查询源码位于 SizeCheck.ql,其元数据声明了问题定位与严重度:
@name Not enough memory allocated for pointer type @description Calling 'malloc', 'calloc' or 'realloc' without allocating enough memory to contain an instance of the type of the pointer may result in a buffer overflow @kind problem @problem.severity warning @security-severity 8.1 @precision medium @id cpp/allocation-too-small @tags reliability security external/cwe/cwe-131 external/cwe/cwe-122其核心判定只有三条:
predicate baseType(AllocationExpr alloc, Type base) { exists(PointerType pointer | pointer.getBaseType() = base and ( exists(AssignExpr assign | assign.getRValue() = alloc and assign.getLValue().getType() = pointer ) or exists(Variable v | v.getInitializer().getExpr() = alloc and v.getType() = pointer) ) ) } from AllocationExpr alloc, Type base, int basesize, int allocated where baseType(alloc, base) and allocated = alloc.getSizeBytes() and decideOnSize(base, basesize) and alloc.(FunctionCall).getTarget() instanceof AllocationFunction and // exclude `new` and similar basesize > allocated select alloc, "Type '" + base.getName() + "' is " + basesize.toString() + " bytes, but only " + allocated.toString() + " bytes are allocated."可以拆出四个关键点:
- 类型关联:
baseType通过"分配表达式的返回值被赋给(或用于初始化)某个指针变量"这条路径,把void *分配结果与它实际承载的类型base关联起来; - 尺寸取最小值:
decideOnSize用min(t.getSize())处理同名类型可能有多尺寸的情况——如果同一名字在不同翻译单元/重载场景下存在多个尺寸,用最小的作为比较基准,避免漏报; - 排除
new:alloc.(FunctionCall).getTarget() instanceof AllocationFunction只保留函数调用形态的分配(malloc/calloc/realloc等),因为new表达式由编译器保证分配尺寸正确,无需检查; - 报告条件:
basesize > allocated,即分配字节数小于目标类型大小,直接提示"X 需要 N 字节,但只分配了 M 字节"。
三、查询二:cpp/suspicious-allocation-size 的"整数倍"检查与去重条款
第二条查询源码位于 SizeCheck2.ql,元数据同样为@security-severity 8.1、CWE-131/122,但报告条件不同:
from AllocationExpr alloc, Type base, int basesize, int allocated where baseType(alloc, base) and allocated = alloc.getSizeBytes() and decideOnSize(base, basesize) and alloc.(FunctionCall).getTarget() instanceof AllocationFunction and // exclude `new` and similar // If the codebase has more than one type with the same name, check if any matches not exists(int size | base.getSize() = size | size = 0 or (allocated / size) * size = allocated ) and not basesize > allocated and // covered by SizeCheck.ql not memberMayBeVarSize(base.getUnspecifiedType(), _) // exclude variable size types select alloc, "Allocated memory (" + allocated.toString() + " bytes) is not a multiple of the size of '" + base.getName() + "' (" + basesize.toString() + " bytes)."它的语义是:分配字节数不是目标类型大小的整数倍((allocated / size) * size != allocated,同时容忍同名类型存在多个尺寸的情况,只要有任何一个尺寸匹配即视为合法)。
其中一行正是变更记录中"不再重复告警"这一点的直接实现证据:
not basesize > allocated and // covered by SizeCheck.ql这行注释点明了两条查询的分工:
basesize > allocated(分配量不足一个元素)的情况归SizeCheck.ql报告;SizeCheck2.ql通过not basesize > allocated显式排除了这个区间,只负责"分配量 ≥ 一个元素但不是整数倍"的数组型误分配。
由此,任意一个分配点至多只会命中两条查询中的一条,从查询逻辑层面保证了同一分配不会收到两份告警。此外还有一个边界处理:not memberMayBeVarSize(base.getUnspecifiedType(), _)排除了含可变长度成员(如 C99 柔性数组)的变长类型,避免对尺寸本身不确定的类型误报。
四、"更多分配函数被理解"背后的模型体系
变更记录第二点——两条查询现在能理解更多分配函数——对应的是 CodeQL C++ 模型库中统一的分配模型:两条查询都import semmle.code.cpp.models.Models,并都以AllocationExpr/AllocationFunction为分析入口。该模型由接口层与实现层两部分组成。
4.1 接口层:可继承、可扩展的抽象
interfaces/Allocation.qll 定义了四个核心抽象:
AllocationExpr:一次分配表达式(malloc调用、new表达式等),提供getSizeBytes()(固定字节数,若可确定)、getSizeExpr()+getSizeMult()(长度表达式与常量乘数)、getAllocatedElementType()、requiresDealloc()等钩子;AllocationFunction:分配函数(如malloc),提供getSizeArg()(尺寸参数下标)、getSizeMult()(乘数参数下标)、getReallocPtrArg()(realloc 的原指针参数)等钩子;HeuristicAllocationExpr/HeuristicAllocationFunction:基于启发式(函数名、参数形态)识别"可能"的分配,用于模型外的兜底;extensible predicate allocationFunctionModel(...):外部可扩展模型谓词,允许使用者按"命名空间 + 类型 + 函数名"追加自己的分配函数模型,并指定尺寸参数、乘数参数、realloc 参数下标以及是否需要释放。
4.2 实现层:内建覆盖的分配函数清单
implementations/Allocation.qll 展示了内建模型的覆盖面,这正是"更多分配函数被理解"的落地位置:
- realloc 家族(
ReallocAllocationFunction):realloc(ptr, size),以及 Windows 传统 APILocalReAlloc/GlobalReAlloc/HeapReAlloc、COM 的CoTaskMemRealloc、OpenSSL 的CRYPTO_realloc、GLib 的g_realloc/g_try_realloc; - 无尺寸参数的分配(
SizelessAllocationFunction):Windows 驱动内存管理中的ExAllocateFromLookasideListEx、MmMapLockedPages系列,NetBSD 的pool_get等——它们没有显式尺寸参数,因此不会进入尺寸比较; new/new[]表达式(NewAllocationExpr/NewArrayAllocationExpr):直接由getAllocatedType().getSize()得出字节数,尺寸天然正确;- 外部模型(
AllocationFunctionFromModel):把allocationFunctionModel谓词中的声明翻译为AllocationFunction子类; - 启发式兜底(
HeuristicAllocationFunctionByName):函数名匹配"%alloc%"或"%Alloc%"、返回指针类型、且存在唯一无符号整型参数(视为尺寸参数);名字匹配%realloc%且存在唯一指针参数时,该指针参数被视为 realloc 输入。
实现层还有两个对查询结果准确性很关键的细节:
realloc(ptr, 0)不算分配:CallAllocationExprImpl的构造条件中显式排除"realloc 目标且尺寸参数为 0"的调用,因为该调用语义上是释放而非分配;sizeof表达式的拆解:deconstructSizeExpr把malloc(a * 2 * sizeof(char32_t))这样的实参拆成"长度表达式a * 2+ 乘数 4",使getSizeBytes()能还原出真实字节数——这保证了用sizeof计算尺寸的合法写法不会因解析失败而漏报。
五、测试用例:用仓库中的测试验证行为边界
两条查询的测试位于 SizeCheck 测试目录,包含test.c、test2.c及对应的.expected期望输出,测试文件头部注释说明其关联 CWE-131,并约定"每个查询只应报告标记为 BAD 的行"。
test.c覆盖cpp/allocation-too-small的典型场景:
void bad0(void) { float *fptr = malloc(3); // $ Alert[cpp/allocation-too-small] // Too small double *dptr = malloc(5); // $ Alert[cpp/allocation-too-small] // Too small } void bad1(void) { float *fptr = malloc(sizeof(short)); // $ Alert[cpp/allocation-too-small] double *dptr = malloc(sizeof(float)); // $ Alert[cpp/allocation-too-small] }而test2.c覆盖cpp/suspicious-allocation-size的"整数倍"场景:
long long *lptr = malloc(27); // $ Alert[cpp/suspicious-allocation-size] double *dptr = malloc(33); // $ Alert[cpp/suspicious-allocation-size] long long *lptr2 = malloc(sizeof(long long)*7/2); // $ Alert[cpp/suspicious-allocation-size] float *fptr2 = malloc(24); // GOOD -- An integral multiple期望输出(SizeCheck.expected 与 SizeCheck2.expected)给出了完整的告警文本,例如:
Type 'float' is 4 bytes, but only 3 bytes are allocated.(来自SizeCheck.ql)Allocated memory (27 bytes) is not a multiple of the size of 'long long' (8 bytes).(来自SizeCheck2.ql)
值得注意的是两个测试文件的 BAD 行互不重叠:malloc(3)之于float只被SizeCheck.ql报告,malloc(27)之于long long *只被SizeCheck2.ql报告——这正是"不再重复告警"这一改进在测试层面的体现。
六、实践要点与局限
综合查询源码与模型库,使用这两条查询时可以把握以下几点:
- 适用前提:分配结果必须通过赋值或变量初始化与某个具名指针类型关联,查询才能把分配与类型对应起来;分配尺寸必须是编译期可确定的固定值(
getSizeBytes()有解),运行时动态尺寸不会触发报告; new被排除:new/new[]的尺寸由语言机制保证,两条查询只针对malloc/calloc/realloc类函数调用;- 同名类型取最小尺寸:若同一类型名在代码库中尺寸不一致,两条查询都以
min(t.getSize())为基准,宁可保守(偏向报告); - 变长类型被排除:含 C99 可变长度成员的数组类型不参与整数倍检查;
- 扩展覆盖:如果你使用的库有自己的分配 API(如
posix_memalign、g_malloc之类),可以通过allocationFunctionModel谓词的外部扩展声明其尺寸参数与 realloc 参数,使其纳入AllocationExpr体系;也可以依赖实现层的启发式(名字含alloc/Alloc、返回指针、唯一无符号整型参数)自动覆盖一部分场景。
七、小结
这次 2020-10-21 的改进,从结果上看只是"不重复告警 + 识别更多分配函数"两句话,但在 SizeCheck2.ql 中一行not basesize > allocated的去重条款、在 implementations/Allocation.qll 中分层的分配模型(内建清单 + 外部可扩展谓词 + 启发式兜底)中,都有清晰的实现落点。这套"查询声明分工、模型负责覆盖"的结构,也是 CodeQL C++ 查询套件处理类似可靠性/安全问题的典型范式:查询本身保持简洁的类型-尺寸比较,识别能力的扩展则集中在可继承、可外部扩展的模型层完成。
- 静态分析
- SAST
- 应用安全
- 漏洞扫描
- 代码质量
【免费下载链接】codeql
CodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security
相关推荐
Ant Design Steps 迷你尺寸步骤条:`size="small"` 使用与实现原理全解析
Ant Design Steps 迷你尺寸步骤条: size="small" 使用与实现原理全解析 <Steps size="small" 是 Ant Desi
前端UI组件设计系统Win11Debloat终极指南:三步实现Windows系统极致优化与深度清理 🚀
Win11Debloat终极指南:三步实现Windows系统极致优化与深度清理 🚀 Win11Debloat 是一个强大而简单的PowerShell脚本工具,
静态分析SAST应用安全漏洞扫描代码质量Structured3D完整指南:如何用3D结构化数据轻松构建智能室内场景
Structured3D完整指南:如何用3D结构化数据轻松构建智能室内场景 如果你正在寻找一个能够将室内设计从平面图转化为智能3D模型的强大工具,那么Struc
静态分析SAST应用安全漏洞扫描代码质量
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考