freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Golang Hyperscan高性能安全检测场景下过坑指南
2023-02-25 21:34:52
所属地 北京

一、背景

Hyperscan是intel开源的一款高性能正则引擎,很适合检测数据流中的恶意特征。使用golang调用hyperscan功能时,需要引用gohs库,它封装好了cgo接口并屏蔽了golang调用hyperscan C库的复杂性。然而当处理大流量数据时,老版本gohs库会偶发panic引起程序崩溃,新版本gohs库虽然解决了panic问题,但存在严重的性能损失,无法使用于大流量高性能场景,因此对gohs源码进行了深入分析,最终不影响hyperscan高性能的前提下解决了偶发panic问题。

二、性能分析

gohs库会偶发panic问题的版本是1.0.0,新版本1.2.1解决了panic问题,但升级之后,导致安全检测系统处理性能骤降,无法应用到线上环境。

升级后处理QPS变化图gohs库升级之后,某集群处理QPS从10w 降低到5w,减少50%

升级后CPU变化图cpu使用率从40%~50%降低到20%~30%,数据基本无法在规定时间内完成安全检测。
分析后发现,gohs库在调用C libhs.so库的scan函数时,会将Go指针传给cgo函数,但传递给C函数的go指针指向的内容包含指向其它go地址的指针,简单来说就是go指针指向了其它go指针,这种情况会引发go panic,这个问题是cgo的一个已知问题,详细信息可以参考https://pkg.go.dev/cmd/cgo#hdr-Passing_pointers
官方给出两种方案,第一种方式是使用go源码中提供的runtime/cgo.Handle方法, gohs1.2.1使用的便是这种方法,源码如下:

官方源码官方采用了sync.map 来存储需要传递给C模块的go指针,并用一个自增uintptr类型全局变量handleIdx 来索引go指针,这个方法核心是使用了sync.map,问题也就出在这,sync.map适用的场景是读多写少的场景,但hyperscan 的scan正则检测是高频调用,读多写多的场景,gohs1.2.1库会频繁向sync.map中写入 key:handleIdx, value:包含go指针的结构体 ,使用一次后立即删除,这会导致频繁向sync.map的dirty表中插入数据,且没办法利用sync.map的缓存特性,此时相当于在全局加了一把互斥锁;从sync.map获取 go指针数据会从dirty表中读取,当读取次数达到dirty数据长度时,即存入次数=读取次数时,触发dirty表迁移到read表,又会增加额外的开销,导致性能比全局互斥锁还要低,因此golang 使用hyperscan做流量安全检测性能下降严重,无法应用于线上大流量场景。
gohs1.2.1性能瓶颈代码位于internal/hs/runtime.go,如下所示:

四、解决方案

对于调用so动态库中的函数,cgo官方给了另一种方法:手动关闭指针检测,即设置GODEBUG=cgocheck=0环境变量,指定运行时不做go指针检测,允许传递给C模块的指针指向的内容中包含指向其它go空间的指针,但这可能会导致go的GC机制将还未被使用的go指针提前释放掉,因此需要调用runtime包下的keepalive函数防止GC机制回收go指针
gohs1.2.1源码修改如下:

五、结果

采用如上解决方案定制修改gohs1.2.1版本库之后,hyperscan正则检测偶发panic问题得到彻底解决,同时性能没有受到影响,上线安全系统稳定运行。

因为没有锁的影响,CPU没有明显降低,保持在40%~50%左右

处理的QPS恢复正常,安全检测系统处理总QPS达到80w+,一切运行正常,符合预期。

# 网络安全 # web安全 # Golang # 高性能 # 恶意流量检测
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录