1.1.1【必须】切片长度校验
- 在对slice进行操作时,必须判断长度是否合法,防止程序panic
1.1.2【必须】nil指针判断
- 进行指针操作时,必须判断该指针是否为nil,防止程序panic,尤其在进行结构体Unmarshal时
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)
// Unmarshal(b[i:], p.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)
}
1.1.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)
}
1.1.4【必须】make分配长度验证
- 在进行make分配内存时,需要对外部可控的长度进行校验,防止程序panic。
1.1.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()
}
}
1.1.6【必须】禁止重复释放channel
- 重复释放一般存在于异常流程判断中,如果恶意攻击者构造出异常条件使程序重复释放channel,则会触发运行时恐慌,从而造成DoS攻击。
// bad
func foo(c chan int) {
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
}
1.1.7【必须】确保每个协程都能退出
- 启动一个协程就会做一个入栈操作,在系统不退出的情况下,协程也没有设置退出条件,则相当于协程失去了控制,它占用的资源无法回收,可能会导致内存泄露。
1.1.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]
1.1.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)
}