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.2floatint要确保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()修改对齐的内存

猜你喜欢

转载自www.cnblogs.com/sunnypoem/p/11368473.html
今日推荐