当前位置: 首页 > news >正文

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);...
}

相关文章:

  • ssh安装和Gitee(码云)源码拉取
  • 设计模式篇---代理模式
  • Kafka 最佳实践:构建可靠、高性能的分布式消息系统
  • 基于JAVA+SpringBoot+Vue的前后端分离的医院信息智能化HIS系统
  • linux下查看文件当下的所有文件的大小和查找大文件
  • 【Spring Boot 源码学习】ApplicationListener 详解
  • JWT的原理
  • pyqt5使用Designer实现按钮上传图片
  • Vue 纯css方式实现自定义进度条组件
  • SQL注入概述
  • ffmpeg6.0之ffprobe.c源码分析二-核心功能源码分析
  • git 常用的使用方法
  • 初识Redis
  • 「Verilog学习笔记」多bit MUX同步器
  • Java 中的抽象类与接口:深入理解与应用
  • 4个实用的微服务测试策略
  • Git学习与使用心得(1)—— 初始化
  • Java,console输出实时的转向GUI textbox
  • Javascript弹出层-初探
  • k8s如何管理Pod
  • python 学习笔记 - Queue Pipes,进程间通讯
  • Redis 中的布隆过滤器
  • Vue 重置组件到初始状态
  • 删除表内多余的重复数据
  • 小程序 setData 学问多
  • 自动记录MySQL慢查询快照脚本
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • ​香农与信息论三大定律
  • #java学习笔记(面向对象)----(未完结)
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (3)nginx 配置(nginx.conf)
  • (AngularJS)Angular 控制器之间通信初探
  • (附源码)ssm高校实验室 毕业设计 800008
  • (四) Graphivz 颜色选择
  • (四)activit5.23.0修复跟踪高亮显示BUG
  • (四)JPA - JQPL 实现增删改查
  • (转)【Hibernate总结系列】使用举例
  • (转)一些感悟
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .NET C# 使用GDAL读取FileGDB要素类
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .NET Core中的去虚
  • .NET MVC 验证码
  • .NET 命令行参数包含应用程序路径吗?
  • .net程序集学习心得
  • .net实现头像缩放截取功能 -----转载自accp教程网
  • .py文件应该怎样打开?
  • @Autowired 和 @Resource 区别的补充说明与示例
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • [2009][note]构成理想导体超材料的有源THz欺骗表面等离子激元开关——
  • [2017][note]基于空间交叉相位调制的两个连续波在few layer铋Bi中的全光switch——
  • [240903] Qwen2-VL: 更清晰地看世界 | Elasticsearch 再次拥抱开源!
  • [AI]文心一言出圈的同时,NLP处理下的ChatGPT-4.5最新资讯
  • [Bugku]密码???[writeup]