北冰洋以北 发表于 2024-7-6 01:44:02

寻找适合编写静态分析规则的语言

本文分享自华为云社区《寻找适合编写静态分析规则的语言》,作者:Uncle_Tom。
1. 步伐静态分析的作用

步伐的静态分析是一种在不运行步伐的环境下,通太过析步伐代码来发现潜在的错误、安全漏洞、性能问题以及不符合编码规范的环境的技术。
步伐的静态分析在现代软件安全中扮演着至关重要的脚色。以下是静态分析在软件安全中的一些关键作用:
代码质量包管:
静态分析有助于确保代码符合安全编码标准和最佳实践,从而提高代码的质量和安全性。
参考:代码的安全检视
合规性检查:
许多行业标准和法规要求对软件举行安全合规性检查。静态分析工具可以资助组织确保其软件产物符合这些标准和法规要求。
参考:一图看懂软件缺陷检查涉及的内容
漏洞检测:
静态分析工具可以在代码编写阶段检测潜在的安全漏洞,如SQL注入、跨站脚本攻击(XSS)、缓冲区溢出等。
参考:2023年最具威胁的25种安全漏洞(CWE TOP 25)
减少开发本钱:
通过在开发早期阶段发现问题,静态分析可以减少后期修复的本钱和时间,由于后期修复通常本钱更高。
参考:构建DevSecOps中的代码三层防护体系
2. 静态分析工具的业务痛点

随着如今工程项目的代码量越来越大,同时开发框架的快速迭代和出现。静态分析工具所需要覆盖的场景也随之快速的增长,但静态分析工具所提供的是通用的检查能力,以及静态分析工具有限的迭代速度,无法满足客户不断出现的各种差异化需求。
现在静态分析工具的重要痛点:
2.1. 无法开发自界说规则

大多数静态分析工具由于设计之初多是为了解决特定的编码问题,以是没有思量到后期的扩展和由用户完成规则的开发。如果需要提供自界说开发能力,需要从架构上重新设计,或者由于检查效率的问题,无法提供通用的检查设置和自界说能力。这将导致无法快速提供客户特定的需求的问题检查。用户只能通过需求反馈的方式,等待工具下个版本的发布,需要的闭环周期很长。
2.2. 对误报和漏报的规则无法快速修改

静态分析工具由于是对代码的静态分析,输入存在不确定性,这些不确定性导致工具在分析策略在上近似(Over-approximation)、下近似(Under-approximation)以及检查效率三者之间寻求某种平衡,这三个因素互相影响、互相制约。

[*]上近似是指分析工具大概将一些实际上不会发生的步伐行为错误地识别为大概发生的。换句话说,它大概导致分析结果过于宽泛,将一些安全的状态或行为错误地标记为不安全的。这也就导致了误报(false positives),即错误地将安全的代码标记为有问题。
[*]下近似是指分析工具大概未能识别出实际上会发生的步伐行为。这意味着分析结果大概过于保守,遗漏了一些潜在的问题。这就导致了漏报(false negatives),即未能发现实际存在的安全问题或错误。
[*]效率是所有利用者一直追求的因素,快了还想快。但哪里有又想马儿跑得快,又想马儿不吃草的好事情。
由于这些原因,静态分析工具通常提供的是一种通用的检查规则,往往不能覆盖特定的场景,或覆盖场景不适合特定用户的利用条件,这也造成检查工具无法克制误报和漏报。比如说:从文件读对有的用户是危险,但对有的用户是安全的,工具无法识别用户读文件的实际场景,只能将所有从文件读设置为危险的。如果用户无法快速对工具规则举行修改,就会被检查结果中的误报或漏报造成困扰。
2.3. 开发自界说规则有肯定的难度

分析引擎提供的自界说开发包,但也需要自界说规则的开发人员掌握静态分析的相干技术,用户上手的难度较大。且由于引擎对API的封装能力,对外提供的检查能力有限,在很大程度上限制了用户自界说规则的实现能力。
基于这些痛点,需要寻找一种适合编写静态分析规则的语言,来降低自界说规则的难度,利用户可以或许直接开发满足自己需求的规则,用户可以自己在很大程度上来控制和解决误报和漏报。
那么什么才是适合用户的编写静态分析规则的语言呢?
3. 寻找适合编写静态分析规则的语言

为了寻找适合用户的编写静态分析规则的语言,我们来看下我们常见的两种编程范式:声明式语言(Declarative Language)和下令式语言(Imperative Language)。这两者语言在如何形貌步伐行为和解决问题的方法上存在根本差异, 但同时各有上风和适用场景, 许多现代编程语言支持这两种范式, 允许步伐员根据具体问题选择最合适的方法。
https://mp.toutiao.com/mp/agw/article_material/open_image/get?code=NzNiYzY0OTE2ZTcwOTQxNjkyMWZiMDZkNGU0Nzk1YzYsMTcyMDE0MTM0MTIxMQ==具体来看一个声明式语言和下令式语言对问题的不同解决方式。问题:
从一个人群中挑出成年人;
具体条件:
选出的人年事大于等于 18 岁。
下令式语言 – Java 语言
public List<Person> selectAdults(List<Person> persons){
    List<Person> result = new ArrayList<>();
    for (Person person : persons) {
      if (person.getAge() >= 18) {
            result.add(person);
      }
    }
    return result;
} 声明式语言 – SQL 语言
SELECT * FROM Persons WHERE Age >= 18;从这个例子可以看出来,声明式语言更适合用户的利用,这也是为什么 SQL 语言在短时间内可以或许迅速的被推广和利用。
声明式语言的特点,也正是我们正在寻找的适合编写静态分析规则的语言。用户只需要关注:“做什么”(What to do),即形貌期望的结果或目标状态,而不指定如何达到这个结果的具体步骤或过程。
我们也可以把这个检查语言称为一种领域特定语言(Domain Specific Language,DSL),为特定领域或问题域定制的语言,专注于解决特定类型的问题。这个语言只专注于 – 编写步伐静态分析的规则。
这里没有直接利用自然语言,重要是自然语言存在表述上的差异和形貌的准确性的问题。固然随着大模子的越来越成熟,直接通过自然语言完成规则的编写,也离我们越来越近了。但不管怎样,在识别到检查条件后,还是需要有一个引擎将这些约束条件转换成具体查询的步伐语言,完成问题代码的搜刮,这就像 SQL 语言负责形貌条件,还需要一个 SQL 的查询引擎,完成 SQL 语言的解析和实施查询。
4. DSL 在步伐静态分析中的应用举例

4.1. 编写检查规则

检查问题:

[*]生产环境中不应该有调试代码。
问题检查条件:

[*]查找所有函数声明
[*]并且(And):函数名以"debug"开头
[*]并且(And):函数只有一个参数
[*]并且(And):参数类型为"java.util.List"
问题代码样例
package com.dsl;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.List;

/**
* 检查问题:生产环境中不应该有调试代码。
* 问题检查条件:
* - 查找所有函数声明;
* - 并且(And):函数名以"debug"开头;
* - 并且(And):函数只有一个参数;
* - 并且(And):参数类型为"java.util.List"。
*/
public class CheckDebug {
    private static final Logger LOG = LogManager.getLogger(CheckDebug.class);

    // 应检查出的问题函数
    public void debugFunction(List<String> msgs) {
      for (String msg : msgs) {
            LOG.error("print debug info: {}", msg);
      }
    }
}编写检查规则
https://bbs-img.huaweicloud.com/blogs/img/20240704/1720074931888666177.gif
DSL 写的检查规则
/**
* 检查问题:生产环境中不应该有调试代码。
* 问题检查条件:
* - 查找所有函数声明;
* - 并且(And):函数名以"debug"开头;
* - 并且(And):函数只有一个参数;
* - 并且(And):参数类型为"java.util.List"。
*/

functionDeclaration fd where
    and(
      fd.name startWith "debug",
      fd.parameters.size() == 1,
      fd.parameters.type.name == "java.util.List"
    );4.1.1. 规则的解读

步伐是由空格分隔的字符串构成的序列。在步伐分析中,这一个个的字符串被称为"token",是源代码中的最小语法单元,是构成编程语言语法的根本元素。
Token可以分为多种类型,常见的有关键字(如if、while)、标识符(变量名、函数名)、字面量(如数字、字符串)、运算符(如+、-、*、/)、分隔符(如逗号,、分号;)等。
步伐在编译过程中,词法分析器(Lexer)读取源代码并将其分解成一系列的token。语法分析器(Parser)会利用这些 token 来构建一个抽象语法树(Abstract Syntax Tree, AST),这个树结构表示了代码的语法结构。这个时候每个 token 也可以称为抽象语法树的节点,树上某个节点的分支就是这个节点的子节点。每个节点都会有节点类型、属性、值。
下面来形貌下规则中利用的 DSL 和需求之间的对应关系。
节点类型、属性、值
在规则中,需要查找的是函数声明。这里利用:functionDeclaration 为代码的函数声明节点。在这个节点下有许多的属性,可以通过“.”的方式获取这些属性。
例如函数节点有:函数名(name)、函数的参数(parameters)等子节点。同时每个属性有自己的类型,以及值。例如:函数名(name)为字符串类型,函数的参数(parameters)一个集合类型;
别名
在编写规则时,界说别名可以显著简化规则编写。在遇到复合条件查询时,建议界说别名,方便后面的利用。
例如:函数声明(functionDeclaration)的别名 “fd”,这样后面可以利用 “fd” 方便了后面对这个函数声明的利用;
集合
函数的参数(parameters)就是一个集合,里面大概会存在 0-n 个参数。对于集合类的节点,可以通过指定集合的索引值得到集合下的子节点。
例如:参数的第一个参数,可以表示为:Parameters。 同样通过 “.” 得到这个参数的其他类型或属性;
内置函数
规则中利用了内置字符串函数。
例如:判断字符串以指定字符串开始的函数,startWith(“debug”),表示判断字符串以 “debug” 开始的字符串;
运算符和条件表达式
规则中的 “==” 是运算符,表示等于。通过运算符将代码的节点类型、属性和具体的值联系在了一起,构成了条件表达式。由此构成了规则需要的条件判断,适配我们期望的约束条件。
条件的组合
通常一个规则需要多个约束或限制条件构成。这里通过并且(and)完成了三个子条件构成的复合逻辑条件表达式。
结论

[*]通过这个案例我们可以看到,这个 DSL 语言非常接近我们的期望的需求表达式;
[*]用户可以通过 DSL 语言快速开发满足需要检查的问题;
[*]用户可以更多的关注如何形貌需要检查的问题,而不需要关注工具是如何的实现。
4.2. 替换已有工具规则,并增长检查条件

4.2.1. 实现原有检查规则

原有检测问题:

[*]继承 java.util.TimerTask 类重写 run 方法,run 方法的实现要有 try-catch 保护。
检查的条件:

[*]查找类继承自 java.util.TimerTask;
[*]并且(and):重写了 run 方法;
[*]并且(and):run 方法中没有 try-catch。
问题代码样例
package com.dsl;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.TimerTask;

/**
* 检查问题:继承 java.util.TimerTask 类重写 run 方法,run 方法的实现要有 try-catch 保护。
* 问题检查条件:
* - 查找类继承自 java.util.TimerTask;
* - 并且(and):重写了 run 方法;
* - 并且(and):run 方法中没有 try-catch。
*/
public class CheckTimerTask extends TimerTask {
    private static final Logger LOG = LogManager.getLogger(CheckTimerTask.class);

    // 应检查出的问题函数
    @Override
    public void run() {
      LOG.info("do some thing");
    }
}编写检查规则
https://bbs-img.huaweicloud.com/blogs/img/20240704/1720083517133433459.gif
DSL 写的检查规则
/**
* 检查问题:继承 java.util.TimerTask 类重写 run 方法,run 方法的实现要有 try-catch 保护。
* 问题检查条件:
* - 查找类继承自 java.util.TimerTask;
* - 并且(and):重写了 run 方法;
* - 并且(and):run 方法中没有 try-catch。
*/
functionDeclaration fd where
    and(
      fd.enclosingClass.superTypes contain parType where
            parType.name == "java.util.TimerTask",
      fd.name == "run",
      fd notContain exceptionBlock
    );4.2.2. 增长检查条件

基于上面一个例子,用户在利用时发现除了需要有异常捕捉之外,还需要记载错误或警告信息。由此需要对原来的规则举行修改,增长新的约束条件。
检查问题:

[*]继承 java.util.TimerTask 类重写 run 方法,run 方法的实现要有 try-catch 保护。
[*]在异常处理块中,需要有信息处理的函数: error 或 warn。
问题检查条件:

[*]查找类继承自 java.util.TimerTask;
[*]并且(and):重写了 run 方法;
[*]并且(and):run 方法中没有 try-catch。
[*]或者(or): run 方法中有异常处理块;

[*]并且(and): 异常处理块中有函数调用;
[*]并且(and):函数名为:error 或 warn。

问题代码样例
package com.dsl;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.TimerTask;

/**
* 检查问题:
* - 继承 java.util.TimerTask 类重写 run 方法,run 方法的实现要有 try-catch 保护。
* - 在异常处理块中,需要有信息处理的函数: error 或 warn。
* 问题检查条件:
* - 查找类继承自 java.util.TimerTask;
* - 并且(and):重写了 run 方法;
* - 并且(and):run 方法中没有 try-catch。
* - 或者(or): run 方法中有异常处理块;
*- 并且(and): 异常处理块中有函数调用;
*- 并且(and):函数名为:error 或 warn。
*/
public class CheckTimerTaskEnhance extends TimerTask {
    private static final Logger LOG = LogManager.getLogger(CheckTimerTaskEnhance.class);

    // 应检查出的问题函数
    @Override
    public void run() {
      try {
            LOG.info("do some thing");
      } catch (Exception e) {
            LOG.info("do some thing");
      }      
    }
}编写检查规则
https://bbs-img.huaweicloud.com/blogs/img/20240704/1720083550709420355.gif
DSL 写的检查规则
/**
* 检查问题:
* - 继承 java.util.TimerTask 类重写 run 方法,run 方法的实现要有 try-catch 保护。
* - 在异常处理块中,需要有信息处理的函数: error 或 warn。
* 问题检查条件:
* - 查找类继承自 java.util.TimerTask;
* - 并且(and):重写了 run 方法;
* - 并且(and):run 方法中没有 try-catch。
* - 或者(or): run 方法中有异常处理块;
*- 并且(and): 异常处理块中有函数调用;
*- 并且(and):函数名为:error 或 warn。
*/
functionDeclaration fd where
    and(
      fd.enclosingClass.superTypes contain parType where
            parType.name == "java.util.TimerTask",
      fd.name == "run",
      or(
            fd notContain exceptionBlock,
            fd contain exceptionBlock eb where
                eb contain functionCall fc where
                  fc.name notMatch "error|warn"
      )
    );结论

[*]通过这个案例,我们可以看到DSL 可以快速的替换现有工具已有的检查;
[*]并可以根据需求增长更多的检查条件,以增长对特殊场景的覆盖,从而减低规则的漏报率,同时也可以通过这个方式,降低工具的误报率,提升规则的检查的准确率。
5. 结论

通过上面两个案例,我们可以看到这个编写自界说规则的 DSL 语言,可以或许:

[*]实现规则的编写;
[*]实现规则的改进,降低误报率和漏报率;
[*]降低了检查规则的开发难度。
欢迎各人试用这个插件,并给出反馈意见。

[*]在 vscode 插件中查询:codenavi 添加插件即可。
点击关注,第一时间了解华为云希奇技术~
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 寻找适合编写静态分析规则的语言