github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/platform/ptrace/ptrace.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 ptrace provides a ptrace-based implementation of the platform
    16  // interface. This is useful for development and testing purposes primarily,
    17  // and runs on stock kernels without special permissions.
    18  //
    19  // In a nutshell, it works as follows:
    20  //
    21  // The creation of a new address space creates a new child process with a single
    22  // thread which is traced by a single goroutine.
    23  //
    24  // A context is just a collection of temporary variables. Calling Switch on a
    25  // context does the following:
    26  //
    27  //	Locks the runtime thread.
    28  //
    29  //	Looks up a traced subprocess thread for the current runtime thread. If
    30  //	none exists, the dedicated goroutine is asked to create a new stopped
    31  //	thread in the subprocess. This stopped subprocess thread is then traced
    32  //	by the current thread and this information is stored for subsequent
    33  //	switches.
    34  //
    35  //	The context is then bound with information about the subprocess thread
    36  //	so that the context may be appropriately interrupted via a signal.
    37  //
    38  //	The requested operation is performed in the traced subprocess thread
    39  //	(e.g. set registers, execute, return).
    40  //
    41  // Lock order:
    42  //
    43  //	 subprocess.mu
    44  //		context.mu
    45  package ptrace
    46  
    47  import (
    48  	"os"
    49  
    50  	"github.com/nicocha30/gvisor-ligolo/pkg/abi/linux"
    51  	pkgcontext "github.com/nicocha30/gvisor-ligolo/pkg/context"
    52  	"github.com/nicocha30/gvisor-ligolo/pkg/hostarch"
    53  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch"
    54  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/platform"
    55  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/platform/interrupt"
    56  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    57  )
    58  
    59  var (
    60  	// stubStart is the link address for our stub, and determines the
    61  	// maximum user address. This is valid only after a call to stubInit.
    62  	//
    63  	// We attempt to link the stub here, and adjust downward as needed.
    64  	stubStart uintptr = stubInitAddress
    65  
    66  	// stubEnd is the first byte past the end of the stub, as with
    67  	// stubStart this is valid only after a call to stubInit.
    68  	stubEnd uintptr
    69  
    70  	// stubInitialized controls one-time stub initialization.
    71  	stubInitialized sync.Once
    72  )
    73  
    74  type context struct {
    75  	archContext
    76  
    77  	// signalInfo is the signal info, if and when a signal is received.
    78  	signalInfo linux.SignalInfo
    79  
    80  	// interrupt is the interrupt context.
    81  	interrupt interrupt.Forwarder
    82  
    83  	// mu protects the following fields.
    84  	mu sync.Mutex
    85  
    86  	// If lastFaultSP is non-nil, the last context switch was due to a fault
    87  	// received while executing lastFaultSP. Only context.Switch may set
    88  	// lastFaultSP to a non-nil value.
    89  	lastFaultSP *subprocess
    90  
    91  	// lastFaultAddr is the last faulting address; this is only meaningful if
    92  	// lastFaultSP is non-nil.
    93  	lastFaultAddr hostarch.Addr
    94  
    95  	// lastFaultIP is the address of the last faulting instruction;
    96  	// this is also only meaningful if lastFaultSP is non-nil.
    97  	lastFaultIP hostarch.Addr
    98  }
    99  
   100  // NewContext implements platform.Platform.NewContext.
   101  func (*PTrace) NewContext(ctx pkgcontext.Context) platform.Context {
   102  	c := new(context)
   103  	c.archContext.init(ctx)
   104  	return c
   105  }
   106  
   107  // Switch runs the provided context in the given address space.
   108  func (c *context) Switch(ctx pkgcontext.Context, mm platform.MemoryManager, ac *arch.Context64, cpu int32) (*linux.SignalInfo, hostarch.AccessType, error) {
   109  	as := mm.AddressSpace()
   110  	s := as.(*subprocess)
   111  restart:
   112  	isSyscall := s.switchToApp(c, ac)
   113  
   114  	var (
   115  		faultSP   *subprocess
   116  		faultAddr hostarch.Addr
   117  		faultIP   hostarch.Addr
   118  	)
   119  	if !isSyscall && linux.Signal(c.signalInfo.Signo) == linux.SIGSEGV {
   120  		faultSP = s
   121  		faultAddr = hostarch.Addr(c.signalInfo.Addr())
   122  		faultIP = hostarch.Addr(ac.IP())
   123  	}
   124  
   125  	// Update the context to reflect the outcome of this context switch.
   126  	c.mu.Lock()
   127  	lastFaultSP := c.lastFaultSP
   128  	lastFaultAddr := c.lastFaultAddr
   129  	lastFaultIP := c.lastFaultIP
   130  	// At this point, c may not yet be in s.contexts, so c.lastFaultSP won't be
   131  	// updated by s.Unmap(). This is fine; we only need to synchronize with
   132  	// calls to s.Unmap() that occur after the handling of this fault.
   133  	c.lastFaultSP = faultSP
   134  	c.lastFaultAddr = faultAddr
   135  	c.lastFaultIP = faultIP
   136  	c.mu.Unlock()
   137  
   138  	// Update subprocesses to reflect the outcome of this context switch.
   139  	if lastFaultSP != faultSP {
   140  		if lastFaultSP != nil {
   141  			lastFaultSP.mu.Lock()
   142  			delete(lastFaultSP.contexts, c)
   143  			lastFaultSP.mu.Unlock()
   144  		}
   145  		if faultSP != nil {
   146  			faultSP.mu.Lock()
   147  			faultSP.contexts[c] = struct{}{}
   148  			faultSP.mu.Unlock()
   149  		}
   150  	}
   151  
   152  	if isSyscall {
   153  		return nil, hostarch.NoAccess, nil
   154  	}
   155  
   156  	si := c.signalInfo
   157  	if faultSP == nil {
   158  		// Non-fault signal.
   159  		return &si, hostarch.NoAccess, platform.ErrContextSignal
   160  	}
   161  
   162  	// See if this can be handled as a CPUID instruction.
   163  	if linux.Signal(si.Signo) == linux.SIGSEGV && platform.TryCPUIDEmulate(ctx, mm, ac) {
   164  		goto restart
   165  	}
   166  
   167  	// Got a page fault. Ideally, we'd get real fault type here, but ptrace
   168  	// doesn't expose this information. Instead, we use a simple heuristic:
   169  	//
   170  	// It was an instruction fault iff the faulting addr == instruction
   171  	// pointer.
   172  	//
   173  	// It was a write fault if the fault is immediately repeated.
   174  	at := hostarch.Read
   175  	if faultAddr == faultIP {
   176  		at.Execute = true
   177  	}
   178  	if lastFaultSP == faultSP &&
   179  		lastFaultAddr == faultAddr &&
   180  		lastFaultIP == faultIP {
   181  		at.Write = true
   182  	}
   183  
   184  	// Handle as a signal.
   185  	return &si, at, platform.ErrContextSignal
   186  }
   187  
   188  // Interrupt interrupts the running guest application associated with this context.
   189  func (c *context) Interrupt() {
   190  	c.interrupt.NotifyInterrupt()
   191  }
   192  
   193  // Release implements platform.Context.Release().
   194  func (c *context) Release() {}
   195  
   196  // FullStateChanged implements platform.Context.FullStateChanged.
   197  func (c *context) FullStateChanged() {}
   198  
   199  // PullFullState implements platform.Context.PullFullState.
   200  func (c *context) PullFullState(as platform.AddressSpace, ac *arch.Context64) error { return nil }
   201  
   202  // PrepareSleep implements platform.Context.platform.PrepareSleep.
   203  func (*context) PrepareSleep() {}
   204  
   205  // PTrace represents a collection of ptrace subprocesses.
   206  type PTrace struct {
   207  	platform.MMapMinAddr
   208  	platform.NoCPUPreemptionDetection
   209  	platform.UseHostGlobalMemoryBarrier
   210  	platform.DoesNotOwnPageTables
   211  }
   212  
   213  // New returns a new ptrace-based implementation of the platform interface.
   214  func New() (*PTrace, error) {
   215  	stubInitialized.Do(func() {
   216  		// Initialize the stub.
   217  		stubInit()
   218  
   219  		// Create the master process for the global pool. This must be
   220  		// done before initializing any other processes.
   221  		master, err := newSubprocess(createStub)
   222  		if err != nil {
   223  			// Should never happen.
   224  			panic("unable to initialize ptrace master: " + err.Error())
   225  		}
   226  
   227  		// Set the master on the globalPool.
   228  		globalPool.master = master
   229  	})
   230  
   231  	return &PTrace{}, nil
   232  }
   233  
   234  // SupportsAddressSpaceIO implements platform.Platform.SupportsAddressSpaceIO.
   235  func (*PTrace) SupportsAddressSpaceIO() bool {
   236  	return false
   237  }
   238  
   239  // CooperativelySchedulesAddressSpace implements platform.Platform.CooperativelySchedulesAddressSpace.
   240  func (*PTrace) CooperativelySchedulesAddressSpace() bool {
   241  	return false
   242  }
   243  
   244  // MapUnit implements platform.Platform.MapUnit.
   245  func (*PTrace) MapUnit() uint64 {
   246  	// The host kernel manages page tables and arbitrary-sized mappings
   247  	// have effectively the same cost.
   248  	return 0
   249  }
   250  
   251  // MaxUserAddress returns the first address that may not be used by user
   252  // applications.
   253  func (*PTrace) MaxUserAddress() hostarch.Addr {
   254  	return hostarch.Addr(stubStart)
   255  }
   256  
   257  // NewAddressSpace returns a new subprocess.
   258  func (p *PTrace) NewAddressSpace(any) (platform.AddressSpace, <-chan struct{}, error) {
   259  	as, err := newSubprocess(globalPool.master.createStub)
   260  	return as, nil, err
   261  }
   262  
   263  type constructor struct{}
   264  
   265  func (*constructor) New(*os.File) (platform.Platform, error) {
   266  	return New()
   267  }
   268  
   269  func (*constructor) OpenDevice(_ string) (*os.File, error) {
   270  	return nil, nil
   271  }
   272  
   273  // Flags implements platform.Constructor.Flags().
   274  func (*constructor) Requirements() platform.Requirements {
   275  	// TODO(b/75837838): Also set a new PID namespace so that we limit
   276  	// access to other host processes.
   277  	return platform.Requirements{
   278  		RequiresCapSysPtrace: true,
   279  		RequiresCurrentPIDNS: true,
   280  	}
   281  }
   282  
   283  func init() {
   284  	platform.Register("ptrace", &constructor{})
   285  }