在go语言中安全问题之内存管理场景示例
Go  /  管理员 发布于 1年前   285
go语言安全问题之内存管理场景
场景1:切片长度校验
在对 slice 进行操作时,必须判断长度是否合法,防止程序 panic
// bad: 未判断data的长度,可导致 index out of rangefunc decode(data []byte) bool {
if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' { fmt.Println("Bad") return true } return false}
// bad: slice bounds out of range
func foo() {
var slice = []int{0, 1, 2, 3, 4, 5, 6} fmt.Println(slice[:10])}
// good: 使用data前应判断长度是否合法
func decode(data []byte) bool {
if len(data) == 6 { if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' { fmt.Println("Good") return true } } return false}
场景2:nil 指针判断
进行指针操作时,必须判断该指针是否为 nil,防止程序 panic,尤其在进行结构体 Unmarshal 时
type Packet struct {
PackeyType uint8 PackeyVersion uint8 Data *Data}
type Data struct {
Stat uint8 Len uint8 Buf [8]byte}
func (p *Packet) UnmarshalBinary(b []byte) error {
if len(b) < 2 { return io.EOF }
p.PackeyType = b[0] p.PackeyVersion = b[1]
// 若长度等于2,那么不会new Data
if len(b) > 2 { p.Data = new(Data) } return nil}
// bad: 未判断指针是否为nil
func main() {
packet := new(Packet) data := make([]byte, 2) if err := packet.UnmarshalBinary(data); err != nil { fmt.Println("Failed to unmarshal packet") return }
fmt.Printf("Stat: %v\n", packet.Data.Stat)}
// good: 判断Data指针是否为nil
func main() {
packet := new(Packet) data := make([]byte, 2)
if err := packet.UnmarshalBinary(data); err != nil { fmt.Println("Failed to unmarshal packet") return }
if packet.Data == nil { return }
fmt.Printf("Stat: %v\n", packet.Data.Stat)}
场景3:整数安全
在进行数字运算操作时,需要做好长度限制,防止外部输入运算导致异常:
确保无符号整数运算时不会反转
确保有符号整数运算时不会出现溢出
确保整型转换时不会出现截断错误
确保整型转换时不会出现符号错误
以下场景必须严格进行长度限制:
作为数组索引
作为对象的长度或者大小
作为数组的边界(如作为循环计数器)
// bad: 未限制长度,导致整数溢出
func overflow(numControlByUser int32) {
var numInt int32 = 0 numInt = numControlByUser + 1 // 对长度限制不当,导致整数溢出
fmt.Printf("%d\n", numInt) // 使用numInt,可能导致其他错误
}
func main() {
overflow(2147483647)}
// good
func overflow(numControlByUser int32) {
var numInt int32 = 0 numInt = numControlByUser + 1 if numInt < 0 { fmt.Println("integer overflow") return } fmt.Println("integer ok")}
func main() {
overflow(2147483647)}
场景4:make 分配长度验证
在进行 make 分配内存时,需要对外部可控的长度进行校验,防止程序 panic。
// bad
func parse(lenControlByUser int, data []byte) {
size := lenControlByUser // 对外部传入的size,进行长度判断以免导致panic
buffer := make([]byte, size) copy(buffer, data)}
// good
func parse(lenControlByUser int, data []byte) ([]byte, error) {
size := lenControlByUser // 限制外部可控的长度大小范围
if size > 64*1024*1024 { return nil, errors.New("value too large") } buffer := make([]byte, size) copy(buffer, data) return buffer, nil}
场景5:禁止 SetFinalizer 和指针循环引用同时使用
当一个对象从被 GC 选中到移除内存之前,runtime.SetFinalizer () 都不会执行,即使程序正常结束或者发生错误。由指针构成的 “循环引用” 虽然能被 GC 正确处理,但由于无法确定 Finalizer 依赖顺序,从而无法调用 runtime.SetFinalizer (),导致目标对象无法变成可达状态,从而造成内存无法被回收。
// bad
func foo() {
var a, b Data a.o = &b b.o = &a
// 指针循环引用,SetFinalizer()无法正常调用
runtime.SetFinalizer(&a, func(d *Data) { fmt.Printf("a %p final.\n", d) }) runtime.SetFinalizer(&b, func(d *Data) { fmt.Printf("b %p final.\n", d) })}
func main() {
for { foo() time.Sleep(time.Millisecond) }}
场景6:禁止重复释放 channel
重复释放一般存在于异常流程判断中,如果恶意攻击者构造出异常条件使程序重复释放 channel,则会触发运行时 panic,从而造成 DoS 攻击。
// bad
func foo(c chan int) {
defer close(c) err := processBusiness() if err != nil { c <- 0 close(c) // 重复释放channel
return } c <- 1}
// good
func foo(c chan int) {
defer close(c) // 使用defer延迟关闭channel
err := processBusiness() if err != nil { c <- 0 return } c <- 1}
场景7:确保每个协程都能退出
启动一个协程就会做一个入栈操作,在系统不退出的情况下,协程也没有设置退出条件,则相当于协程失去了控制,它占用的资源无法回收,可能会导致内存泄露。
// bad: 协程没有设置退出条件
func doWaiter(name string, second int) {
for { time.Sleep(time.Duration(second) * time.Second) fmt.Println(name, " is ready!") }}
场景8:不使用 unsafe 包
由于 unsafe 包绕过了 Golang 的内存安全原则,一般来说使用该库是不安全的,可导致内存破坏,尽量避免使用该包。若必须要使用 unsafe 操作指针,必须做好安全校验。
// bad: 通过unsafe操作原始指针
func unsafePointer() {
b := make([]byte, 1) foo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(0xfffffffe))) fmt.Print(*foo + 1)}
// [signal SIGSEGV: segmentation violation code=0x1 addr=0xc100068f55 pc=0x49142b]
场景9:不使用 slice 作为函数入参
slice 在作为函数入参时,函数内对 slice 的修改可能会影响原始数据
// bad // slice作为函数入参时包含原始数组指针
func modify(array []int) { array[0] = 10 // 对入参slice的元素修改会影响原始数据
} func main() {
array := []int{1, 2, 3, 4, 5} modify(array)
fmt.Println(array) // output:[10 2 3 4 5]
}
// good // 数组作为函数入参,而不是slice
func modify(array [5]int) { array[0] = 10 }
func main() { // 传入数组,注意数组与slice的区别
array := [5]int{1, 2, 3, 4, 5} modify(array)
fmt.Println(array) }
122 在
学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..123 在
Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..原梓番博客 在
在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..博主 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..1111 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
Copyright·© 2019 侯体宗版权所有·
粤ICP备20027696号