7 数字操作

7.1 【必须】防止整数溢出

在计算时需要考虑整数溢出的可能,尤其在进行内存操作时,需要对分配、拷贝等大小进行合法校验,防止整数溢出导致的漏洞。

错误(该例子在计算时产生整数溢出)

  1. const kMicLen = 4;
  2. // 整数溢出
  3. void Foo() {
  4. int len = 1;
  5. char payload[10] = { 0 };
  6. char dst[10] = { 0 };
  7. // Bad, 由于len小于4字节,导致计算拷贝长度时,整数溢出
  8. // len - MIC_LEN == 0xfffffffd
  9. memcpy(dst, payload, len - kMicLen);
  10. }

正确例子

  1. void Foo() {
  2. int len = 1;
  3. char payload[10] = { 0 };
  4. char dst[10] = { 0 };
  5. int size = len - kMicLen;
  6. // 拷贝前对长度进行判断
  7. if (size > 0 && size < 10) {
  8. memcpy(dst, payload, size);
  9. printf("memcpy good\n");
  10. }
  11. }

关联漏洞:

高风险-内存破坏

7.2 【必须】防止Off-By-One

在进行计算或者操作时,如果使用的最大值或最小值不正确,使得该值比正确值多1或少1,可能导致安全风险。

错误:

  1. char firstname[20];
  2. char lastname[20];
  3. char fullname[40];
  4. fullname[0] = '\0';
  5. strncat(fullname, firstname, 20);
  6. // 第二次调用strncat()可能会追加另外20个字符。如果这20个字符没有终止空字符,则存在安全问题
  7. strncat(fullname, lastname, 20);

正确:

  1. char firstname[20];
  2. char lastname[20];
  3. char fullname[40];
  4. fullname[0] = '\0';
  5. // 当使用像strncat()函数时,必须在缓冲区的末尾为终止空字符留下一个空字节,避免off-by-one
  6. strncat(fullname, firstname, sizeof(fullname) - strlen(fullname) - 1);
  7. strncat(fullname, lastname, sizeof(fullname) - strlen(fullname) - 1);

对于 C++ 代码,再次强烈建议使用 stringvector 等组件代替原始指针和数组操作。

关联漏洞:

高风险-内存破坏

7.3 【必须】避免大小端错误

在一些涉及大小端数据处理的场景,需要进行大小端判断,例如从大段设备取出的值,要以大段进行处理,避免端序错误使用。

关联漏洞:

中风险-逻辑漏洞

7.4 【必须】检查除以零异常

在进行除法运算时,需要判断被除数是否为零,以防导致程序不符合预期或者崩溃。

错误:

  1. double divide(double x, double y) {
  2. return x / y;
  3. }
  4. int divide(int x, int y) {
  5. return x / y;
  6. }

正确:

  1. double divide(double x, double y) {
  2. if (y == 0) {
  3. throw DivideByZero;
  4. }
  5. return x / y;
  6. }

关联漏洞:

低风险-拒绝服务

7.5 【必须】防止数字类型的错误强转

在有符号和无符号数字参与的运算中,需要注意类型强转可能导致的逻辑错误,建议指定参与计算时数字的类型或者统一类型参与计算。

错误例子

  1. int Foo() {
  2. int len = 1;
  3. unsigned int size = 9;
  4. // 1 < 9 - 10 ? 由于运算中无符号和有符号混用,导致计算结果以无符号计算
  5. if (len < size - 10) {
  6. printf("Bad\n");
  7. } else {
  8. printf("Good\n");
  9. }
  10. }

正确例子

  1. void Foo() {
  2. // 统一两者计算类型为有符号
  3. int len = 1;
  4. int size = 9;
  5. if (len < size - 10) {
  6. printf("Bad\n");
  7. } else {
  8. printf("Good\n");
  9. }
  10. }

关联漏洞:

高风险-内存破坏

中风险-逻辑漏洞

7.6 【必须】比较数据大小时加上最小/最大值的校验

在进行数据大小比较时,要合理地校验数据的区间范围,建议根据数字类型,对其进行最大和最小值的判断,以防止非预期错误。

错误:

  1. void Foo(int index) {
  2. int a[30] = {0};
  3. // 此处index是int型,只考虑了index小于数组大小,但是并未判断是否大于0
  4. if (index < 30) {
  5. // 如果index为负数,则越界
  6. a[index] = 1;
  7. }
  8. }

正确:

  1. void Foo(int index) {
  2. int a[30] = {0};
  3. // 判断index的最大最小值
  4. if (index >=0 && index < 30) {
  5. a[index] = 1;
  6. }
  7. }

关联漏洞:

高风险-内存破坏