trailofbits.go.missing-runlock-on-rwmutex.missing-runlock-on-rwmutex

profile photo of trailofbitstrailofbits
Author
unknown
Download Count*

Missing RUnlock on an RWMutex lock before returning from a function

Run Locally

Run in CI

Defintion

rules:
  - id: missing-runlock-on-rwmutex
    message: Missing `RUnlock` on an `RWMutex` lock before returning from a function
    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 `RUnlock` on an `RWMutex` lock before returning from a function
      references:
        - https://pkg.go.dev/sync#RWMutex
        - 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.RLock()
          ...
      - pattern-not-inside: |
          $T.RUnlock()
          ...
      - pattern-not-inside: |
          defer $T.RUnlock()
          ...
      - pattern-not-inside: |
          $FOO(..., ..., func(...) { 
              ... 
          })

Examples

missing-runlock-on-rwmutex.go

// Modified from https://gobyexample.com/mutexes

package main

import (
	"fmt"
	"sync"
)

type RWContainer struct {
	mu       sync.RWMutex
	counters map[string]int
}

func main() {
	c := RWContainer{
		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 *RWContainer) inc(name string) error {
	c.mu.RLock()
	c.counters[name]++
	if name == "c" {
		// ruleid: missing-runlock-on-rwmutex
		return fmt.Errorf("letter not allowed")
	}
	c.mu.RUnlock()
	return nil
}

func (c *RWContainer) inc2(name string) error {
	c.mu.RLock()
	c.counters[name]++
	if name == "c" {
		// ruleid: missing-runlock-on-rwmutex
		return fmt.Errorf("letter not allowed")
	}
	// ruleid: missing-runlock-on-rwmutex
	return nil
}

func (c *RWContainer) inc_FP(name string) error {
	c.mu.RLock()
	c.counters[name]++
	if name == "c" {
		// ok: missing-runlock-on-rwmutex
		c.mu.RUnlock()
		return fmt.Errorf("letter not allowed")
	}
	c.mu.RUnlock()
	return nil
}