1.1.1【必须】切片长度校验

    • 在对slice进行操作时,必须判断长度是否合法,防止程序panic

    1.1.2【必须】nil指针判断

    • 进行指针操作时,必须判断该指针是否为nil,防止程序panic,尤其在进行结构体Unmarshal时
    1. PackeyType uint8
    2. PackeyVersion uint8
    3. Data *Data
    4. }
    5. type Data struct {
    6. Stat uint8
    7. Len uint8
    8. Buf [8]byte
    9. }
    10. func (p *Packet) UnmarshalBinary(b []byte) error {
    11. if len(b) < 2 {
    12. return io.EOF
    13. }
    14. p.PackeyType = b[0]
    15. p.PackeyVersion = b[1]
    16. // 若长度等于2,那么不会new Data
    17. if len(b) > 2 {
    18. p.Data = new(Data)
    19. // Unmarshal(b[i:], p.Data)
    20. }
    21. return nil
    22. }
    23. // bad: 未判断指针是否为nil
    24. func main() {
    25. packet := new(Packet)
    26. data := make([]byte, 2)
    27. if err := packet.UnmarshalBinary(data); err != nil {
    28. fmt.Println("Failed to unmarshal packet")
    29. return
    30. }
    31. fmt.Printf("Stat: %v\n", packet.Data.Stat)
    32. }
    33. // good: 判断Data指针是否未nil
    34. func main() {
    35. packet := new(Packet)
    36. data := make([]byte, 2)
    37. if err := packet.UnmarshalBinary(data); err != nil {
    38. fmt.Println("Failed to unmarshal packet")
    39. return
    40. }
    41. if packet.Data == nil {
    42. return
    43. fmt.Printf("Stat: %v\n", packet.Data.Stat)
    44. }

    1.1.3【必须】整数安全

      • 确保无符号整数运算时不会反转
      • 确保有符号整数运算时不会出现溢出
      • 确保整型转换时不会出现截断错误
      • 确保整型转换时不会出现符号错误
      • 作为数组索引
      • 作为对象的长度或者大小
      • 作为数组的边界(如作为循环计数器)
    1. // bad: 未限制长度,导致整数溢出
    2. func overflow(numControlByUser int32) {
    3. var numInt int32 = 0
    4. numInt = numControlByUser + 1
    5. //对长度限制不当,导致整数溢出
    6. fmt.Printf("%d\n", numInt)
    7. //使用numInt,可能导致其他错误
    8. }
    9. func main() {
    10. overflow(2147483647)
    11. }
    12. // good:
    13. func overflow(numControlByUser int32) {
    14. var numInt int32 = 0
    15. numInt = numControlByUser + 1
    16. if numInt < 0 {
    17. fmt.Println("integer overflow")
    18. return;
    19. }
    20. fmt.Println("integer ok")
    21. }
    22. func main() {
    23. overflow(2147483647)
    24. }

    1.1.4【必须】make分配长度验证

    • 在进行make分配内存时,需要对外部可控的长度进行校验,防止程序panic。

    1.1.5【必须】禁止SetFinalizer和指针循环引用同时使用

    • 当一个对象从被GC选中到移除内存之前,runtime.SetFinalizer()都不会执行,即使程序正常结束或者发生错误。由指针构成的“循环引用”虽然能被GC正确处理,但由于无法确定Finalizer依赖顺序,从而无法调用runtime.SetFinalizer(),导致目标对象无法变成可达状态,从而造成内存无法被回收。
    1. // bad
    2. func foo() {
    3. var a, b Data
    4. a.o = &b
    5. b.o = &a
    6. //指针循环引用,SetFinalizer()无法正常调用
    7. runtime.SetFinalizer(&a, func(d *Data) {
    8. fmt.Printf("a %p final.\n", d)
    9. })
    10. runtime.SetFinalizer(&b, func(d *Data) {
    11. fmt.Printf("b %p final.\n", d)
    12. })
    13. }
    14. func main() {
    15. for {
    16. foo()
    17. }
    18. }

    1.1.6【必须】禁止重复释放channel

    • 重复释放一般存在于异常流程判断中,如果恶意攻击者构造出异常条件使程序重复释放channel,则会触发运行时恐慌,从而造成DoS攻击。
    1. // bad
    2. func foo(c chan int) {
    3. err := processBusiness()
    4. if err != nil {
    5. c <- 0
    6. close(c) // 重复释放channel
    7. return
    8. }
    9. c <- 1
    10. }
    11. // good
    12. func foo(c chan int) {
    13. defer close(c) // 使用defer延迟关闭channel
    14. err := processBusiness()
    15. if err != nil {
    16. c <- 0
    17. return
    18. }
    19. c <- 1
    20. }

    1.1.7【必须】确保每个协程都能退出

    • 启动一个协程就会做一个入栈操作,在系统不退出的情况下,协程也没有设置退出条件,则相当于协程失去了控制,它占用的资源无法回收,可能会导致内存泄露。

    1.1.8【推荐】不使用unsafe包

    • 由于unsafe包绕过了 Golang 的内存安全原则,一般来说使用该库是不安全的,可导致内存破坏,尽量避免使用该包。若必须要使用unsafe操作指针,必须做好安全校验。
    1. // bad: 通过unsafe操作原始指针
    2. func unsafePointer() {
    3. b := make([]byte, 1)
    4. foo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(0xfffffffe)))
    5. fmt.Print(*foo + 1)
    6. }
    7. // [signal SIGSEGV: segmentation violation code=0x1 addr=0xc100068f55 pc=0x49142b]

    1.1.9【推荐】不使用slice作为函数入参

    • slice是引用类型,在作为函数入参时采用的是地址传递,对slice的修改也会影响原始数据
    1. // bad
    2. // slice作为函数入参时是地址传递
    3. func modify(array []int) {
    4. array[0] = 10 // 对入参slice的元素修改会影响原始数据
    5. }
    6. func main() {
    7. array := []int{1, 2, 3, 4, 5}
    8. modify(array)
    9. fmt.Println(array) // output:[10 2 3 4 5]
    10. }
    11. // good
    12. // 数组作为函数入参时,而不是slice
    13. func modify(array [5]int) {
    14. array[0] = 10
    15. }
    16. func main() {
    17. // 传入数组,注意数组与slice的区别
    18. array := [5]int{1, 2, 3, 4, 5}
    19. modify(array)
    20. fmt.Println(array)
    21. }