Huawei: 安全编码&代码坏味道
安全编码&代码坏味道
安全编码
引言
安全编码之基本思想
程序在处理外部数据时必须经过严格的合法性校验
禁用不用的端口,尽量减少代码的攻击面
编码在一定范围内应该对不信任模块间采取防御式编程,以此来弥补潜在的人工疏忽
HCSEC黄金规则
多所有的外部数据进行边界和格式检查
对于内部数据需要划分信任域进行检查
在形成系统执行命令前经过充分净化
只有线程安全的代码才能应用在多线程中
所有STL只支持同时读,只要有一个线程发生写操作,其他线程都可能存在问题
尽量减少全局变量、静态变量或单例的使用,控制共享的变量的作用域
避免重复代码
废弃代码存在安全隐患
- 未编译、未链接的代码,以及注释的代码的安全问题仍然是安全问题
安全编程规范
字符串与数组操作
确保有足够的存储空间,防止缓冲区溢出
调用格式化函数时,禁止format参数由外部可控,否则可能造成字符串格式化漏洞
若在调用printf时没有传入参数列表,那么printf会按照format格式去栈中取对应的参数(地址偏移)
可以利用编译器检查格式化参数类型与实际参数的匹配性:
1
add_definitions(-DSECUREC_SUPPORT_FORMAT_WARNING=1)
正确使用安全函数
安全函数增强了哪些安全特性?
强化边界检查:在接口参数中增加buffer长度的参数
保证字符串以’\0’结尾,防止防伪buffer边界之外的信息
发生缓冲区溢出时,将目的缓冲区的首字节置为0
正确设置安全函数中的destMax参数(包含结束符)
数组或指针作为函数参数时,必须同时将其长度作为函数的参数
常见错误
使用宏、常量或魔鬼数字作为destMax
参数是数组,使用形参中数组的长度
- 形参看上去数组有长度,但实际上会退化为指针
使用宏或函数对安全函数进行封装
必须检查安全函数的返回值,并进行正确的处理
整数
注意溢出
除法和取模时保证除数不为0
内存
在做内存操作时,应该做好严格的边界校验
空指针、已经释放的野指针
尽量使用引用、智能指针,而非指针
文件操作
不规范的文件路径,导致文件泄露
- 使用realpath函数对路径进行规范化,之后再对路径进行校验
打开文件后不关闭,导致文件句柄耗尽而无法打开文件
- 尽量使用守护类、自定义析构的unique_ptr
代码坏味道
- 代码坏味道: 《重构 改善既有代码的设计》By Martin Fowler
一、 代码坏味道概述
代码坏味道不是功能性问题,主要表现为 可读性 和 可维护性 的一些问题
常见的代码坏味道: 冗余和重复 、 局部膨胀 、 耦合结构不良
不同层次的坏味道
直观: 一般为规范性问题,可以通过代码扫描工具识别。
微观: 主要为软件细节设计的问题,一般比较具体明确,可以通过查看代码进行识别。
宏观: 代码架构上的整体的问题,主要为软件高系统设计的问题,一般比较主观抽象,需要结合业务领域知识进行识别。 例:类职责不单一
不同类别的坏味道
膨胀剂: 太长的方法、太大的类、太多参数、基本类型偏执、数据泥团
滥用OO:switch语句(可以用多态)、临时字段、被拒绝的馈赠(为避免组合而使用继承)、异曲同工的类
难以修改: 发散式修改(类/函数承载了过多功能)、霰弹式修改(有一处修改,其他地方也需要修改)
可有可无:注释、重复代码、冗赘类/元素、数据类、死代码、夸夸其谈未来性
耦合: 依恋情结(需要调用一堆接口来实现一个功能)、内幕交易、消息链、中间人
神秘命名、全局数据、可变数据、循环语句
二、 冗余和重复
重复代码 Duplicated Code
多个地点有同样的程序片段
万恶之源 一旦变化,到处修改,漏改一处就是bug
解决方案
同一个类的两个函数有重复代码: 提取
互为兄弟的类有重复代码: 移到父类
互为兄弟的类有相似代码: 在父类创建模板方法,差异部分交给子类实现
毫不相关的类出现重复: 先提炼到一个类里,然后在另一个类里使用
CAUTION 过多消除重复可能会增强耦合
过多注释 Comments
冗余注释之所以存在,是因为代码很糟糕
破坏可读性,有些误导性的注释让维护人员陷入困境
解决方案
用来解释一段代码用来做什么时,把它提取成函数,修改函数名
注释why,而不仅是how和what
夸夸其谈未来性 Speculative Generality
过度关注未来可能的变化,增加了一些不必要的东西
滥用设计模式,导致难以找到主要的逻辑走向
过度的设计导致代码不易理解、错误不易定位,可能还会降低代码执行的效率
放置过量的callback或特殊情况来处理一些非必要的事情
解决方案
如果某个抽象类没有太大作用,使用折叠层次Collapse Hierarchy删除抽象类
非必要的delegation可以使用内联类Inline Class代替
函数的某些参数未被使用,可以实施Remove Parameter
函数名称带有多余的意味,应该实施Rename Method让他现实一点
解决方案
太多参数:把多个参数用类封装
switch: map + find
循环查找: find + lambda
三、 局部膨胀
过长参数列表 Long Parameter List
太长参数列表难以理解
解决方案
一个参数可以通过另一个参数查询时,使用以查询取代参数Replace Parameter with Query
多个参数属于同一个数据结构时,直接传入数据结构的对象以保持对象完整Preserve Whole Object
多个参数有关联,总是同时使用,可以引入参数对象Introduce Parameter Object
某个参数是标记用于区分函数行为的,移除标记参数Remove Flag Argument
多个函数有相同的参数,实际上是围绕这些参数工作,可以将多个函数组合成类Combine Functions into Class
注意全局变量、静态变量等隐形传入的“参数”
常常同时存在 过长函数、数据泥团、基本类型偏执 等其他坏味道,需要一并消除
四、 耦合结构不良
发散式变化Divergent Change
某个模块因为不同的原因在不同的方向上变化
职责过多,理解困难
解决方案
按不同变化方向进行拆分
如果功能按照某种步骤进行,可以使用拆分阶段Split Phase将不同的阶段分开
模块的职责是否单一根据所在架构层次的不同而不同
霰弹式修改Shotgun Surgery
代码维护时多处修改,容易遗漏
解决方案
将功能集中到一起,常用到搬移函数Move Function、搬移字段Move Field、搬移语句到函数Move Statements into Function 等搬移特性的重构手法,集中到负责的一个模块
如果功能本身在架构层次上不应该分开,可以使用内联函数Inline Function、内联类Inline Class
重复switch Repeated Switches
解决方案
面向对象的语言: 以多态取代条件表达式
面向过程的语言: 表驱动、策略模式
不是所有switch都需要重构,以下情况需要重构
类型码不断增加
单个case处理多件事
相同的switch语句分散在多处代码中
遵循单一职责原则
减少N:1:N的调用
临时字段Temporary Field
某个变量仅为某种特定场景而设,或只在该对象某一段生命周期内生效
不易被理解,可能会被误用
解决方案
Extract Class
作为参数传递
五、 代码坏味道工程工具
静态检查工具Xlint、Clang、FindBugs、CheckStyle、SourceMonitor,结合IDE使用,可以在写代码时识别规范性代码坏味道
CodeDEX提交代码时识别规范性或更高层次的坏味道
代码度量工具
- Post title:Huawei: 安全编码&代码坏味道
- Post author:Franky
- Create time:2023-03-09 21:35:29
- Post link:https://franky.pro/2023/03/09/Huawei-安全编码-代码坏味道/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.