1.1 内存管理

1.1.1【必须】切片长度校验

  • 在对slice进行操作时,必须判断长度是否合法,防止程序panic
  1. // bad: 未判断data的长度,可导致 index out of range
  2. func decode(data [] byte) bool {
  3. if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' {
  4. fmt.Println("Bad")
  5. return true
  6. }
  7. return false
  8. }
  9. // bad: slice bounds out of range
  10. func foo() {
  11. var slice = []int{0, 1, 2, 3, 4, 5, 6}
  12. fmt.Println(slice[:10])
  13. }
  14. // good: 使用data前应判断长度是否合法
  15. func decode(data [] byte) bool {
  16. if len(data) == 6 {
  17. if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' {
  18. fmt.Println("Good")
  19. return true
  20. }
  21. }
  22. return false
  23. }

1.1.2【必须】nil指针判断

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

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. // bad
  2. func parse(lenControlByUser int, data[] byte) {
  3. size := lenControlByUser
  4. //对外部传入的size,进行长度判断以免导致panic
  5. buffer := make([]byte, size)
  6. copy(buffer, data)
  7. }
  8. // good
  9. func parse(lenControlByUser int, data[] byte) ([]byte, error){
  10. size := lenControlByUser
  11. //限制外部可控的长度大小范围
  12. if size > 64*1024*1024 {
  13. return nil, errors.New("value too large")
  14. }
  15. buffer := make([]byte, size)
  16. copy(buffer, data)
  17. return buffer, nil
  18. }

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. time.Sleep(time.Millisecond)
  18. }
  19. }

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

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

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

  • 启动一个协程就会做一个入栈操作,在系统不退出的情况下,协程也没有设置退出条件,则相当于协程失去了控制,它占用的资源无法回收,可能会导致内存泄露。
  1. // bad: 协程没有设置退出条件
  2. func doWaiter(name string, second int) {
  3. for {
  4. time.Sleep(time.Duration(second) * time.Second)
  5. fmt.Println(name, " is ready!")
  6. }
  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. }