标签:
1、注意:strncpy、strncat等带n版本的字符串操作函数在源字符串长度超出n标识的长度时,会将包括’\0’结束符在内的超长字符串截断,导致’\0’结束符丢失。这时需要手动为目标字符串设置’\0’结束符。
char dst[11]; // 【注意】最好每次定义时初始化为0: dst[11] = {0};
char src[] = "0123456789";
char *tmp = NULL;
memset(dst, ‘@‘, sizeof(dst));
memcpy(dst, src, strlen(src));
dst[sizeof(dst) - 1] = ’\0’; //【修改】dst以’\0’结尾
在使用像memcpy、strcpy、strncpy、sscanf()、sprintf()、snprintf()和wcstombs()这样的函数时,复制重叠对象会存在未定义的行为,这种行为可能破坏数据的完整性。
memcpy与memmove的目的都是将N个字节的源内存地址的内容拷贝到目标内存地址中。
但当源内存和目标内存存在重叠时,memcpy会出现错误,而memmove能正确地实施拷贝,但这也增加了一点点开销。
memmove的处理措施:
?当源内存的首地址等于目标内存的首地址时,不进行任何拷贝
?当源内存的首地址大于目标内存的首地址时,实行正向拷贝
?当源内存的首地址小于目标内存的首地址时,实行反向拷贝
#define BUF_SIZE 128
void Compliant()
{
char buffer[BUF_SIZE + 1];
sprintf(buffer, "Usage: %.100s argument\n", argv[0]); /*【修改】字符串加上精度说明符 */
/* ...do something... */
}
//通过精度限制从argv[0] 中只能拷贝 100 个字节。
无符号数u1 u2,在计算u1+u2时,需要判断u1+u2是否大于UINT_MAX
if((UINT_MAX - ui1) < ui2) //【修改】确保无符号整数运算时不会出现反转
{
return ERROR;
}
else
{
*ret = ui1+ ui2;
}
INT32 si1, INT32 si2;
INT64 tmp = (INT64)si1 *(INT64)si2; /*【修改】确保有符号整数运算时不会出现溢出 */
//++ 将32位有符号数转换成64位,并且计算完成后需要比较结果是否有符号数的范围内
if((INT_MAX < tmp) || (INT_MIN > tmp))
{
return ERROR;
}
【截断错误】将一个较大整型转换为较小整型,并且该数的原值超出较小类型的表示范围,就会发生截断错误,原值的低位被保留而高位被丢弃。
【符号错误】从带符号整型转换到无符号整型会发生符号错误,符号错误并不丢失数据,但数据失去了原来的含义。带符号整型转换到无符号整型,最高位(high-order bit)会丧失其作为符号位的功能。如果该带符号整数的值非负,那么转换后值不变;如果该带符号整数的值为负,那么转换后的结果通常是一个非常大的正数。
//++[符号错误绕过长度检查]
int length; //++ 声明为无符号数
char buf[BUF_SIZE];
length = atoi(argv[1]); //【错误】atoi返回值可能为负数
if (length < BUF_SIZE) // len为负数,长度检查无效
{
memcpy(buf, argv[2], length); /* 带符号的len被转换为size_t类型的无符号整数,负值被解释为一个极大的正整数。memcpy()调用时引发buf缓冲区溢出*/
printf("Data copied\n");
}
UINT32 blockNum;
UINT64 alloc = (UINT64)blockNum * 16; /*【修改】确保整型表达式转换时不出现数值错误 */
//++ 先将两个32位的数之积转换为64位再赋值给64位的变量。
说明:位操作符(~、>>、<<、&、^、|)应该只用于无符号整型操作数,因为有符号整数上的有些位操作的结果是由编译器所决定的,可能会出现出乎意料的行为或编译器定义的行为。
申请内存后初始化(memset )
禁止内存指针移动后(非malloc分配后的起始值),通过该指针释放内存,会出现未知错误。因为malloc一块内存后,它的前一个字节存放了分配的内存大小,free时会根据该字节所代表的大小去free内存。
禁止使用system()和popen()。替代方案是POSIX的exec系列函数或Win32 API CreateProcess()等与命令解释器无关的进程创建函数来替代。
错误示例:
system(sprintf("any_exe %s", input)); //【错误】参数不是硬编码,禁止使用system
这行代码是需要执行一个名为any_exe的程序,程序参数来自用户的输入input。这种情况下,恶意用户输入参数:
happy; useradd attacker
最终shell将字符串”any_exe happy; useradd attacker”解释为两条独立的命令连续执行:
any_exe happy
useradd attacker
这样攻击者通过注入了一条命令”useradd attacker”创建了一个新用户。这明显不是程序所希望的。
改用:
if (execve("/usr/bin/any_exe", args, envs) == -1) /*【修改】使用execve代替system */
说明: std::ostrstream的使用上需要特别注意几点:
(1)str() 会调用成员函数freeze(),它会冻结字符序列,当缓冲区不够大以至于需要分配新缓冲区时,这么做可以避免事情变得复杂。
(2)str()不会附加字符串终止符号(’\0’)。
(3)data()返回所有字符串,没有附带’\0’结尾字符(目前有些编译器自动调用c_str方法了)。
上面如果不注意,就可能会导致内存访问越界、缓冲区溢出等问题,所以建议不要使用ostrstream。[C++03]标准将std::strstream标明为deprecated,替代方案是std::stringstream。ostringstream没有上述问题。
错误示例:下列代码使用了std::ostrstream,可能会导致内存访问越界等问题。
void NoCompliant()
{
std::ostrstream mystr; //【错误】不要使用std::ostrstream
mystr << “hello world”;
// ostream.str方法返回的指针,没有空结束符,容易造成问题
char *p = mystr.str();
std::cout << mystr.str() << std::endl;
}
C标准的系列字符串处理函数strcpy/strcat/sprintf/scanf/gets,不检查目标缓冲区的大小,容易引入缓冲区溢出的安全漏洞。
C++标准库提供了字符串类抽象的一个公共实现std::string,支持字符串的常规操作
说明:字符输入/输出函数fgetc()、getc()和getchar()都从一个流读取一个字符,并把它以int值的形式返回。如果这个流到达了文件尾或者发生读取错误,函数返回EOF。fputc()、putc()、putchar()和ungetc()也返回一个字符或EOF。
如果这些I/O函数的返回值需要与EOF进行比较,不要将返回值转换为char类型。
因为char是有符号8位的值,int是32位的值。如果getchar()返回的字符的ASCII值为0xFF,转换为char类型后将被解释为EOF。0xFF这个值被有符号扩展后是0xFFFFFFFF,刚好等于EOF的值。
注意:对于sizeof(int) == sizeof(char)的平台,用int接收返回值也可能无法与EOF区分,这时要用feof()和ferror()检测文件尾和文件错误。
说明:当文件路径来自非信任域时,需要先将文件路径规范化再做校验。路径在验证时会有很多干扰因素,如相对路径与绝对路径,如文件的符号链接、硬链接、快捷路径、别名等。
所以在验证路径时需要对路径进行标准化,使得路径表达唯一化、无歧义。
如果没有作标准化处理,攻击者就有机会:
推荐做法:
Linux下对文件进行标准化,可以防止黑客通过构造指向系统关键文件的链接文件。realpath() 函数返回绝对路径,删除了所有符号链接:
void Compliant(char *lpInputPath)
{
char realpath[MAX_PATH];
if ( realpath(inputPath, realpath) == NULL)
/* handle error */;
/*... do something ...*/
}
Windows下可以使用PathCanonicalize函数对文件路径进行标准化:
void Compliant(char *lpInputPath)
{
char realpath[MAX_PATH];
char *lpRealPath = realpath;
if ( PathCanonicalize(lpRealPath,lpInputPath) == NULL)
/* handle error */;
/*... do something ...*/
}
说明:该建议应用场景如下,当对文件的元信息进行操作时(比如修改它的所有者、对文件进行统计,或者修改它的权限位),首先要打开该文件,然后对打开的文件进行操作。只要有可能,应尽量避免使用获取文件名的操作,而是使用获取文件描述符的操作。这样做将避免文件在程序运行时被替换(一种可能的竞争条件)。
例如,当access()和open()两者都利用一个字符串参数而不是一个文件句柄来进行相关操作时,攻击者就可以通过在access()和open()之间的间隙替换掉原来的文件,如下所示:
错误示例:下列代码使用access()函数,可能引发竞争条件问题。
void Noncompliant(char * file)
{
if(!access(file, W_OK)) //【不推荐】不要使用函数access(),易引发条件竞争
{
f = fopen(file, "w+");
/*...*/
/* close f after operate(f)*/
}
else
{
fprintf(stderr, "Unable to open file %s.\n", file);
}
}
说明:调用容器的erase(iter)方法后,迭代子指向的对象被析构,迭代子已经失效,如果再对迭代子执行递增递减或者引用操作会导致程序崩溃。
//++ 错误用法:
m_mapID2NE.erase(iter);
iter++; //【错误】erase后,iter指向的对象可能已失效
//++ 正确用法:
m_mapID2NE.erase(iter++); //【修改】将迭代子后置递增作为erase参数
也可以使用earse方法的返回值来保存迭代子,因为返回的是被删除元素迭代子指向的下一个元素位置:iter = erase(iter)
。
注意这种用法可以用于list和vector的erase(),但不适用于map。因为std::map::erase()的返回值在不同STL实现版本是有差异的,有的有返回值,有的没有返回值,所以对map只能使用推荐做法。
标签:
原文地址:http://blog.csdn.net/tanxuan231/article/details/51167359