C/C++语言的安全编码规范

在很多领域,如网络安全领域、汽车智能驾驶等领域都非常注重代码安全,这是确保产品质量和安全的关键环节,很多大公司如华为等都对产品的安全编码有着极高要求,并有相应的一套完整编程规范来引,像华为这种对产品质量要求极高的公司,还会有相应的安全编码考试,以培养员工的安全编码思想。安全编码不仅关于产品质量,在某些场景下更是避免软件灾难的最重要防线,对于从事软件开发人员来说不得不重视。.

一、安全编码的基本思想

开发人员在安全编码过程中应该保持如下的假设:

1、程序所处理的所有外部数据都是不可信的攻击数据;

外部数据定义如下:

  • 文件
  • 注册表
  • 网络
  • 环境变量
  • 命令行
  • 用户输入
  • 用户态数据
  • 进程间通信
  • 函数参数(对于API)
  • 全局变量(其他线程会修改全局变量)

2、攻击者时刻试图监听、篡改、破坏程序的运行环境、外部数据。

基于以上假设,得出安全编码基本思想:

(1)程序在处理外部数据时必须经过严格的合法性校验;

(2)尽量减少代码的攻击面。即代码的实现应该尽量简单,避免与外部环境做多余的数据交互。

(3)通过防御性的编码策略来弥补潜在的编码人员的疏忽。这些措施包括:

  • 变量声明应该赋初值
  • 谨慎使用全局变量
  • 避免使用功能复杂、易用错的函数
  • 禁用易用错的编码器的机制
  • 小心处理资源访问过程
  • 不要改变操作系统的运行环境
  • 合理使用调试断言(ASSERT)
  • 严格的错误处理

二、安全编码的要求

安全编码的内容非常广泛,涉及变量、断言、函数、循环、异常机制、类、安全退出、字符串/数组操作、整数、内存、不安全函数、文件输入/输出、敏感信息处理等各方面,这里简略叙述最重要的内存、函数、字符串处理等几个模块,以期能达到举一反三作用,引导大家树立安全编码思想。

1、内存

在C/C++编码中,内存的使用要非常谨慎,因为内存结合指针使用时非常灵活,稍微使用不当就可能造成段错误或内存泄漏等严重问题。内存的安全编码应该遵循如下要求:

(1)内存申请前,必须对申请内存的大小进行合法性校验

内存申请的大小值可能来自外部数据,必须检查其合法性,防止过多的、非法的申请内存。不能申请长度为0的内存。

int Foo(int size)
{
    if (size <= 0) {
        //error
        ...
    }

    ...
    char *msg = (char *)malloc(size);
    if (msg == NULL) {
        ...
    }
    ...
}

(2)内存分配后必须判断是否成功

如下

char *msg = (char *)malloc(size);
if (msg == NULL) {
    ...
}

(3)禁止引用未初始化的内存

malloc、new分配出来的内存没有被初始化为0,要确保内存被引用前是被初始化的。

可使用memset来初始化申请的内存,如下

int *CalcMetrixColomn(int **metrix, int *param, int size)
{
    int *result = NULL;
    ...
    int bufsize =  size * sizeof(int);
    ...
    result = (int *)malloc(bufsize);
    ...
    int ret = memset(result, 0, bufsize);  // 确保内存被初始化后才被引用
    ...
    result[0] += metrux[0][0] * param[0];
    ...
    return result;
}

(4)内存释放之后要赋予新值

内存释放之后,如果其指针未立即设置未NULL,也未分配一个新的对象,那么可能会导致该指针在后续代码中产生双重释放的风险,还存在访问已释放内存的危险。

如下

char *msg = NULL;
...
msg = (char *)malloc(len);
...
if (...) {
    free(msg);  // 在此分支对内存进行了释放
    msg = NULL;  // 释放后要立即将指针赋值为NULL,否则会为后续代码带来风险
}
...
if (msg != NULL) {
    free(msg);
    msg = NULL;
}

(5)禁止使用realloc函数

realloc函数原型如下:

void *realloc(void *p, int size);

该函数随着参数的不同,其行为也不一样,也就是一个函数被赋予了多种不同行为。这不是一个设计良好的函数,极易引发各种bug。

(6)禁止使用alloca函数申请栈上内存

该函数在有些平台下不支持,使用alloca函数会降低程序的兼容性和可移植性。该函数在栈帧里申请内存,申请的大小很可能超过栈的边界,影响后续的代码执行。

2、函数

(1)数组作为函数参数时,必须同时将其长度作为函数的参数

通过函数参数传递数组或一块内存进行写操作时,函数参数必须同时传递数组元素个数或所传递的内存块大小,否则函数在使用数组下标或访问内存偏移时,无法判断下标或便宜的合法范围,产生越界访问的漏洞。

如下

int ParseMsg(BYTE *msg, int msgLen)
{
    ASSERT(msg != NULL);
    ASSERT(msgLen != 0);
    ...
}
...
int len = ...
BYTE *msg = (BYTE *)malloc(len);
....
ParseMsg(msg, len);  // 将msg的大小作为参数传递到函数中
....

以上代码msg为申请的内存块,对于固定长度的数组,也必须将其大小作为函数的参数传入。

对于const char *类型的参数,它的长度是通过'\0'的位置计算出来,不需要传长度参数。但如果是char *类型,且参数作为写内存的缓冲区,则需要传长度参数。

(2)不对内容进行修改的指针型参数,定义为const

如果参数是指针型类型,且内容不会被修改,应定义为const类型。

int Foo(const char *filePath)
{
    ...
    int fd = open(filePath, ...);
    ...
}

(3)谨慎使用不可重入函数

不可重入函数在多线程环境下,其执行结果不能达到预期效果,需谨慎使用。常见的不可重入函数包括:rand、srand、getenv、getenv_s、strtok、strerro、setlocale、atomic_init、tmpnam、gethostbyaddr、gethostbyname等。

(4)字符串或指针作为函数参数时,请检查参数是否为NULL

如果字符串或者指针作为函数参数,为了防止空指针引用错误,在引用前必须确保该参数不为NULL,如果上层调用者已经保证了该参数不可能为NULL,在调用本函数时,在函数开始处可以加ASSERT进行校验。 例如下面的代码,因为BYTE *p有可能为NULL,因此在使用前需要进行判断。

int Foo(int *p, int count)
{
    if (p != NULL && count > 0) {
        int c = p[0];
    }
    ...
}
int Foo2()
{
    int *arr = ...
    int count = ...
    Foo(arr, count);
    ...
}

相关推荐

  1. C/C++语言安全编码规范

    2023-12-11 06:50:07       53 阅读
  2. CleanCode、安全编码规范

    2023-12-11 06:50:07       29 阅读
  3. C语言代码编码规范

    2023-12-11 06:50:07       62 阅读
  4. CI/CD与容器编排结合

    2023-12-11 06:50:07       28 阅读
  5. 编程】C++语言编程规范-2

    2023-12-11 06:50:07       53 阅读
  6. 华为C&C++语言编程规范--笔记

    2023-12-11 06:50:07       37 阅读
  7. 编码风格之(1)C语言建议规范

    2023-12-11 06:50:07       61 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-11 06:50:07       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-11 06:50:07       100 阅读
  3. 在Django里面运行非项目文件

    2023-12-11 06:50:07       82 阅读
  4. Python语言-面向对象

    2023-12-11 06:50:07       91 阅读

热门阅读

  1. 计算机视觉-机器学习-人工智能顶会 会议地址

    2023-12-11 06:50:07       49 阅读
  2. 【求职】外企德科-网易游戏测试面试记录

    2023-12-11 06:50:07       55 阅读
  3. git commit语义规范

    2023-12-11 06:50:07       54 阅读
  4. Docker安装教程

    2023-12-11 06:50:07       71 阅读
  5. Spark

    2023-12-11 06:50:07       38 阅读
  6. Spark-Streaming+Kafka+mysql实战示例

    2023-12-11 06:50:07       48 阅读
  7. 《微信小程序开发从入门到实战》学习四十七

    2023-12-11 06:50:07       50 阅读
  8. 学习 Vue 3 源码

    2023-12-11 06:50:07       66 阅读
  9. facebook回传

    2023-12-11 06:50:07       60 阅读
  10. Go简单了解

    2023-12-11 06:50:07       49 阅读
  11. keepalived 高可用主备

    2023-12-11 06:50:07       54 阅读