mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
426deb60fd
The problem is that it checkes circular dependencies and one then adds the target lock to the graph. Reported-by: syzbot+7687e27a2029723c7fb4@syzkaller.appspotmail.com PiperOrigin-RevId: 512637945
192 lines
5.9 KiB
Go
192 lines
5.9 KiB
Go
// Copyright 2022 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build lockdep
|
|
// +build lockdep
|
|
|
|
package locking
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"gvisor.dev/gvisor/pkg/goid"
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
)
|
|
|
|
// NewMutexClass allocates a new mutex class.
|
|
func NewMutexClass(t reflect.Type, lockNames []string) *MutexClass {
|
|
c := &MutexClass{
|
|
typ: t,
|
|
nestedLockNames: lockNames,
|
|
nestedLockClasses: make([]*MutexClass, len(lockNames)),
|
|
}
|
|
for i := range lockNames {
|
|
c.nestedLockClasses[i] = NewMutexClass(t, nil)
|
|
c.nestedLockClasses[i].lockName = lockNames[i]
|
|
}
|
|
return c
|
|
}
|
|
|
|
// MutexClass describes dependencies of a specific class.
|
|
type MutexClass struct {
|
|
// The type of the mutex.
|
|
typ reflect.Type
|
|
|
|
// Name of the nested lock of the above type.
|
|
lockName string
|
|
|
|
// ancestors are locks that are locked before the current class.
|
|
ancestors ancestorsAtomicPtrMap
|
|
// nestedLockNames is a list of names for nested locks which are considered difference instances
|
|
// of the same lock class.
|
|
nestedLockNames []string
|
|
// namedLockClasses is a list of MutexClass instances of the same mutex class, but that are
|
|
// considered OK to lock simultaneously with each other, as well as with this mutex class.
|
|
// This is used for nested locking, where multiple instances of the same lock class are used
|
|
// simultaneously.
|
|
// Maps one-to-one with nestedLockNames.
|
|
nestedLockClasses []*MutexClass
|
|
}
|
|
|
|
func (m *MutexClass) String() string {
|
|
if m.lockName == "" {
|
|
return m.typ.String()
|
|
}
|
|
return fmt.Sprintf("%s[%s]", m.typ.String(), m.lockName)
|
|
}
|
|
|
|
type goroutineLocks map[*MutexClass]bool
|
|
|
|
var routineLocks goroutineLocksAtomicPtrMap
|
|
|
|
// maxChainLen is the maximum length of a lock chain.
|
|
const maxChainLen = 32
|
|
|
|
// checkLock checks that class isn't in the ancestors of prevClass.
|
|
func checkLock(class *MutexClass, prevClass *MutexClass, chain []*MutexClass) {
|
|
chain = append(chain, prevClass)
|
|
if len(chain) >= maxChainLen {
|
|
// It can be a race condition with another thread that added
|
|
// the lock to the graph but don't complete the validation.
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "WARNING: The maximum lock depth has been reached: %s", chain[0])
|
|
for i := 1; i < len(chain); i++ {
|
|
fmt.Fprintf(&b, "-> %s", chain[i])
|
|
}
|
|
log.Warningf("%s", b.String())
|
|
return
|
|
}
|
|
if c := prevClass.ancestors.Load(class); c != nil {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "WARNING: circular locking detected: %s -> %s:\n%s\n",
|
|
chain[0], class, log.LocalStack(3))
|
|
|
|
fmt.Fprintf(&b, "known lock chain: ")
|
|
c := class
|
|
for i := len(chain) - 1; i >= 0; i-- {
|
|
fmt.Fprintf(&b, "%s -> ", c)
|
|
c = chain[i]
|
|
}
|
|
fmt.Fprintf(&b, "%s\n", chain[0])
|
|
c = class
|
|
for i := len(chain) - 1; i >= 0; i-- {
|
|
fmt.Fprintf(&b, "\n====== %s -> %s =====\n%s",
|
|
c, chain[i], *chain[i].ancestors.Load(c))
|
|
c = chain[i]
|
|
}
|
|
panic(b.String())
|
|
}
|
|
prevClass.ancestors.RangeRepeatable(func(parentClass *MutexClass, stacks *string) bool {
|
|
// The recursion is fine here. If it fails, you need to reduce
|
|
// a number of nested locks.
|
|
checkLock(class, parentClass, chain)
|
|
return true
|
|
})
|
|
}
|
|
|
|
// AddGLock records a lock to the current goroutine and updates dependencies.
|
|
func AddGLock(class *MutexClass, lockNameIndex int) {
|
|
gid := goid.Get()
|
|
|
|
if lockNameIndex != -1 {
|
|
class = class.nestedLockClasses[lockNameIndex]
|
|
}
|
|
currentLocks := routineLocks.Load(gid)
|
|
if currentLocks == nil {
|
|
locks := goroutineLocks(make(map[*MutexClass]bool))
|
|
locks[class] = true
|
|
routineLocks.Store(gid, &locks)
|
|
return
|
|
}
|
|
|
|
if (*currentLocks)[class] {
|
|
panic(fmt.Sprintf("nested locking: %s:\n%s", class, log.LocalStack(2)))
|
|
}
|
|
(*currentLocks)[class] = true
|
|
// Check dependencies and add locked mutexes to the ancestors list.
|
|
for prevClass := range *currentLocks {
|
|
if prevClass == class {
|
|
continue
|
|
}
|
|
checkLock(class, prevClass, nil)
|
|
|
|
if c := class.ancestors.Load(prevClass); c == nil {
|
|
stacks := string(log.LocalStack(2))
|
|
class.ancestors.Store(prevClass, &stacks)
|
|
}
|
|
}
|
|
}
|
|
|
|
// DelGLock deletes a lock from the current goroutine.
|
|
func DelGLock(class *MutexClass, lockNameIndex int) {
|
|
if lockNameIndex != -1 {
|
|
class = class.nestedLockClasses[lockNameIndex]
|
|
}
|
|
gid := goid.Get()
|
|
currentLocks := routineLocks.Load(gid)
|
|
if currentLocks == nil {
|
|
panic("the current goroutine doesn't have locks")
|
|
}
|
|
if _, ok := (*currentLocks)[class]; !ok {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "Lock not held: %s:\n", class)
|
|
fmt.Fprintf(&b, "Current stack:\n%s\n", string(log.LocalStack(2)))
|
|
fmt.Fprintf(&b, "Current locks:\n")
|
|
for c := range *currentLocks {
|
|
heldToClass := class.ancestors.Load(c)
|
|
classToHeld := c.ancestors.Load(class)
|
|
if heldToClass == nil && classToHeld == nil {
|
|
fmt.Fprintf(&b, "\t- Holding lock: %s (no dependency to/from %s found)\n", c, class)
|
|
} else if heldToClass != nil && classToHeld != nil {
|
|
fmt.Fprintf(&b, "\t- Holding lock: %s (mutual dependency with %s found, this should never happen)\n", c, class)
|
|
} else if heldToClass != nil && classToHeld == nil {
|
|
fmt.Fprintf(&b, "\t- Holding lock: %s (dependency: %s -> %s)\n", c, c, class)
|
|
fmt.Fprintf(&b, "%s\n\n", *heldToClass)
|
|
} else if heldToClass == nil && classToHeld != nil {
|
|
fmt.Fprintf(&b, "\t- Holding lock: %s (dependency: %s -> %s)\n", c, class, c)
|
|
fmt.Fprintf(&b, "%s\n\n", *classToHeld)
|
|
}
|
|
}
|
|
fmt.Fprintf(&b, "** End of locks held **\n")
|
|
panic(b.String())
|
|
}
|
|
|
|
delete(*currentLocks, class)
|
|
if len(*currentLocks) == 0 {
|
|
routineLocks.Store(gid, nil)
|
|
}
|
|
}
|