有符号数加减法,存在溢出问题,经常被用于攻击构造。那么如何编写一个可以防止溢出的“正确的”函数呢?
加法运算
错误写法
signed int func(signed int a, signed int b) {
return a + b;
}
分析:a与b相加可能溢出(又分为上溢和下溢)
上溢条件:a + b > INT_MAX
下溢条件:a + b < INT_MIN
正确写法
signed int func(signed int a, signed int b)
{
if (((b > 0) && (a > INT_MAX - b)) ||
((b < 0) && (a < INT_MIN - b))) {
/* Handle error */
} else {
return a + b;
}
}
减法运算
错误写法
signed int func(signed int a, signed int b)
{
return a - b;
}
正确写法
signed int func(signed int a, signed int b)
{
if ((b > 0 && a < INT_MIN + b) ||
(b < 0 && a > INT_MAX + b)) {
/* Handle error */
} else {
return a - b;
}
}
乘法
错误写法
signed int func(signed int a, signed int b)
{
return a * b;
}
正确写法
#include <stddef.h>
#include <assert.h>
#include <limits.h>
#include <inttypes.h>
extern size_t popcount(uintmax_t)
#define PRECISION(umax_value) popcount(umax_value)
signed int func(signed int a, signed int b)
{
signed long long tmp;
assert(PRECISION(ULLONG_MAX) >= 2 * PRECISION(UINT_MAX));
tmp = (signed long long)a * (signed long long)b;
if ((tmp > INT_MAX) || (tmp < INT_MIN)) {
/* 处理错误 */
} else {
return tmp;
}
}
除法
错误写法
signed int func(signed int a, signed int b)
{
if (b == 0) {
/* 处理错误 */
} else {
return a / b;
}
}
正确写法
signed long func(signed long a, signed long b)
{
if ((b == 0) || ((a == LONG_MIN) && (b == -1))) {
/* 处理错误 */
} else {
return a / b;
}
}
这里这个(a == LONG_MIN) && (b == -1)
的条件非常有意思,要理解为什么这种情况下计算会出错,得理解数在计算机中是如何存储的。
取余
错误写法
signed long func(signed long a, signed long b)
{
if (b == 0) {
/* 错误处理 */
} else {
return a % b;
}
}
正确写法
#include <limits.h>
signed long func(signed long a, signed long b)
{
if ((b == 0) || ((a == LONG_MIN) && (b == -1))) {
/* 错误处理 */
} else {
return a % b;
}
}