c++编码规范(二)

 

3 格式化输出安全
规则3.1:格式化输出函数的格式化参数和实参类型必须匹配
说明:使用格式化字符串应该小心,确保格式字符和参数在数据类型上的匹配。格式字符和参数之间的不匹配会导致未定义的行为。大多数情况下,不正确的格式化字符串会可能会导致格式化漏洞,使程序异常终止。
错误示例1:格式字符和参数的类型不匹配
void  Noncompliant_ArgMismatch()
{
 char *error_msg = "Resource not available to user.";
 int error_type = 3;
 /* ...do something... */
 printf("Error (type %s): %d\n", error_type, error_msg); /*【错误】格式化参数类型不匹配 */
}
推荐做法:
void  Noncompliant_ArgMismatch()
{
 char *error_msg = "Resource not available to user.";
 int error_type = 3;
 /* ...do something... */
 printf("Error (type %s): %d\n", error_msg, error_type); /*【修改】匹配格式化参数类型 */
}
错误示例2:将结构体作为参数
void  Noncompliant_StructAsArg()
{
 struct sParam
 {
  int num;
  char msg[100];
  int result;
 };
 struct sParam tmp = {10, "hello Baby!", 0};
 char *errormsg = "Resource not available to user.";
 int errortype = 3;
 /* ...do something... */
 if (tmp.result == 0)
 {
  printf("Error Param: %s \n", tmp); /*【错误】不能将整个结构体作为格式化参数 */
 } 
}
推荐做法:
void  Noncompliant_StructAsArg()
{
 struct sParam
 {
  int num;
  char msg[100];
  int result;
 };
 struct sParam tmp = {10, "hello Baby!", 0};
 char *errormsg = "Resource not available to user.";
 int errortype = 3;
 /* ...do something... */
 if (tmp.result == 0)
 {
  printf("Error Param:num=%d, msg=%s, result=%d\n", tmp.num, tmp.msg, tmp.result);  //【修改】将结构体的内部变量作为格式化参数
 } 
}
规则3.2:格式化输出函数的格式化参数和实参个数必须匹配
说明:使用格式化字符串应该小心,确保格式字符和参数在数量上的匹配。格式字符和参数之间的不匹配会导致未定义的行为。大多数情况下,不正确的格式化字符串会导致程序异常终止。
错误示例:格式字符和参数的数量不匹配,格式化字符串在编码时会大量使用,如拼装SQL语句和拼装调试信息。尤其是调试信息,量大时容易copy-paste省事,这就容易出现不匹配的错误。
void  Noncompliant()
{
 char *error_msg = "Resource not available to user.";
 /* ...do something... */
 printf("Error (type %s)\n");    //【错误】格式化参数个数不匹配
}
推荐做法:
void  Compliant()
{
 char *error_msg = "Resource not available to user.";
 /* ...do something... */
 printf("Error (type %s)\n", error_msg); //【修改】使格式化参数个数匹配
}
规则3.3:禁止以用户输入来构造格式化字符串
说明:调用格式化I/O函数时,不要直接或者间接将用户输入作为格式化字符串的一部分或者全部。如果攻击者对一个格式化字符串可以部分或完全控制,将导致进程崩溃、查看栈的内容、改写内存、甚至执行任意代码等风险。
错误示例:下列代码直接将用户输入作为格式字符串输出。
void Noncompliant(char *user, char *password)
{
    char input[1000];
    if (fgets(input, sizeof(input) - 1, stdin) == NULL)
    {
        /* handle error */
    }
    input[sizeof(input)-1] = ’\0’;
    printf(input); //【错误】不允许将用户输入直接作为格式字符串
}
示例代码的input直接来自用户输入,并作为格式化字符串直接传递给printf()。当用户输入的是“%s%s%s%s%s%s%s%s%s%s%s%s”,就可能触发无效指针或未映射的地址读取。格式字符%s显示栈上相应参数所指定的地址的内存。这里input被当成格式化字符串,而没有提供参数,因此printf()读取栈中任意内存位置,直到格式字符耗尽或者遇到一个无效指针或未映射地址为止。
推荐做法:通过显式参数”%s”将 printf()的格式化字符串确定下来。
void Compliant(char *user, char *password)
{
    char input[1000];
    if (fgets(input, sizeof(input)-1, stdin) == NULL)
    {
        /* handle error */
    }
    input[sizeof(input)-1] = ’\0’;
    printf(“%s”, input);  //【修改】通过%s将格式字符串确定下来
}
建议3.1:使用格式化函数时推荐使用精度说明符
说明:使用格式化函数时(例如sprintf(),scanf_s()等),可能会含有字符串参数,应尽量为格式化指示符加上精度说明符以限制拷贝字符串的长度,防止缓冲区溢出漏洞。
错误示例:使用格式化函数sprintf,没有添加精度说明符,可能会导致缓冲区溢出。
#define BUF_SIZE 128
void NoCompliant()
{
 char buffer[BUF_SIZE + 1];
 sprintf( buffer, "Usage: %s argument\n", argv[0] );
 /* ...do something... */
}
推荐做法:优先采用snprintf替代sprintf来防止缓冲区溢出。若没有带n版本的snprintf函数,可参考如下示例,使用sprintf,在接收字符串时,加上精度说明符,确定接收的长度,以免造成缓冲区溢出。
#define BUF_SIZE 128
void Compliant()
{
 char buffer[BUF_SIZE + 1];
 sprintf(buffer, "Usage: %.100s argument\n", argv[0]); /*【修改】字符串加上精度说明符 */
 /* ...do something... */
}
通过精度限制从argv[0] 中只能拷贝 100 个字节。
4 整数安全
C99标准定义了整型提升(integer promotions)、整型转换级别(integer conversion rank)以及普通算术转换(usual arithmetic conversions)的整型操作。不过这些操作实际上也带来了安全风险。
规则4.1:禁止无符号整数运算时出现反转
说明:反转是指无法用无符号整数表示的运算结果将会根据该类型可以表示的最大值加1执行求模操作。将运算结果用于以下之一的用途,应防止反转:
 作为数组索引
 指针运算
 作为对象的长度或者大小
 作为数组的边界
 作为内存分配函数的实参
错误示例:下列代码可能导致相加操作产生无符号数反转现象。
INT32 NoCompliant(UINT32 ui1, UINT32 ui2, UINT32 * ret)
{
 if( NULL == ret )
 {
  return ERROR;
 }
 *ret = ui1 + ui2;
 /*上面的代码可能会导致ui1加ui2产生无符号数反转现象,譬如ui1 = UINT_MAX且ui2 = 2;这可能会导致后面的内存分配数量不足或者产生易被利用的潜在风险;*/
 return (OK);
}
推荐做法:
INT32 Compliant(UINT32 ui1, UINT32 ui2, UINT32 * ret)
{
 if( NULL == ret )
 {
  return ERROR;
 }
 if((UINT_MAX - ui1) < ui2) //【修改】确保无符号整数运算时不会出现反转
 {
  return ERROR;
 }
 else
 {
  *ret = ui1+ ui2;
 }
 return OK;
}
延伸阅读材料:漏洞VU#551436就是因为反转问题,导致分配内存空间不足,引发堆溢出。
规则4.2:禁止有符号整数运算时出现溢出
说明:整数溢出是是一种未定义的行为,意味着编译器在处理有符号整数溢出时具有很多选择。将运算结果用于以下之一的用途,应防止溢出:
? 作为数组索引
? 指针运算
? 作为对象的长度或者大小
? 作为数组的边界
? 作为内存分配函数的实参
错误示例:下列代码中两个有符号整数相乘可能会产生溢出。
INT32 NoCompliant(INT32 si1, INT32 si2, INT32 *ret)
{
 if ( NULL == ret )
 {
  return ERROR;
 }
 *ret = si1 * si2;
 /* 上面的代码可能会产生两个有符号整数相乘可能会产生溢出,譬如si1 = INT_MAX且si2 非0;*/
 return OK;
}
推荐做法:
INT32 Compliant(INT32 si1, INT32 si2, INT32 *ret)
{
 if ( NULL == ret )
 {
  return ERROR;
 }
 INT64 tmp = (INT64)si1 *(INT64)si2; /*【修改】确保有符号整数运算时不会出现溢出 */
 if((INT_MAX < tmp) || (INT_MIN > tmp))
 {
  return ERROR;
 }
 *ret = si1 * si2;
 return OK;
}
延伸阅读材料:整数溢出可能导致缓冲区溢出以及任意代码执行。Apple Mac OS X 10.3及以前版本在处理GIF文件时,存在整数溢出漏洞,可被利用执行任意代码。攻击者可以将特定的GIF文件放在Web页面或者邮件附件中,诱使目标打开此文件从而触发利用。具体可参考US-CERT披露的漏洞VU#559444。
规则4.3:禁止整型转换时出现截断错误
说明: 将一个较大整型转换为较小整型,并且该数的原值超出较小类型的表示范围,就会发生截断错误,原值的低位被保留而高位被丢弃。截断错误会引起数据丢失,甚至可能引发安全问题。特别是将运算结果用于以下用途:
 作为数组索引
 指针运算
 作为对象的长度或者大小
 作为数组的边界(如作为循环计数器)
错误示例:数据类型强制转化导致数据被截断。
INT32  NoCompliant(UINT32 ui, INT8 *ret)
{
 if( NULL == ret )
 {
  return ERROR;
 }
 *ret = (INT8)ui;
 /*上面的代码会导致数据被截断,譬如ui = UINT_MAX场景下*/
 return (OK);     
}
推荐做法:
INT32  Compliant(UINT32 ui, INT8 *ret)
{
 if(NULL == ret)
 {
  return ERROR;
 }
 if(SCHAR_MAX >= ui) //【修改】确保整型转换时不会出现截断
 {
  *ret = (INT8)ui;
 }
 else
 {
  return ERROR;
 }   
 return OK;
}
规则4.4:禁止整型转换时出现符号错误
说明: 有时从带符号整型转换到无符号整型会发生符号错误,符号错误并不丢失数据,但数据失去了原来的含义。
带符号整型转换到无符号整型,最高位(high-order bit)会丧失其作为符号位的功能。如果该带符号整数的值非负,那么转换后值不变;如果该带符号整数的值为负,那么转换后的结果通常是一个非常大的正数。
错误示例:符号错误绕过长度检查
#define BUF_SIZE 10
int main(int argc, char* argv[])
{
 int length;
 char buf[BUF_SIZE];
 if (argc != 3)
 {
  return -1;
 }
 length = atoi(argv[1]); //【错误】atoi返回值可能为负数
 if (length < BUF_SIZE)  // len为负数,长度检查无效
 {
  memcpy(buf, argv[2], length); /* 带符号的len被转换为size_t类型的无符号整数,负值被解释为一个极大的正整数。memcpy()调用时引发buf缓冲区溢出*/
  printf("Data copied\n");
 }
 else
 {
  printf("Too many data\n");
 }
}
推荐做法1:将length声明为无符号整型,这样符号错误后产生的极大正整数可以在与BUF_SIZE比较时检查出来;
推荐做法2:在长度检查时,除了要保证长度小于BUF_SIZE,还要保证长度大于0。
规则4.5:把整型表达式比较或赋值为一种更大类型之前必须用这种更大类型对它进行求值
说明:若一个整型表达式与一个很大长度的整数类型进行比较或者赋值为这种类型的变量,需要对该整型表达式的其中一个操作数类型显示转换为更大长度的整数类型,用这种更大的进行求值。这里所说的更大整数类型是相对整型表达式的操作数类型而言,譬如整型表达式的操作数类型是unsigned int ,则该规则所说的更大类型是指 unsigned long long。
错误示例:数据类型不一致导致整型表达式赋值错误。
void *NoCompliant(UINT32 blockNum)
{
 if(0 == blockNum )
 {
  return NULL;
 }
 UINT64 alloc = blockNum * 16;
 /*blockNum为32位的无符号数,两个32位的数相乘仍为32位的数,这会导致
 alloc <= UNIT_MAX始终为TRUE.*/
 return (alloc <= UINT_MAX)?malloc(blockNum*16):NULL;
}
 /*...申请的内存使用后free...*/
推荐做法:
void *Compliant(UINT32 blockNum)
{
 if(0 == blockNum )
 {
  return NULL;
 }
 UINT64 alloc = (UINT64)blockNum * 16; /*【修改】确保整型表达式转换时不出现数值错误 */
 return (alloc <= UINT_MAX)?malloc(blockNum*16):NULL;
}
 /*...申请的内存使用后free...*/
建议4.1:避免对有符号整数进行位操作符运算
说明:位操作符(~、>>、<<、&、^、|)应该只用于无符号整型操作数,因为有符号整数上的有些位操作的结果是由编译器所决定的,可能会出现出乎意料的行为或编译器定义的行为。
错误示例:对有符号数作位操作运算。
#define BUF_LEN (4)
INT32 NoCompliant(void)
{
 INT32 ret = 0;
 INT32 i = 0x8000000; //【不推荐】避免使用有符号数作位操作符运算
 INT8 buf[BUF_LEN];
 memset(buf,0,BUF_LEN);
 ret = snprintf(buf, BUF_LEN, "%u", i >> 24);
 /* i >> 24的结果是0xFFFFFFF8(10进制4294967288),导致转换为
 一个字符串时,长度超过BUF_LEN,无法存储在buf中,因此被snprintf截
 断;若是采用sprintf, 这个例子就会产生缓冲区溢出*/
 if(-1 == ret || BUF_LEN <= ret)
 {
  return ERROR;
 }
 return OK;
}
推荐做法:
#define BUF_LEN (4)
INT32 Compliant(void)
{
 INT32 ret = 0;
 UINT32 i = 0x8000000;//【修改】使用无符号代替有符号数作位操作符运算
 INT8 buf[BUF_LEN];
 memset(buf, 0, BUF_LEN);
 ret = snprintf(buf, 4, "%u", i >> 24);
 if(-1 == ret || BUF_LEN <= ret)
 {
  return ERROR;
 }
 return OK;
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值