trailofbits.go.hanging-goroutine.hanging-goroutine

Author
232
Download Count*
License
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: CC-BY-NC-SA-4.0
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"
}
Short Link: https://sg.run/Dw8o