Neverflow.h:基于C99 VLA特性的防止缓冲区溢出的小型宏集合
作者:Sec-Labs | 发布时间:
项目地址
https://github.com/skullchap/neverflow
neverflow.h
一组小巧的宏,用于防止缓冲区溢出。基于C99 VLA特性。
前言...
C语言的可变长数组(VLA)特性已经被讨论过很多次,大部分时间都是负面的。连续的讨论声称这个特性很危险且不安全,被Linux源代码禁用,微软编译器忽略等等,导致VLA特性成为自C11开始可选的特性。
然而,很多人仍然忽略了这个特性的一个非常重要的细节,它不仅仅是能够在堆栈上声明运行时数组,随时可能会溢出。它是可变修改类型的声明。
简而言之,这是Dennnis Ritchie关于VLA的论文和这个非常有用的StackOverflow答案以更好地阐明VLA的实际用途。
注意:请记住,Neverflow还没有经过充分的测试,目前更像是特性的试验阶段。
大致上,有两个主要的宏要记住:**NEW** 用于声明数组,**AT** 用于运行时检查索引是否在范围内并返回其后面的元素的地址。
// NEW(TYPE, NAME, COUNT)
// AT(NAME, IDX)
#include "neverflow.h"
int
main(void)
{
NEW(int, myarr, 10); // 10个元素的数组声明
int *p = AT(myarr, 4); // 指向第5个元素的指针
int v = *AT(myarr, 4); // 通过解引用获取值
*AT(myarr, 4) = 56; // 直接通过解引用更改值
*AT(myarr, 30) = 56; // 出现了问题
// main.c:14: Buffer Overflow. Index [30] is out of range [0-9]
// main.c:14: Function: main
}
为了更好地语义化区分数组中元素的地址或元素本身,**GET** 宏被定义为 *AT 的简写。它的定义如下
#define GET(NAME, IDX) *AT(NAME, IDX)
小细节...
LET 是 __auto_type 的简写,主要在使用 AT 时更容易进行类型推断。
LET e1 = AT(myarr, 4); // e1 是指向int的指针
LET e2 = *AT(myarr, 4); // e2 是int
SIZE(myarr) 返回已分配内存的大小。 请勿直接将 sizeof 用于 VLA 而不进行解引用!它将返回指向数组的指针的 sizeof
LEN(myarr) 返回数组中元素的数量。
默认情况下,Neverflow使用stdlib的calloc作为分配函数,并使用gcc/clang特性自动释放/清理内存。如果要禁用自动清理,请在包含 neverflow.h 之前定义 **NO_AUTOFREE**。如果要使用自己的分配函数,请定义 **ALLOCF**。
#define NO_AUTOFREE // 你需要自己释放内存
#define ALLOCF malloc
#include "neverflow.h"
...
使用 ARR 宏传递数组到函数中同时保留neverflow特性,可以这样做:
void
func(int count, ARR(int, arr, count))
{
int c = LEN(arr);
printf("ELEM COUNT: %d\n", c); // 10
*AT(arr, 12) = 42; // 失败
// main.c:13: Buffer Overflow. Index [12] is out of range [0-9]
// main.c:13: Function: func
}
int
main(void)
{
NEW(int, myarr, 10);
int count = LEN(myarr);
func(count, myarr);
}
作为 ARR 很好的副作用,也可以以这种方式“包装”原始指针/数组:
void
func(int count, ARR(int, arr, count))
{
int c = LEN(arr);
printf("ELEM COUNT: %d\n", c);
printf("6th elem: %d\n", GET(arr, 5)); // 42
}
int
main(void)
{
void *p = malloc(10 * sizeof(int));
ARR(int, myarr, 10) = p;
*AT(myarr, 5) = 42;
func(LEN(myarr), myarr);
}
修改记录
[0.0.2] - 移除名称混淆,添加 ARR() 宏以便于将数组传递给函数。它的另一个好处是可以包装原始指针并提供运行时边界检查的可能性。
[0.0.1] - 初始版本