github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/kernel/task_stop.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  // This file implements task stops, which represent the equivalent of Linux's
    18  // uninterruptible sleep states in a way that is compatible with save/restore.
    19  // Task stops comprise both internal stops (which form part of the task's
    20  // "normal" control flow) and external stops (which do not); see README.md for
    21  // details.
    22  //
    23  // There are multiple interfaces for interacting with stops because there are
    24  // multiple cases to consider:
    25  //
    26  //  - A task goroutine can begin a stop on its associated task (e.g. a
    27  //		vfork() syscall stopping the calling task until the child task releases its
    28  //		MM). In this case, calling Task.interrupt is both unnecessary (the task
    29  //		goroutine obviously cannot be blocked in Task.block or executing application
    30  //		code) and undesirable (as it may spuriously interrupt a in-progress
    31  //		syscall).
    32  //
    33  // Beginning internal stops in this case is implemented by
    34  // Task.beginInternalStop / Task.beginInternalStopLocked. As of this writing,
    35  // there are no instances of this case that begin external stops, except for
    36  // autosave; however, autosave terminates the sentry without ending the
    37  // external stop, so the spurious interrupt is moot.
    38  //
    39  //	- An arbitrary goroutine can begin a stop on an unrelated task (e.g. all
    40  //		tasks being stopped in preparation for state checkpointing). If the task
    41  //		goroutine may be in Task.block or executing application code, it must be
    42  //		interrupted by Task.interrupt for it to actually enter the stop; since,
    43  //		strictly speaking, we have no way of determining this, we call
    44  //		Task.interrupt unconditionally.
    45  //
    46  // Beginning external stops in this case is implemented by
    47  // Task.BeginExternalStop. As of this writing, there are no instances of this
    48  // case that begin internal stops.
    49  //
    50  //	- An arbitrary goroutine can end a stop on an unrelated task (e.g. an
    51  //		exiting task resuming a sibling task that has been blocked in an execve()
    52  //		syscall waiting for other tasks to exit). In this case, Task.endStopCond
    53  //		must be notified to kick the task goroutine out of Task.doStop.
    54  //
    55  // Ending internal stops in this case is implemented by
    56  // Task.endInternalStopLocked. Ending external stops in this case is
    57  // implemented by Task.EndExternalStop.
    58  //
    59  //	- Hypothetically, a task goroutine can end an internal stop on its
    60  //		associated task. As of this writing, there are no instances of this case.
    61  //		However, any instances of this case could still use the above functions,
    62  //		since notifying Task.endStopCond would be unnecessary but harmless.
    63  
    64  import (
    65  	"fmt"
    66  )
    67  
    68  // A TaskStop is a condition visible to the task control flow graph that
    69  // prevents a task goroutine from running or exiting, i.e. an internal stop.
    70  //
    71  // NOTE(b/30793614): Most TaskStops don't contain any data; they're
    72  // distinguished by their type. The obvious way to implement such a TaskStop
    73  // is:
    74  //
    75  //	type groupStop struct{}
    76  //	func (groupStop) Killable() bool { return true }
    77  //	...
    78  //	t.beginInternalStop(groupStop{})
    79  //
    80  // However, this doesn't work because the state package can't serialize values,
    81  // only pointers. Furthermore, the correctness of save/restore depends on the
    82  // ability to pass a TaskStop to endInternalStop that will compare equal to the
    83  // TaskStop that was passed to beginInternalStop, even if a save/restore cycle
    84  // occurred between the two. As a result, the current idiom is to always use a
    85  // typecast nil for data-free TaskStops:
    86  //
    87  //	type groupStop struct{}
    88  //	func (*groupStop) Killable() bool { return true }
    89  //	...
    90  //	t.beginInternalStop((*groupStop)(nil))
    91  //
    92  // This is pretty gross, but the alternatives seem grosser.
    93  type TaskStop interface {
    94  	// Killable returns true if Task.Kill should end the stop prematurely.
    95  	// Killable is analogous to Linux's TASK_WAKEKILL.
    96  	Killable() bool
    97  }
    98  
    99  // beginInternalStop indicates the start of an internal stop that applies to t.
   100  //
   101  // Preconditions:
   102  //   - The caller must be running on the task goroutine.
   103  //   - The task must not already be in an internal stop (i.e. t.stop == nil).
   104  func (t *Task) beginInternalStop(s TaskStop) {
   105  	t.tg.pidns.owner.mu.RLock()
   106  	defer t.tg.pidns.owner.mu.RUnlock()
   107  	t.tg.signalHandlers.mu.Lock()
   108  	defer t.tg.signalHandlers.mu.Unlock()
   109  	t.beginInternalStopLocked(s)
   110  }
   111  
   112  // Preconditions: Same as beginInternalStop, plus:
   113  //   - The signal mutex must be locked.
   114  func (t *Task) beginInternalStopLocked(s TaskStop) {
   115  	if t.stop != nil {
   116  		panic(fmt.Sprintf("Attempting to enter internal stop %#v when already in internal stop %#v", s, t.stop))
   117  	}
   118  	t.Debugf("Entering internal stop %#v", s)
   119  	t.stop = s
   120  	t.beginStopLocked()
   121  }
   122  
   123  // endInternalStopLocked indicates the end of an internal stop that applies to
   124  // t. endInternalStopLocked does not wait for the task to resume.
   125  //
   126  // The caller is responsible for ensuring that the internal stop they expect
   127  // actually applies to t; this requires holding the signal mutex which protects
   128  // t.stop, which is why there is no endInternalStop that locks the signal mutex
   129  // for you.
   130  //
   131  // Preconditions:
   132  //   - The signal mutex must be locked.
   133  //   - The task must be in an internal stop (i.e. t.stop != nil).
   134  func (t *Task) endInternalStopLocked() {
   135  	if t.stop == nil {
   136  		panic("Attempting to leave non-existent internal stop")
   137  	}
   138  	t.Debugf("Leaving internal stop %#v", t.stop)
   139  	t.stop = nil
   140  	t.endStopLocked()
   141  }
   142  
   143  // BeginExternalStop indicates the start of an external stop that applies to t.
   144  // BeginExternalStop does not wait for t's task goroutine to stop.
   145  func (t *Task) BeginExternalStop() {
   146  	t.tg.pidns.owner.mu.RLock()
   147  	defer t.tg.pidns.owner.mu.RUnlock()
   148  	t.tg.signalHandlers.mu.Lock()
   149  	defer t.tg.signalHandlers.mu.Unlock()
   150  	t.beginStopLocked()
   151  	t.interrupt()
   152  }
   153  
   154  // EndExternalStop indicates the end of an external stop started by a previous
   155  // call to Task.BeginExternalStop. EndExternalStop does not wait for t's task
   156  // goroutine to resume.
   157  func (t *Task) EndExternalStop() {
   158  	t.tg.pidns.owner.mu.RLock()
   159  	defer t.tg.pidns.owner.mu.RUnlock()
   160  	t.tg.signalHandlers.mu.Lock()
   161  	defer t.tg.signalHandlers.mu.Unlock()
   162  	t.endStopLocked()
   163  }
   164  
   165  // beginStopLocked increments t.stopCount to indicate that a new internal or
   166  // external stop applies to t.
   167  //
   168  // Preconditions: The signal mutex must be locked.
   169  func (t *Task) beginStopLocked() {
   170  	if newval := t.stopCount.Add(1); newval <= 0 {
   171  		// Most likely overflow.
   172  		panic(fmt.Sprintf("Invalid stopCount: %d", newval))
   173  	}
   174  }
   175  
   176  // endStopLocked decrements t.stopCount to indicate that an existing internal
   177  // or external stop no longer applies to t.
   178  //
   179  // Preconditions: The signal mutex must be locked.
   180  func (t *Task) endStopLocked() {
   181  	if newval := t.stopCount.Add(-1); newval < 0 {
   182  		panic(fmt.Sprintf("Invalid stopCount: %d", newval))
   183  	} else if newval == 0 {
   184  		t.endStopCond.Signal()
   185  	}
   186  }
   187  
   188  // BeginExternalStop indicates the start of an external stop that applies to
   189  // all current and future tasks in ts. BeginExternalStop does not wait for
   190  // task goroutines to stop.
   191  func (ts *TaskSet) BeginExternalStop() {
   192  	ts.mu.Lock()
   193  	defer ts.mu.Unlock()
   194  	ts.stopCount++
   195  	if ts.stopCount <= 0 {
   196  		panic(fmt.Sprintf("Invalid stopCount: %d", ts.stopCount))
   197  	}
   198  	if ts.Root == nil {
   199  		return
   200  	}
   201  	for t := range ts.Root.tids {
   202  		t.tg.signalHandlers.mu.Lock()
   203  		t.beginStopLocked()
   204  		t.tg.signalHandlers.mu.Unlock()
   205  		t.interrupt()
   206  	}
   207  }
   208  
   209  // PullFullState receives full states for all tasks.
   210  func (ts *TaskSet) PullFullState() {
   211  	ts.mu.Lock()
   212  	defer ts.mu.Unlock()
   213  	if ts.Root == nil {
   214  		return
   215  	}
   216  	for t := range ts.Root.tids {
   217  		t.Activate()
   218  		if mm := t.MemoryManager(); mm != nil {
   219  			t.p.PullFullState(t.MemoryManager().AddressSpace(), t.Arch())
   220  		}
   221  		t.Deactivate()
   222  	}
   223  }
   224  
   225  // EndExternalStop indicates the end of an external stop started by a previous
   226  // call to TaskSet.BeginExternalStop. EndExternalStop does not wait for task
   227  // goroutines to resume.
   228  func (ts *TaskSet) EndExternalStop() {
   229  	ts.mu.Lock()
   230  	defer ts.mu.Unlock()
   231  	ts.stopCount--
   232  	if ts.stopCount < 0 {
   233  		panic(fmt.Sprintf("Invalid stopCount: %d", ts.stopCount))
   234  	}
   235  	if ts.Root == nil {
   236  		return
   237  	}
   238  	for t := range ts.Root.tids {
   239  		t.tg.signalHandlers.mu.Lock()
   240  		t.endStopLocked()
   241  		t.tg.signalHandlers.mu.Unlock()
   242  	}
   243  }