一、导读
文章主要从四个方面说明:
- 产品代码漏洞检查的背景和方法
- 代码漏洞的搜索深挖技术
- 提高产品质量的方法
- 总结与展望
二、产品代码漏洞检查的背景和方法
1. why-为什么要检查产品代码的漏洞
一般情况下,产品质量的问题多数与程序代码相关。比如银行软件出现漏洞,导致十几个客户信用卡被盗刷。2003年阿丽亚娜5型火箭升空爆炸造成5亿美元的损失。由于电控系统的软件问题导致大面积停电事故,给交通,通信,居民生活造成严重影响等等,都是和产品代码相关。代码的漏洞检查与分析可以帮助用户从根源上减少70%-80%的产品崩溃和安全性问题。只有代码中的崩溃和安全缺陷得以及时消除,最终形成的产品才能具备较高的质量,有效降低整个产品风险。
2. when-什么时候检查产品代码的漏洞
在产品开发测试发布过程中,流程越往后,漏洞造成的影响越大。漏洞发现的越早,修复成本越低。
如下图中红色的曲线,横坐标是产品发布流程,纵坐标是修复缺陷成本,可以看出,在测试阶段,修复成本比较低,在产品发布之后,修复成本是成指数增长的。
所以在产品测试阶段,最好把产品代码中的漏洞都检查出来。那怎么对产品代码的漏洞进行检查。
3. how-检查产品代码漏洞的方法
现阶段一般有2种方法,而且这2种方法我们已经实现了。
一种是源代码的漏洞扫描与检查,主要方法是对编码规范的检查,常见的编码规范有4类,分别是错误类,安全类,禁用类和建议类,具体内容详见下图。自定义代码规范的制定与实时更新,根据具体业务场景的代码规范的制定等等方法都能很好的检查出产品代码的漏洞。
另外一种是对二进制文件的漏洞扫描与检查,比如google提供的veridex工具,可以扫描非法API调用,该工具将非法API分了3类。
4. 深度挖掘产品代码漏洞的方法
通过上面介绍的2种方法,只能对特定代码或二进制进行检查,但是对产品,乃至整个公司的代码仓库,隐藏的bug却是惊人的。
由此,在上述2种基础的方法上,我们引入了另外一个技术,代码漏洞的搜索深挖技术,简而言之,即代码搜索。
此外,经过调研发现,国外也有类似的研究,NASA,microsoft等机构已经利用代码搜索技术,发现了多个零日漏洞。
三、代码漏洞的搜索深挖技术
1. 代码搜索的问题和挑战
主要是6个困难点,如下图所示。
- 代码特征的确定
- 搜索速度慢
- 代码信息量太少,不好定位漏洞。
- 代码入库非常缓慢
- 过滤条件不好兼容
- 数据量大,搜索数据量高达千万级代码文件。
针对这些问题我们做了一序列的优化和改进。
2. 代码搜索的技术架构
主要是5部分,如下图所示。
- python后台部分用于增量更新数据源信息和实时更新索引。
- 正排数据源,主要采用mysql数据库,包括表结构的设计,索引和分表设计等。
- Sphinx实时分布式索引,用于提供索引创建服务和搜索索引服务等。
- Php+nginx服务端部分,为前端提供接口服务。
- 前端部分,用于展示搜索结果和后台管理等。
3. 代码搜索的服务端
代码搜索的服务端为前端或其他系统提供API接口,一共有6大模块,包括搜索模型,登录模型,校验模型,用户模块,日志模块,代码review模块。数据库为上述6大模块提供数据支持。
4. 代码搜索的后台
大致分为3层:最下面一层数据来源层,支持svn和git的代码仓库,来源包括qbuild系统和授权系统,获取代码日志,增量下载文件,最后存储在数据库中。索引层主要是从数据源中获取文档信息,然后经过分词模型,倒排索引算法,将索引存储在文件系统中。
服务层主要是sphinx索引工具提供的索引服务,通过排序,获取索引文档信息后,从正排数据库中拿到文档全部信息,返回结果数据。
5. 数据源增量入库方案
代码搜索的困难点之一是数据源入库非常慢,针对这个问题,我们有如下的优化方案,数据源的增量入库方案。
主要有8个步骤:
- 分别是从qbuild或授权系统获取代码地址
- 获取当前代码地址的提交日期
- 根据提交日期获取代码提交日志
- 通过解析日志,获取增量文件列表,然后每个文件进行下面的处理,先进行去重判断
- 然后下载该文件,再进行去重判断
- 存储在数据源中
- 经过分词工具
- 最终实时存储索引
这个过程比较长,但是分解到每一步,却比较容易实现,比如获取代码提交日志和代码文件下载,svn对应的命令可以参考如下。
- svn log -r {0} --xml -v "{1}" --username "{2}" --password "{3}" --non-interactive --no-auth-cache --trust-server-cert> {4}
- svn export -r {0} "{1}" "{2}" --force --username {3} --password "{4}" --non-interactive --no-auth-cache --trust-server-cert
在数据源增量入库方案中,有一个很大的问题需要解决,就是重复的问题。可以看一下,对于svn有路径包含重复的问题,下面那个路径是包含上面那个路径的,上面那个路径将会被入库2次。
- http://svn.example.com/svn/testxxx/111/222/333
- http://svn.example.com/svn/testxxx/111
Git也有相似的问题,分支重复,不同分支代码会有大量重复提交的记录。
- http://git.example.com/root/11 分支:master
- http://git.example.com/root/11 分支:v1.1
我们的去重方法是,针对svn,利用模块id+revision的方式,对于svn,同一个模块id下的revison是递增的,不会有重复问题。相应的,git是通过仓库id+提交sha1值去重的,对于同一个仓库,提交的sha1值是唯一的。
6. 实时分布式索引技术
代码搜索系统遇到的另外一个困难是搜索太慢,为此我们引入了sphinx索引工具,为什么选择sphinx索引工具呢。该工具支持高达数十亿个文档,数TB的数据和每秒数千个查询。支持各种数据源,包括xml,sql,python等。支持结果的各种过滤聚合功能,快速高效的索引,应用场合广泛,比如维基百科,优酷土豆,github等。下图是今年的索引工具的排行榜,可以看到sphinx排在第5,受众范围广。
(1) sphinx工具使用
Sphinx主要包括3个可用的工具:
分别是index实时索引工具,主要是对数据源的数据进行倒排索引,并存储,使用命令如下,sphinx.conf是sphinx的配置文件。
- eg: /usr/local/sphinx/bin/indexer -c sphinx.conf code
Searchd搜索服务工具,php可以通过sphinx扩展,访问该服务,使用命令如下。
- eg: /usr/local/sphinx/bin/searchd -c sphinx.conf &
Search搜索工具,客户端搜索工具,可以用该工具测试索引的正确性,一般只是测试使用。
- eg: /usr/local/sphinx/bin/search -c sphinx.conf mykeyword
可以看到这3个命令都用到了sphinx的配置文件,那么这个文件怎么配置。
(2) sphinx实时分布式的配置详情
一般情况下,最初会采用主索引和增量索引的方式,但是随着数据的增加,服务和运维都有压力,通过优化,我们最终采用实时分布式的方式。实时索引的好处有,代码索引无延时,没有额外的定时程序更新和合并索引服务,降低运维成本,提高搜索精确性和可靠性。分布式的好处有,资源利用率提高,搜索效率提高,搜索并发性提高等
实时分布式的配置如下:
- 第1个实时索引的配置,type是rt,也就是realtime,path表示该索引存储的位置,下面几行是字段的定义,rt_field就是需要索引的字段,rt_attr_uint和rt_attr_timestamp是索引字段的属性,一个是int类型,一个是时间戳类型。
- 第2个配置是分布式配置,type是distributed,下面几行是分布式位置。
- 第3个配置是索引服务配置,9312接口是提供索引服务的,9306是接收实时索引服务的,下面2行是日志位置。
- index coderealtime
- {
- type = rt
- path = user/local/sphinx/indexer/files/coderealtime
- rt_field = content
- rt_field = filename
- rt_attr_uint = rpid
- rt_attr_timestamp = cdate
- }
- index codedistributed
- {
- type = distributed
- local = coderealtime
- agent = localhost:9312:crt1
- agent = localhost:9312:crt2
- }
- searchd
- {
- listen = 9312
- listen = 9306:mysql41
- log = /user/local/sphinx/indexer/logs/searchd.log
- query_log = /user/local/sphinx/indexer/logs/query.log
- }
(3) 代码搜索排序方法
代码搜索最重要的一个指标就是排序方法,本方案,主要从3个方面对代码结果进行排序,分别是词组评分,代码提交时间,和BM25算法。这3个指标中最重要的是BM25算法,下面简单的介绍该算法的实现方法,公式如下:
Score(Q,d)是衡量某次query查询和文档的相关性计算公式,d表示当前文档,Q是query中所有的关键字集合,qi是其中的某个关键词,n是Q的长度,Wi是这个词的权重,R(q,d)是这个词和文档的权重。Wi默认是IDF值,N表示所有文档数,n(qi)表示包含该关键词的文档数,0.5是避免n(qi)为0的情况。大致的意思是关键词在所有文档中出现频率越多说明越普遍,就越不重要,权重越低。R(q,d)是这个词和文档的权重,大致的意思是某个关键词在该篇文档出现的次数越多,说明越重要。
Wi突出的全局的权重,R(q,d)表示的局部权重。举个通俗的例子,在图书查找过程中,比如[作者]这个词,几乎在所有书中都会出现,所以[作者]这个词的权重很低,[人工智能]这个词不常见,如果某个图书中经常提到人工智能这个词,大概率这本书在讲人工智能。BM25算法通过统计的方法,就能对代码进行合理的排序。
四、提高产品质量的方法
如何利用代码搜索技术提高产品质量,主要是2种方法:
- 第1种方法是结合业务督促开发修复代码漏洞,一方面根据前面介绍的检查产品代码漏洞的2种方法,根据这些检查出来的漏洞进行深度搜索,将产品和公司代码库中隐藏的漏洞都修复了,去除产品隐患,另一方面结合业务,比如某个函数实现有漏洞,可以根据函数名进行搜索,查看函数调用的模块,避免代码漏洞的扩散。
- 第2种方法是对产品代码的敏感词的检查,比如代码审计系统的敏感词和禁用api的检查,文件签名系统的敏感签名信息的检查等。
下面这个图是代码搜索的一个demo,主要有3部分构成,最上面是搜索输入,中间是过滤条件,包括时间,代码语言,归属人,代码仓库。最下面是搜索的结果,主要包括文件名,仓库名,文件位置,版本号,提交日期和归属人,测试人员可以根据仓库和归属人信息找到对应的开发负责人,进而督促修复漏洞。
五、总结与展望
本文主要从3个部分阐述了如何从代码层提高产品质量:
- 第一部分是产品代码漏洞检查的背景和方法,主要讲了检查产品代码漏洞的2种方法,即源代码漏洞扫描与检查、二进制文件漏洞扫描与检查,但是这2种方法只能对特定项目的代码进行检查,隐藏的bug量是巨大的,从而引出第二部分,代码漏洞的搜索深挖技术。
- 第二部分是本文的重点,展开讲了代码搜索的技术方案及实现细节
- 第三部分从2个方面说明了如何利用代码搜索技术提高产品质量。
代码搜索系统能够快速定位问题,通过对细节的不断探索,搜索速度显著提升,搜索排序质量提高了,本系统辅助优化了产品代码质量。接下来,我们将从2方面进一步优化,分别是代码推荐结合代码语义上下文和AI的方法,进一步提升代码推荐的精确度,以及函数式的代码推荐。
【本文是51CTO专栏机构360技术的原创文章,微信公众号“360技术( id: qihoo_tech)”】