C语言安全编码摘录
C语言安全编码规范
1 来源
《The SEI CERT C Coding Standard, 2016 Edition》
2 预处理器PRE
2.2避免不安全宏的参数出现副作用
一个不安全的函数宏在展开时多次使用或根本不使用某个参数,所以不要调用包含赋值、增量、减量、输入、输出等具有副作用参数的不安全宏。
#define ABS(x) (((x) < 0)?-(x):(x)) int m = ABS(++n) |
2.2.6NDEBUG&assert()
assert()宏是编码中整合诊断与测试的一种方便机制。assert宏的行为依赖于类对象的宏定义NDEBUG,若宏NDEBUG没有定义,assert()宏就会计算参数表达式的值,若结果为0,则调用abort()函数。若宏NDEBUG已被定义,assert宏入参表达式在断言中不会被计算。
assert(i++ > 0) |
3 声明和初始化DCL
3.4不要声明或定义保留的标识符
#ifndef _MY_HEADER_H_ #define _MY_HEADER_H static const size_t _max_limit = 1024; #endif /*_MY_HEADER_H_*/ |
以上_MY_HEADER_H_和 _max_limit 可能其它定义冲突,去掉开头的下划线。
3.5.2动态大小的结构体正确用法
struct flexArrayStruct{ size_t num; int data[]; }; void func(size_t array_size){ struct flexArrayStruct *structP = (struct flexArrayStruct *) malloc(sizeof(struct flexArrayStruct)+sizeof(int)*array_size); if (NULL == structP){ /*Handle malloc failure*/ } structP->num = array_size; for(size_t i = 0; i < array_size; ++i){ structP->data[i] = 1; } } |
3.6 结构跨越信任边界时避免信息泄露
意思是说如下的结构体,因为对齐的因素,实际大小占12字节,拷贝的时候,会将填充的信息拷贝到其它地方。
struct test{ int a; char b; int c; }; extern int copy_to_user(void *dst,void *src, size_t size); void do_stuff(void *usr_buf){ struct test arg={.a =1, .b =2, .c=3}; copy_to_user(usr_buf, &arg, sizeof(arg)); } |
解决办法之一,用结构体1字节对齐。
3.7.9 超长标识符
C99外部标识符最长为31的限制
3.8不要在switch语句第一个case前声明变量
变量i被实例化为块内部自动存储值,但初始化语句不会被执行。
switch(expr){ int i = 4; f(i); case 0: i = 18; default: printf(“%d\n”,i); } |
4 表达式EXP
4.1.1不要依赖序列点之间的求值顺序
void func(int i, int *b){ int a = i + b[++i]; } |
4.10.1不要比较填充数据
如下代码,如果结构体因为对齐有填充,不能用memcmp比较两个结构体的内容。
struct s{ char c; int i; char buffer[13]; }; void compare(const struct s*left, const struct s *right){ if (0 == memcmp(left, righe, sizeof(struct s))){ /**/ } } |
5 整数INT
5.1.1.1保证无符号整数操作不会出现回绕
加法保护
void func(unsigned int ui_a, unsigned int ui_b){ unsigned int usum; if (UINT_MAX - ui_a < ui_b){ /*Handle error*/ }else{ usum = ui_a + ui_b; } } |
5.1.2.1减法操作保护
void func(unsigned int ui_a, unsigned int ui_b){ unsigned int udiff; if (ui_a < ui_b){ /*Handle error*/ }else{ udiff = ui_a - ui_b; } } |
5.1.3.1 乘法操作保护
测试乘法的操作数,以保证不会出现回绕.
num_vertices = M; if (num_vertices > SIZE_MAX / sizeof(vertex_t){ /*Handle error*/ } vetices = malloc(num_vertices * sizeof(vertex_t)); |
5.2.9保证整型转换不会丢失或错误解释数据
time函数在表示当前日历时间无效时,会将-1转换为time_t类型后的值。如果time_t的精度小于signed int的无符号整数类型,那么下面转换有误。
void func(void){ time_t now = time(NULL); if (now != -1){ /*应改为now != (time_t)-1*/ /*....*/ } } |
5.3.3.1有符号整数加法保护
两端都要做溢出保护
void func(signed int si_a, signed int si_b){ signed int sum; if(((si_b > 0)&& (si_a > (INT_MAX -si_b))) || ((si_b < 0)&&(si_a < (INT_MIN-si_b)))){ /*Handle error*/ }else{ sum = si_a + si_b; } } |
5.4.1有符号除法保护
void func(signed long s_a, signed long s_b){ signed long result; if ((s_b ==0) || ((s_a == LONG_MIN)&&(s_b == -1))){ }else{ result = s_a / s_b; } } |
5.5.2不要将表达式移动负数位或者移动大于等于操作数中存在的位数
无符号类型左位移保护
#include <limits.h> #include <stddef.h> #include <inttypes.h> extern size_t popcount(uintmax_t); #define PRECISION(x) popcount(x)
void func(unsigned int ui_a, unsigned int ui_b){ unsigned int uresult = 0; if (ui_b >= PRECISION(UINT_MAX)){ /*Handle error*/ }else{ uresult = ui_a << ui_b; } } |
5.6.1使用正确的整数精度
在C中整数类型包含大小和精度两部分,大小表示一个对象使用的字节数,可以通过sizeof得到。一个整数类型的精度是它用来表示值的位数,不包括任何符号位和填充位。
例如,在一个平台上用64位来存储无符号整数,但仅用48位表示值,左移56位将导致未定义的行为。
6 浮点数FLP
6.1.1不要用浮点数作为循环计数器
void func(void){ for(float x = 0.1f; x <= 1.0f; x+= 0.1f){ /*Loop may iterate 9 or 10times*/ } } |
6.1.3 浮点数精度导致死循环
for(float x = 100000001.0f; x <= 100000010.0f; x += 1.0f){ /*Loop may not terminate*/ } |
6.3.2float转int要确保float值的范围适合int
extern size_t popcount(uintmax_t); #define PRECISION(umax_value) popcount(umax_value) void func(float f_a){ int i_a; if (PRECISION(INT_MAX) < log2f(fabsf(f_a)) || (f_a != 0.0F && fabs(f_a) < FLT_MIN)){ /*Handle error */ }else{ i_a = f_a; } } |
6.5.1不要使用对象表示来比较浮点值
-0.0 和 0.0是等价的,但是对象表示中使用的位模式是不相同的。
struct S{ int i; float f; }; bool are_equal(const struct S *s1, const struct S *s2){ if (!s1 && !s2)return true; else if (!s1 || !s2) return false; return 0 == memcmp(s1, s2, sizeof(struct S)); } |
7 数组ARR
7.1.8索引超出范围的访问
多维数组访问时,数组下标不要搞混了。
7.2.4确保变长数组的大小参数在有效范围内
enum {N1 = 4096}; void *func(size_t n2){ if (n2 > SIZE_MAX/(N1*sizeof(int))){ return NULL; } typedef int A[n2][N1]; A *array = malloc(sizeof(A)); if (!array){ return NULL; } /*init*/ return array; } |
8 字符和字符串STR
8.1.1不要试图修改字符串常量
char *p = “string literals”; p[0] = ‘S’; //未定义行为 |
8.2保证字符串的存储具有足够的空间容纳字符数据和null结尾符
void copy(size_t n, char src[n], char dest[n]){ size_t i; for (i = 0; src[i] && (i < n-1); ++i){ //合规解决方法 dest[i] = src[i]; } dest[i] = ‘\0’; } |
8.2.9 fscanf()合规方法,防止缓冲区溢出
void get_data(void){ char buf[1024]; if (1 != fscanf(stdin, “%1023s”, buf)){ /*Handle error*/ } } |
8.3不要将非null结尾的字符序列当做字符串传递给库函数
void func(void){ char c_str[3] = “abc”; printf(“%s\n”,c_str); /*没有null结尾的字符序列,传给了printf*/ } |
9 内存管理MEM
9.3.5含有灵活数据成员的结构拷贝
struct flex_array_struct{ size_t num; int data[]; }; void print_array(struct flex_array_struct struct_p){ for (size_t i = 0; i < struct_p.num; ++i){ printf(“%d”, struct_p.data[i]); /*error当以传值方式传递入参时,灵活数组成员的大小不会被考虑,因此,只有num被拷贝*/ } } |
9.4.3只释放动态分配的内存
下面的不合规代码中,realloc的参数,buf不是指向动态分配的内存。
char buf[1024]; char *p = (char *)realloc(buf, 2048); /*error*/ |
9.5.4为对象分配足够的内存
struct tm *tmb; tmb = (struct tm *)malloc(sizeof(tmb)); /*错误,只申请了指针所占的大小*/ |
9.6不要使用realloc()修改对齐的内存
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异