建议和规则

建议:

  • 理解编译器所使用的数据模型

  • 使用rsize_t或size_t类型表示所有表示对象长度的整数值

  • 理解整数转换规则

  • 使用安全的整数库

  • 对来自不信任来源的整数值实行限制

  • 如果输入函数无法处理所有可能出现的错误就不要用它们转换字符数据

  • 使用strtol()或相关函数把字符串标记换换为整数

  • 只使用显式的有符号或无符号char类型表示数值

  • 验证所有的整数值位于范围内

  • 保证枚举常量映射到唯一的值

  • 使用%操作符时不要假设余数总是正的

  • 把指针转换为整数或者把整数转换为指针时需要小心

  • 当普通的整数位段用于表达式时,不要对它的类型作出假设

  • 只对无符号操作数使用位操作符

  • 避免在同一个数据上执行位操作和算术运算

  • 在程序员定义的整数类型的格式化I/O中使用intmax_t或者unitmax_t

规则:

  • 保证无符号整数运算不产生回绕

  • 保证整型转换不会丢失或错误解释数据

  • 保证有符号整数运算不会产生溢出

  • 保证除法和求模运算不会导致除零错误

  • 移位的数量不能是负数或大于操作数的位数

  • 把整型表达式比较或赋值为一种较大类型之前用这种较大类型对它进行求值

本文地址:http://www.cnblogs.com/archimedes/p/c-security-int.html,转载请注明源地址。

理解编译器所使用的数据模型

<limits.h>除了提供特定类型的位数信息之外,还定义了一些宏,可以用于确定任何遵循标准的编译器所使用的标准整数类型数的整型范围

e.g.  UNIT_MAX是unsigned int可以出现的最大值,LONG_MIN是long int可以出现的最小值

<sdtint.h>头文件在类型中引入了特定的长度限制,可用于避免对特定数据模型的依赖

e.g.  int_least32_t是编译器所支持的最小有符号整数类型,它包含了至少32位

所有编译器要求提供的类型:

<inttypes.h>声明了用于操纵最大宽度整数以及把数值字符串转换为最大宽度整数的函数

代码:

unsigned int a, b;
unsigned long c;
//初始化a和b
c = (unsigned long)a * b; //可能会溢出

解决方案:

#if UNIT_MAX > UNITMAX / UNIT_MAX
#error No safe type is available.
#endif
unsigned int a, b;
unsigned long c;
//初始化a和b
c = (unitmax_t)a * b;

使用rsize_t或size_t类型表示所有表示对象长度的整数值

size_t类型表示sizeof操作符执行结果的无符号整型类型。size_t类型的变量保证具有足够的精度,能够表示一个对象的长度。2007年引入了一种新的类型rsize_t,它被定义为size_t,但是明确用于保存单个对象的长度,任何表示对象长度的变量,包括作为大小、索引、循环计数器和长度的整数值,如果有可能,都应该声明为rsize_t,否则就声明为size_t

代码:

char *copy(size_t n, const char *str) {
int i;
char *p;
if(n == ) {
//error...
}
p = (char*)malloc(n);
if(p == NULL) {
//error...
}
for(i = ; i < n; ++i) {
p[i] = *str++;
}
return p;
}
char *p = copy(, "hi there");

当n > INT_MAX,i超过INT_MAX时,i的值将是从(INT_MIN)开始的负值,p[i]所引用的内存位置是在p所引用的内存之前,导致写入发生在数组边界之外

解决方案:

char *copy(rsize_t n, const char *str) {
int i;
char *p;
if(n == 0 || n > RSIZE_MAX) {
//error...
}
p = (char*)malloc(n);
if(p == NULL) {
//error...
}
for(i = ; i < n; ++i) {
p[i] = *str++;
}
return p;
}
char *p = copy(, "hi there");

理解整数转换规则

转换可以作为类型转换的结果显式或隐式地发生,C99的整数转换规则定义了C编译器如何处理转换,这些规则包括:整型提升、整型转换秩和寻常算术转换。

目的是保证转换结果是相同的值,并且这些值对计算所产生的影响是最小的

整型提升

在执行算术运算的时候,小于int的整数类型将会提升,较小的转换为int类型,否则转换为unsigned int类型

代码:

#include<stdio.h>
int main(void){
signed char cresult, c1, c2, c3;
c1 = ;
c2 = ;
c3 = ;
cresult = c1 * c2 / c3;
printf("cresult= %d\n", cresult); //cresult= 75
return ;
}

假设signed char是用8位值表示,c1 * c2 = 300就无法表示,但是由于整数提升的缘故,c1,c2,c3都转换为int类型

使用安全的整数库

安全整数库的一个例子是IntegerLib,可以免费使用,这个库的目标是提供一些工具函数的集合,在编写C程序的时候帮助软件开发人员避免整数溢出、整数截断和符号错误

对来自不信任来源的整数值实行限制

所有不信任来源的整数值都应该求值,以确定是否存在可确认的上界和下界

代码:

int create_table(size_t length) {
char **table;
if(sizeof(char *) > SIZE_MAX / length) {
return -; //溢出
}
size_t table_length = length * sizeof(char*);
table = (char**)malloc(table_length);
if(table == NULL) {
return -; //error
}
/*...*/
return ;
}

length可接受的范围定义为[1, MAX_TABLE_LENGTH]

解决方案:

enum {MAX_TABLE_LENGTH = };
int create_table(size_t length) {
size_t table_length;
char **table;
if(length == 0 || length > MAX_TABLE_LENGTH) { // 假设MAX_TABLE_LENGTH * sizeof(char *) < SIZE_MAX
return -; //error
}
assert(length <= SIZE_MAX / sizeof(char *));
table_length = length * sizeof(char*);
table = (char**)malloc(table_length);
if(table == NULL) {
return -; //error
}
/*...*/
return ;
}

如果输入函数无法处理所有可能出现的错误就不要用它们转换字符数据

如果输入函数无法处理所有可能出现的输入,就不要使用它们把输入值转换为整数。比如:scanf()、fscanf()、vscanf()、vfscanf()这些函数可以处理合法的整数值,但是缺乏对非法值的健壮错误处理功能。另一种方法是把字符数据输入以null字符结尾的字节字符串形式,并利用strtol()或者相关函数把它转换为一个整数值。

代码:

long sl;
if(scanf("%ld", &sl) != ) {
/*处理错误*/
}

一般而言,不要使用scanf对输入字符串的整数或浮点数进行解析,因为输入中可能包含实参类型无法表示的数

解决方案:

char buff[];
char *end_ptr;
long sl;
if(fgets(buf, sizeof(buff), stdin) == NULL) {
if(puts("EOF or read error\n") == EOF) {
//处理错误
}
} else {
errno = ;
sl = strtol(buff, &end_ptr, );
if(ERANGE == errno) {
if(puts("number out of range\n") == EOF) {
//处理错误
}
} else if(end_ptr == buf) {
if(puts("not valid numberic input\n") == EOF) {
//处理错误
}
} else if('\n' != *end_ptr && '\0' != *end_ptr) {
if(puts("extra characters on input line\n") == EOF) {
//处理错误
}
}
}

只使用显式的有符号或无符号char类型表示数值

代码:

char c = ;
int i = ;
printf("i/c = %d\n", i/c); //" i/c = -17 "

解决方案:

unsigned char c = ;
int i = ;
printf("i/c = %d\n", i/c); //" i/c = 5 "

验证所有的整数值位于范围内

保证枚举常量映射到唯一的值

代码:

enum {red = , orange, yellow, green, blue, indigo = , violet}; //yellow=indigo=6

解决方案:

enum {red, orange, yellow, green, blue, indigo, violet}; //不提供显式的整数赋值
enum {red, orange, yellow, green, blue, indigo, violet}; //只提供第一个成员赋值
enum { //为所有的成员赋值
red = ,
orange = ,
yellow = ,
green = ,
blue = ,
indigo = ,
violet =
};

使用%操作符时不要假设余数总是正的

C99对%操作符的定义提示了下列行为:

17%3=2

17%(-3)=2

-17%3=-2

-17%(-3)=-2

结果与被除数具有相同的符号

代码:

int insert(int index, int *list, int size, int value) {
if(size != ) {
index = (index + 1) % size;
list[index] = value;
return index;
} else {
return -;
}
}

解决方案1(使用一个修正的取模内联函数):

inline imod(int i, int j) {
return (i % j) < ? (i % j) + (j < ? -j : j) : i % j;
}

解决方案2(使用size_t):

int insert(size_t index, int *list, size_t size, int value) {
if(size != ) {
index = (index + ) % size;
list[index] = value;
return index;
} else {
return -;
}
}

保证无符号整数运算不产生回绕

C99规定(无符号整数将会回绕):涉及无符号操作数的计算不会溢出,因为无法由最终的无符号整数类型表示的结果将会根据这种最终类型可以表示的最大值加1执行求模操作

整数运算:

来自不信任来源的整数值如果按下面方式之一使用,就不允许回绕:

  • 作为数组索引

  • 在任何指针运算中

  • 作为对象的长度或大小

  • 作为数组的上届(例如循环计数器)

  • 作为内存分配函数的实参

  • 在安全关键代码中

代码:

unsigned int ui1, ui2, sum;
//初始化ui1, ui2
sum = ui1 +ui2;

解决方案:

unsigned int ui1, ui2, sum;
//初始化ui1, ui2
if(UINT_MAX - ui1 < ui2) {
//处理错误
} else {
sum = ui1 +ui2;
}

参考资料

《C安全编码标准》

C安全编码--整数理解的更多相关文章

  1. 从ord()中对Unicode编码的理解

    刚开始学习编程的时候,老对字符串编码的理解模模糊糊.也一直看这方便的资料,今天在看Dive in python时,突然有了新的理解(不知道是否正确). Python有个built-in函数ord(), ...

  2. 对one hot 编码的理解,sklearn. preprocessing.OneHotEncoder()如何进行fit()的?

    查阅了很多资料,逐渐知道了one hot 的编码,但是始终没理解sklearn. preprocessing.OneHotEncoder()如何进行fit()的?自己琢磨了一下,后来终于明白是怎么回事 ...

  3. ASCLL、Unicode和UTF-8编码的理解

    我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit) ...

  4. 关于Base64编码的理解

    版权声明:本文为[viclee]原创,如需转载请注明出处~ https://blog.csdn.net/goodlixueyong/article/details/52132250 之前在很多业务中都 ...

  5. [py]编码-强力理解版

    py编码骨灰级总结 思路: python执行py文件步骤--py2/3定义变量时unicode差异 1,py2 py3执行py文件的步骤 2,py2 定义变量x='mao' 1.x='mao', # ...

  6. 初学者对ASCII编码、Unicode编码、UTF-8编码的理解

    最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是 255(二进制 11111111=十进制 255),如果要表示更大的整数,就必须用更多的字节. ...

  7. 谈谈对Java中Unicode、编码的理解

    我们经常会遇到编码问题.Java号称国际化的语言,是因为它的class文件采用UTF-8,而JVM运行时使用UTF-16(至于为什么JVM中要采用UTF-16,我没看过 相关的资料,但我猜可能是因为J ...

  8. 通过对比ASCII编码来理解Unicode编码

    Unicode是个规范,可以理解为一个索引表,世界上所有字符基本上在这个索引表中都能找到唯一一个数码与之对应,就像ASCII码表一样,也是一个规范,也可以看成是一个索引表,所有的英文字符都可以在这个索 ...

  9. 前端对base64编码的理解,原生js实现字符base64编码

    目录 常见对base64的认知(不完全正确) 多问一个为什么,base64到底是个啥? 按照我们的思路实现一下 到这里基本就实现了,结果跟原生的方法打印的是一样的 下一次 @( 对于前端工程师来说ba ...

随机推荐

  1. HTML语言特殊字符对照表(ISO Latin-1字符集)

    HTML字符实体(Character Entities) 有些字符在HTML里有特别的含义,比如小于号<就表示HTML Tag的开始,这个小于号是不显示在我们最终看到的网页里的.那如果我们希望在 ...

  2. jdbc根据实例名 连接 sql server

    jdbc:sqlserver://PC;instanceName=sql2012;databaseName=xxxxx

  3. GET方法传递中文参数乱码解决办法

    1.在页面中对你的URL进行编码 使用------encodeURI(你要使用的中文参数值)如:...?username"+encodeURI(“小甜甜") 2.在后台通过解码来接 ...

  4. 基于kubernetes构建Docker集群管理详解-转

    http://blog.liuts.com/post/247/ 一.前言        Kubernetes 是Google开源的容器集群管理系统,基于Docker构建一个容器的调度服务,提供资源调度 ...

  5. EF Attach时已存在的处理方式

    如果我们在先前的步骤中读取过数据,如 var list = db.Model.ToList(); 之后再,附加 var o = new Model { Id = 1 }; db.Model.Attac ...

  6. Java Annotation认知(包括框架图、详细介绍、示例说明)

    摘要 Java Annotation是JDK5.0引入的一种注释机制. 网上很多关于Java Annotation的文章,看得人眼花缭乱.Java Annotation本来很简单的,结果说的人没说清楚 ...

  7. 自己动手模拟百分百<select>下拉列表

    浏览器默认的下拉确实不好用啊,主要是样式不好修改和统一. (一)下手之前先理清一下<select>的流程: 1.结构:<select> <option value=&qu ...

  8. 浅析I/O模型及其设计模式

    前言 I/O在软件开发中的重要性无需多言,无论是在操作系统.网络协议.DBMS这种底层支撑软件还是在移动APP,大型网站服务器等应用软件的开发中都是最核心最重要的部分.特别是现在软件服务使用量和数据量 ...

  9. python内置模块(4)

    这一部分是python内置模块系列的最后一部分,介绍了一些小巧有用的内置模块. 目录: 1.random 2.shelve 3.getpass 4.zipfile 5.tarfile 6.bisect ...

  10. 文本溢出text-overflow

    文本溢出text-overflow 问题:有一个新闻标题,标题宽度为200px,文字为宋体,加粗,文字大小为16px,颜色为黑色,行高为25px,要求单行显示,并且超出时显示“…”,请按要求完成效果. ...