Coverity代码扫描-不安全库函数的解决方案

Coverity代码扫描-不安全库函数的解决方案

1、引言

因C语言在大多数情况下注重效率,故C语言中的库函数不会自动进行边界检查处理。然而,获得效率的代价是C 程序员必须十分警觉以避免缓冲区溢出问题。C 语言中大多数缓冲区溢出问题可以直接追溯到标准 C 库。最有害的罪魁祸首是不进行自变量检查的、有问题的字符串操作(strcpy、strcat、sprintf 和 gets)。一般来讲,像“避免使用 strcpy()”和“永远不使用 gets()”这样严格的规则接近于这个要求。本文旨在介绍coverity代码静态扫描工具对C语言中不安全库函数的扫描过程,并针对不安全库函数扫描产生的**“越界风险”**进行处理。


2、Coverity代码扫描警告

图2-1


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wh5Gct8K-1692947339129)(Coverity代码扫描-不安全库函数的解决方案.assets/20230824-111038.jpg)]

图2-2


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XNUTAGXe-1692947339131)(Coverity代码扫描-不安全库函数的解决方案.assets/20230824-111440.jpg)]

图2-3

上图为coverity代码静态扫描工具,在扫描C语言不安全库函数时上报的警告。当应用层或驱动层在调用strcpy、strncpy、strcat、sprintf、snprintf、memcpy和memmove等不安全库函数时,由于库函数不会自动地进行边界检查,故存在”内存越界“的风险。如使用strcpy()函数将源字符串复制到缓冲区时,没有指定要复制字符的具体数目。当源字符串碰巧作为用户输入字符串,且不限制其输入长度时,易出现源字符串长度大于目标字符串的长度,导致内存越界。即使使用strncpy()函数复制指定长度的字符串,但并不能保证目标字符串以空字符(‘\0’)终止。如果源字符串的长度大于等于指定的复制长度,目标字符串将不会被正确终止,可能导致后续对该字符串的处理出现问题


3、C语言不安全函数表

函数严重性解决方案
gets最危险使用 fgets(buf, size, stdin)。
strcpy很危险改为使用 strncpy。
strcat很危险改为使用 strncat。
sprintf很危险改为使用 snprintf,或者使用精度说明符。
scanf很危险使用精度说明符,或自己进行解析。
sscanf很危险使用精度说明符,或自己进行解析。
fscanf很危险使用精度说明符,或自己进行解析。
vfscanf很危险使用精度说明符,或自己进行解析。
vsprintf很危险改为使用 vsnprintf,或者使用精度说明符。
vscanf很危险使用精度说明符,或自己进行解析。
vsscanf很危险使用精度说明符,或自己进行解析。
strecpy很危险确保分配的目的地参数大小是源参数大小的四倍。
strtrns危险手工检查来查看目的地大小是否至少与源字符串相等。
syslog中等危险在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。
getopt中等危险在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。
getchar中等危险如果在循环中使用该函数,确保检查缓冲区边界。
fgetc中等危险如果在循环中使用该函数,确保检查缓冲区边界。
getc中等危险如果在循环中使用该函数,确保检查缓冲区边界。
read中等危险如果在循环中使用该函数,确保检查缓冲区边界。
memcpy低危险确保缓冲区大小与它所说的一样大。
snprintf低危险确保缓冲区大小与它所说的一样大。
strncpy低危险确保缓冲区大小与它所说的一样大。

3、C语言部分不安全函数解决方式

为解决coverity静态工具扫描C语言不安全库函数产生的警告,本节将封装C语言不安全库函数,并在封装函数中进行边界检查,添加空字符(‘\0’)终止

a. strcpy和strncpy封装

/* 代码中尽量避免直接使用strcpy字符拷贝函数 */

/* strcpy 封装函数 */
int32_t strcpy_s(char *pszDest, size_t nDestSize, const char *pszSrc)
{
    size_t src_size = 0;

    if ( !pszDest || !nDestSize || !pszSrc )
        return ERR_PARAMETER;
    
    src_size = strlen(pszSrc) + 1;
    if ( nDestSize <= src_size )
        src_size = nDestSize;
    strncpy(pszDest, pszSrc, src_size);
    pszDest[nDestSize - 1] = 0;
    if ( src_size <= nDestSize )
        return ERR_OK;
    else
        return ERR_BUFFERSIZE;
}

/* strncpy 封装函数 */
int32_t strncpy_s(char *pszDest, size_t nDestSize, const char *pszSrc, size_t nBytesCopy)
{
    size_t src_size = 0;
    size_t dest_size = 0;

    if ( !pszDest || !nDestSize || !pszSrc)
        return ERR_PARAMETER;
    
    src_size = MIN(strlen(pszSrc) + 1, nBytesCopy);
    if ( nDestSize <= src_size )
        src_size = nDestSize;
    strncpy(pszDest, pszSrc, src_size);
    dest_size = nDestSize - 1;
    if ( dest_size > src_size )
        dest_size = src_size;
    pszDest[dest_size] = '\0';
    if ( nBytesCopy <= nDestSize )
        return ERR_OK;
    else
        return ERR_BUFFERSIZE;
}

b. strcat和strncat封装

/* strcat 封装函数 */
int32_t strcat_s(char *pszDest, size_t nDestSize, const char *pszSrc)
{
    size_t dest_offset = 0;
    size_t src_size = 0;

    if ( !pszDest || !nDestSize || !pszSrc )
        return ERR_PARAMETER;
   
    src_size = strlen(pszSrc) + 1;
    dest_offset = strlen(pszDest);
    strncpy(&pszDest[dest_offset], pszSrc, MIN(nDestSize - dest_offset, src_size));
    pszDest[nDestSize - 1] = '\0';
    if ( dest_offset + src_size <= nDestSize )
        return ERR_OK;
    else
        return ERR_BUFFERSIZE;
}

/* strncat 封装函数 */
int32_t strncat_s(char *pszDest, size_t nDestSize, const char *pszSrc, size_t nBytesCat)
{
    size_t dest_offset = 0;
    size_t src_size = 0;

    if ( !pszDest || !nDestSize || !pszSrc)
        return ERR_PARAMETER;
   
    src_size = MIN(strlen(pszSrc) + 1, nBytesCat);
    dest_offset = strlen(pszDest);
    strncpy(&pszDest[dest_offset], pszSrc, MIN(nDestSize - dest_offset, src_size));
    pszDest[nDestSize - 1] = '\0';
    if ( dest_offset + src_size <= nDestSize )
        return ERR_OK;
    else
        return ERR_BUFFERSIZE;
}

c. sprintf封装

/* 使用sprintf函数,当目标字符长度较小时,易出现内存越界导致程序终止 
 * 故尽量使用相对安全的snprintf来替代程序中的sprintf函数。此外,尽
 * 管snprintf不会崩溃退出,但当要存储的字符串长度大于目标字符串长度时,
 * 会只保存目标串长度-1长度的字符串,并在末尾补\0字符串结束符,出现字
 * 符截断现象,故可对snprintf函数进行封装,提高程序的健壮性。*/   
int32_t snprintf_s(char *buf, size_t nDestSize, const char *format, ...)
{
    va_list args;
    int32_t ret = 0;
    
    if ( !buf || !nDestSize)
        return ERR_PARAMETER;

    va_start(args, format);
 	ret = vsnprintf(buf, nDestSize, format, args);
    va_end(args);
   
    if ( ret <= nDestSize - 1)
        return ERR_OK;
    else
        return ERR_BUFFERSIZE;
}

d. memcpy和memmove封装

/* memcpy 封装函数 */
int32_t memcpy_s(void *pDest, size_t ulDestSize, const void *pSrc, size_t ulToCopy)
{
    if ( !pDest || ulDestSize < ulToCopy || !pSrc )
        return ERR_PARAMETER;

    memcpy(pDest, pSrc, ulToCopy);
    return ERR_OK;
}

/* memmove 封装函数 */
int32_t memmove_s(void *pDest, size_t ulDestSize, const void *pSrc, size_t ulToCopy)
{
    if ( !pDest || ulDestSize < ulToCopy || !pSrc )
        return ERR_PARAMETER;

    memmove(pDest, pSrc, ulToCopy);
    return ERR_OK;
}

4、结论

综上所述,本文主要介绍了C语言不安全库函数使用过程中存在的隐患,当程序调用C语言库函数时,尽量使用危险程序较低的库函数,如(strncpy替代strcpy,strncat替代strcat、snprintf替代sprintf等)。其次,针对没有相同功能,且风险程度较高的库函数,可封装该类型的库函数,进行边界检查,以及添加空字符(‘\0’)终止符等操作,来提高代码的健壮性。


5、参考文档


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值