赞
踩
在很多领域,如网络安全、汽车智能驾驶等都非常注重代码安全,这是确保产品质量,减少产品事故的关键环节,很多大公司例如华为等都对产品的安全编码有着极高要求,并有相应的一套完整编程规范来引导,少数对产品质量要求极高的公司如华为还会有相应的安全编码考试,以此培养员工的安全编码思想和意识。安全编码不仅关乎产品质量,在某些场景下更是避免软件灾难的最重要防线,对于从事软件开发的人员来说,安全编码思想的重要性不言而喻。
开发人员在安全编码过程中应该保持如下的假设:
外部数据定义如下:
基于以上假设,得出安全编码基本思想:
(1)程序在处理外部数据时必须经过严格的合法性校验;
(2)尽量减少代码的攻击面。即代码的实现应该尽量简单,避免与外部环境做多余的数据交互。
(3)通过防御性的编码策略来弥补潜在的编码人员的疏忽。这些措施包括:
安全编码的内容非常广泛,涉及变量、断言、函数、循环、异常机制、类、安全退出、字符串/数组操作、整数、内存、不安全函数、文件输入/输出、敏感信息处理等各方面,这里简略叙述最重要的内存、函数、字符串处理等几个模块,以期能达到举一反三作用,引导大家树立安全编码思想。
在C/C++编码中,内存的使用要非常谨慎,因为内存结合指针使用时非常灵活,稍微使用不当就可能造成段错误或内存泄漏等严重问题。内存的安全编码应该遵循如下要求:
内存申请的大小值可能来自外部数据,必须检查其合法性,防止过多的、非法的申请内存。不能申请长度为0的内存。
- int Foo(int size)
- {
- if (size <= 0) {
- //error
- ...
- }
-
- ...
- char *msg = (char *)malloc(size);
- if (msg == NULL) {
- ...
- }
- ...
- }
如下
- char *msg = (char *)malloc(size);
- if (msg == NULL) {
- ...
- }
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;
- }
内存释放之后,如果其指针未立即设置未NULL,也未分配一个新的对象,那么可能会导致该指针在后续代码中产生双重释放的风险,还存在访问已释放内存的危险。
如下
- char *msg = NULL;
- ...
- msg = (char *)malloc(len);
- ...
- if (...) {
- free(msg); // 在此分支对内存进行了释放
- msg = NULL; // 释放后要立即将指针赋值为NULL,否则会为后续代码带来风险
- }
- ...
- if (msg != NULL) {
- free(msg);
- msg = NULL;
- }
realloc函数原型如下:
void *realloc(void *p, int size);
该函数随着参数的不同,其行为也不一样,也就是一个函数被赋予了多种不同行为。这不是一个设计良好的函数,极易引发各种bug。
(6)禁止使用alloca函数申请栈上内存
该函数在有些平台下不支持,使用alloca函数会降低程序的兼容性和可移植性。该函数在栈帧里申请内存,申请的大小很可能超过栈的边界,影响后续的代码执行。
通过函数参数传递数组或一块内存进行写操作时,函数参数必须同时传递数组元素个数或所传递的内存块大小,否则函数在使用数组下标或访问内存偏移时,无法判断下标或便宜的合法范围,产生越界访问的漏洞。
如下
- 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 *类型,且参数作为写内存的缓冲区,则需要传长度参数。
如果参数是指针型类型,且内容不会被修改,应定义为const类型。
- int Foo(const char *filePath)
- {
- ...
- int fd = open(filePath, ...);
- ...
- }
不可重入函数在多线程环境下,其执行结果不能达到预期效果,需谨慎使用。常见的不可重入函数包括:rand、srand、getenv、getenv_s、strtok、strerro、setlocale、atomic_init、tmpnam、gethostbyaddr、gethostbyname等。
如果字符串或者指针作为函数参数,为了防止空指针引用错误,在引用前必须确保该参数不为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);
- ...
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。