github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/kernel/task_futex.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kernel
    16  
    17  import (
    18  	"github.com/ttpreport/gvisor-ligolo/pkg/abi/linux"
    19  	"github.com/ttpreport/gvisor-ligolo/pkg/hostarch"
    20  	"github.com/ttpreport/gvisor-ligolo/pkg/marshal/primitive"
    21  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel/futex"
    22  	"github.com/ttpreport/gvisor-ligolo/pkg/usermem"
    23  )
    24  
    25  // Futex returns t's futex manager.
    26  //
    27  // Preconditions: The caller must be running on the task goroutine, or t.mu
    28  // must be locked.
    29  func (t *Task) Futex() *futex.Manager {
    30  	return t.image.fu
    31  }
    32  
    33  // SwapUint32 implements futex.Target.SwapUint32.
    34  func (t *Task) SwapUint32(addr hostarch.Addr, new uint32) (uint32, error) {
    35  	return t.MemoryManager().SwapUint32(t, addr, new, usermem.IOOpts{
    36  		AddressSpaceActive: true,
    37  	})
    38  }
    39  
    40  // CompareAndSwapUint32 implements futex.Target.CompareAndSwapUint32.
    41  func (t *Task) CompareAndSwapUint32(addr hostarch.Addr, old, new uint32) (uint32, error) {
    42  	return t.MemoryManager().CompareAndSwapUint32(t, addr, old, new, usermem.IOOpts{
    43  		AddressSpaceActive: true,
    44  	})
    45  }
    46  
    47  // LoadUint32 implements futex.Target.LoadUint32.
    48  func (t *Task) LoadUint32(addr hostarch.Addr) (uint32, error) {
    49  	return t.MemoryManager().LoadUint32(t, addr, usermem.IOOpts{
    50  		AddressSpaceActive: true,
    51  	})
    52  }
    53  
    54  // GetSharedKey implements futex.Target.GetSharedKey.
    55  func (t *Task) GetSharedKey(addr hostarch.Addr) (futex.Key, error) {
    56  	return t.MemoryManager().GetSharedFutexKey(t, addr)
    57  }
    58  
    59  // GetRobustList sets the robust futex list for the task.
    60  func (t *Task) GetRobustList() hostarch.Addr {
    61  	t.mu.Lock()
    62  	addr := t.robustList
    63  	t.mu.Unlock()
    64  	return addr
    65  }
    66  
    67  // SetRobustList sets the robust futex list for the task.
    68  func (t *Task) SetRobustList(addr hostarch.Addr) {
    69  	t.mu.Lock()
    70  	t.robustList = addr
    71  	t.mu.Unlock()
    72  }
    73  
    74  // exitRobustList walks the robust futex list, marking locks dead and notifying
    75  // wakers. It corresponds to Linux's exit_robust_list(). Following Linux,
    76  // errors are silently ignored.
    77  func (t *Task) exitRobustList() {
    78  	t.mu.Lock()
    79  	addr := t.robustList
    80  	t.robustList = 0
    81  	t.mu.Unlock()
    82  
    83  	if addr == 0 {
    84  		return
    85  	}
    86  
    87  	var rl linux.RobustListHead
    88  	if _, err := rl.CopyIn(t, hostarch.Addr(addr)); err != nil {
    89  		return
    90  	}
    91  
    92  	next := primitive.Uint64(rl.List)
    93  	done := 0
    94  	var pendingLockAddr hostarch.Addr
    95  	if rl.ListOpPending != 0 {
    96  		pendingLockAddr = hostarch.Addr(rl.ListOpPending + rl.FutexOffset)
    97  	}
    98  
    99  	// Wake up normal elements.
   100  	for hostarch.Addr(next) != addr {
   101  		// We traverse to the next element of the list before we
   102  		// actually wake anything. This prevents the race where waking
   103  		// this futex causes a modification of the list.
   104  		thisLockAddr := hostarch.Addr(uint64(next) + rl.FutexOffset)
   105  
   106  		// Try to decode the next element in the list before waking the
   107  		// current futex. But don't check the error until after we've
   108  		// woken the current futex. Linux does it in this order too
   109  		_, nextErr := next.CopyIn(t, hostarch.Addr(next))
   110  
   111  		// Wakeup the current futex if it's not pending.
   112  		if thisLockAddr != pendingLockAddr {
   113  			t.wakeRobustListOne(thisLockAddr)
   114  		}
   115  
   116  		// If there was an error copying the next futex, we must bail.
   117  		if nextErr != nil {
   118  			break
   119  		}
   120  
   121  		// This is a user structure, so it could be a massive list, or
   122  		// even contain a loop if they are trying to mess with us. We
   123  		// cap traversal to prevent that.
   124  		done++
   125  		if done >= linux.ROBUST_LIST_LIMIT {
   126  			break
   127  		}
   128  	}
   129  
   130  	// Is there a pending entry to wake?
   131  	if pendingLockAddr != 0 {
   132  		t.wakeRobustListOne(pendingLockAddr)
   133  	}
   134  }
   135  
   136  // wakeRobustListOne wakes a single futex from the robust list.
   137  func (t *Task) wakeRobustListOne(addr hostarch.Addr) {
   138  	// Bit 0 in address signals PI futex.
   139  	pi := addr&1 == 1
   140  	addr = addr &^ 1
   141  
   142  	// Load the futex.
   143  	f, err := t.LoadUint32(addr)
   144  	if err != nil {
   145  		// Can't read this single value? Ignore the problem.
   146  		// We can wake the other futexes in the list.
   147  		return
   148  	}
   149  
   150  	tid := uint32(t.ThreadID())
   151  	for {
   152  		// Is this held by someone else?
   153  		if f&linux.FUTEX_TID_MASK != tid {
   154  			return
   155  		}
   156  
   157  		// This thread is dying and it's holding this futex. We need to
   158  		// set the owner died bit and wake up any waiters.
   159  		newF := (f & linux.FUTEX_WAITERS) | linux.FUTEX_OWNER_DIED
   160  		if curF, err := t.CompareAndSwapUint32(addr, f, newF); err != nil {
   161  			return
   162  		} else if curF != f {
   163  			// Futex changed out from under us. Try again...
   164  			f = curF
   165  			continue
   166  		}
   167  
   168  		// Wake waiters if there are any.
   169  		if f&linux.FUTEX_WAITERS != 0 {
   170  			private := f&linux.FUTEX_PRIVATE_FLAG != 0
   171  			if pi {
   172  				t.Futex().UnlockPI(t, addr, tid, private)
   173  				return
   174  			}
   175  			t.Futex().Wake(t, addr, private, linux.FUTEX_BITSET_MATCH_ANY, 1)
   176  		}
   177  
   178  		// Done.
   179  		return
   180  	}
   181  }