trailofbits.go.missing-unlock-before-return.missing-unlock-before-return

Author
unknown
Download Count*
License
Missing mutex unlock before returning from a function. This could result in panics resulting from double lock operations
Run Locally
Run in CI
Defintion
rules:
- id: missing-unlock-before-return
message: Missing mutex unlock before returning from a function. This could
result in panics resulting from double lock operations
languages:
- go
severity: ERROR
metadata:
category: security
cwe: "CWE-667: Improper Locking"
subcategory:
- vuln
confidence: MEDIUM
likelihood: HIGH
impact: MEDIUM
technology:
- --no-technology--
description: Missing `mutex` unlock before returning from a function
references:
- https://pkg.go.dev/sync#Mutex
- https://blog.trailofbits.com/2020/06/09/how-to-check-if-a-mutex-is-locked-in-go/
license: CC-BY-NC-SA-4.0
patterns:
- pattern-either:
- pattern: panic(...)
- pattern: return ...
- pattern-inside: |
$T.Lock()
...
- pattern-not-inside: |
$T.Unlock()
...
- pattern-not-inside: |
defer $T.Unlock()
...
- pattern-not-inside: |
$FOO(..., ..., func(...) {
...
})
Examples
missing-unlock-before-return.go
// Modified from https://gobyexample.com/mutexes
package main
import (
"fmt"
"sync"
)
type Container struct {
mu sync.Mutex
counters map[string]int
}
func main() {
c := Container{
counters: map[string]int{"a": 0, "b": 0},
}
var wg sync.WaitGroup
doIncrement := func(name string, n int) {
for i := 0; i < n; i++ {
err := c.inc(name)
if err != nil {
continue
}
}
wg.Done()
}
wg.Add(3)
go doIncrement("a", 10000)
go doIncrement("a", 10000)
go doIncrement("b", 10000)
go doIncrement("c", 10000)
go doIncrement("a", 10000)
go doIncrement("c", 10000)
go doIncrement("a", 10000)
go doIncrement("b", 10000)
wg.Wait()
fmt.Println(c.counters)
}
func (c *Container) inc(name string) error {
c.mu.Lock()
c.counters[name]++
if name == "c" {
// ruleid: missing-unlock-before-return
return fmt.Errorf("letter not allowed")
}
c.mu.Unlock()
return nil
}
func (c *Container) inc_FP(name string) error {
c.mu.Lock()
c.counters[name]++
if name == "c" {
// ok: missing-unlock-before-return
c.mu.Unlock()
return fmt.Errorf("letter not allowed")
}
c.mu.Unlock()
return nil
}
// This could still cause a deadlock in the case that the caller function
// implements a recover() to avoid the application from crashing
func (c *Container) inc2(name string) error {
c.mu.Lock()
c.counters[name]++
if name == "c" {
// ruleid: missing-unlock-before-return
panic("letter not allowed")
}
c.mu.Unlock()
return nil
}
func (c *Container) inc2_FP(name string) error {
c.mu.Lock()
c.counters[name]++
if name == "c" {
c.mu.Unlock()
// ok: missing-unlock-before-return
panic("letter not allowed")
}
c.mu.Unlock()
return nil
}
func (c *Container) inc3(name string) error {
c.mu.Lock()
c.counters[name]++
if name == "c" {
c.mu.Unlock()
// ok: missing-unlock-before-return
return fmt.Errorf("letter not allowed")
}
// ruleid: missing-unlock-before-return
return nil
}
Short Link: https://sg.run/18Bk