trailofbits.go.hanging-goroutine.hanging-goroutine

profile photo of trailofbitstrailofbits
Author
232
Download Count*

Potential goroutine leak due to unbuffered channel send inside loop or unbuffered channel receive in select block

Run Locally

Run in CI

Defintion

rules:
  - id: hanging-goroutine
    message: Potential goroutine leak due to unbuffered channel send inside loop or
      unbuffered channel receive in select block
    languages:
      - go
    severity: ERROR
    metadata:
      category: security
      cwe: "CWE-833: Deadlock"
      subcategory:
        - vuln
      confidence: MEDIUM
      likelihood: MEDIUM
      impact: LOW
      technology:
        - --no-technology--
      description: Goroutine leaks
      references:
        - https://blog.trailofbits.com/2021/11/08/discovering-goroutine-leaks-with-semgrep
      license: AGPL-3.0 license
      vulnerability_class:
        - Other
    patterns:
      - pattern-either:
          - pattern: |
              for ... {
                ...
                go func(...) {
                  ...
                  $CHANNEL <- $VAL
                  ...
                }(...)
              }
              ...
              $Y = <- $CHANNEL
              ...
          - pattern: |
              for ... {
                ...
                go func(...) {
                  ...
                  $CHANNEL <- $VAL
                  ...
                }(...)
              }
              ...
              $Y := <- $CHANNEL
              ...
          - pattern: |
              for ... {
                ...
                go func(...) {
                  ...
                  $CHANNEL <- $VAL
                  ...
                }(...)
              }
              ...
              return <- $CHANNEL
          - pattern: |
              for ... {
                ...
                go func(...) {
                  ...
                  select {
                    case ...
                    case $CHANNEL <- $VAL: ...
                    case ...
                  }
                  ...
                }(...)
              ...
              }
              ...
              return <- $CHANNEL
          - pattern: |
              go func(...){
                ...
                $CHANNEL <- $X
                ...
              }(...)
              ...
              select {
              case ...
              case $Y = <- $CHANNEL: 
              ...
              }
          - pattern: |
              go func(...){
                ...
                $CHANNEL <- $X
                ...
              }(...)
              ...
              select {
              case ...
              case $Y := <- $CHANNEL: 
              ...
              }
          - pattern: |
              go func(...){
                ...
                $CHANNEL <- $X
                ...
              }(...)
              ...
              select {
              case ...
              case <- $CHANNEL: 
              ...
              }
          - pattern: |
              go func(...){
                ...
                $CHANNEL <- $X
                ...
              }(...)
              ...
              select {
              case ...
              case $Y <- $CHANNEL: 
              ...
              }
      - pattern-not: |
          for ... {
            ...
            go func(...) {
              ...
              $CHANNEL <- $VAL
              ...
            }(...)
          }
          ...
          $Y = <- $CHANNEL
          ...
      - pattern-inside: |
          $CHANNEL := make(...)
          ...
      - pattern-not-inside: |
          ...
          select {
          case ...
          case ...: 
            ...
            ... =<- $CHANNEL
            ...
          }
      - pattern-not-inside: |
          ...
          select {
          case ...
          case ...: 
            ...
            <-$CHANNEL
            ...
          }
      - pattern-not-inside: |
          $CHANNEL := make(..., $T)
          ...

Examples

hanging-goroutine.go

package main

import (
	"fmt"
	"runtime"
	"time"
)

var (
	result = ""
)

func main() {
	req1(1)
	time.Sleep(time.Second * 5)
	fmt.Println(result)
	fmt.Println(runtime.NumGoroutine())
}

func req1(timeout time.Duration) string {
	ch := make(chan string)
	// ruleid: hanging-goroutine
	go func() {
		newData := test()
		ch <- newData // block
	}()
	select {
	case result = <- ch:
		fmt.Println("case result")
		return result
	case <- time.After(timeout):
		fmt.Println("case time.Afer")
		return ""
	}
}

func req1_FP(timeout time.Duration) string {
	ch := make(chan string, 1)
	// ok: hanging-goroutine
	go func() {
		newData := test()
		ch <- newData // block
	}()
	select {
	case result = <- ch:
		fmt.Println("case result")
		return result
	case <- time.After(timeout):
		fmt.Println("case time.Afer")
		return ""
	}
}

func req2(timeout time.Duration) string {
	ch := make(chan string)
	// ruleid: hanging-goroutine
	go func() {
		newData := test()
		ch <- newData // block
	}()
	select {
	case <- ch:
		fmt.Println("case result")
		return result
	case <- time.After(timeout):
		fmt.Println("case time.Afer")
		return ""
	}
}

func req2_FP(timeout time.Duration) string {
	ch := make(chan string, 1)
	// ok: hanging-goroutine
	go func() {
		newData := test()
		ch <- newData // block
	}()
	select {
	case <- ch:
		fmt.Println("case result")
		return result
	case <- time.After(timeout):
		fmt.Println("case time.Afer")
		return ""
	}
}

func test() string {
	time.Sleep(time.Second * 2)
	return "very important data"
}