gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/mm/address_space.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 mm
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"gvisor.dev/gvisor/pkg/context"
    21  	"gvisor.dev/gvisor/pkg/hostarch"
    22  	"gvisor.dev/gvisor/pkg/sentry/memmap"
    23  	"gvisor.dev/gvisor/pkg/sentry/platform"
    24  )
    25  
    26  // AddressSpace returns the platform.AddressSpace bound to mm.
    27  //
    28  // Preconditions: The caller must have called mm.Activate().
    29  func (mm *MemoryManager) AddressSpace() platform.AddressSpace {
    30  	if mm.active.Load() == 0 {
    31  		panic("trying to use inactive address space?")
    32  	}
    33  	return mm.as
    34  }
    35  
    36  // Activate ensures this MemoryManager has a platform.AddressSpace.
    37  //
    38  // The caller must not hold any locks when calling Activate.
    39  //
    40  // When this MemoryManager is no longer needed by a task, it should call
    41  // Deactivate to release the reference.
    42  func (mm *MemoryManager) Activate(ctx context.Context) error {
    43  	// Fast path: the MemoryManager already has an active
    44  	// platform.AddressSpace, and we just need to indicate that we need it too.
    45  	for {
    46  		active := mm.active.Load()
    47  		if active == 0 {
    48  			// Fall back to the slow path.
    49  			break
    50  		}
    51  		if mm.active.CompareAndSwap(active, active+1) {
    52  			return nil
    53  		}
    54  	}
    55  
    56  	for {
    57  		// Slow path: may need to synchronize with other goroutines changing
    58  		// mm.active to or from zero.
    59  		mm.activeMu.Lock()
    60  		// Inline Unlock instead of using a defer for performance since this
    61  		// method is commonly in the hot-path.
    62  
    63  		// Check if we raced with another goroutine performing activation.
    64  		if mm.active.Load() > 0 {
    65  			// This can't race; Deactivate can't decrease mm.active from 1 to 0
    66  			// without holding activeMu.
    67  			mm.active.Add(1)
    68  			mm.activeMu.Unlock()
    69  			return nil
    70  		}
    71  
    72  		// Do we have a context? If so, then we never unmapped it. This can
    73  		// only be the case if !mm.p.CooperativelySchedulesAddressSpace().
    74  		if mm.as != nil {
    75  			mm.active.Store(1)
    76  			mm.activeMu.Unlock()
    77  			return nil
    78  		}
    79  
    80  		// Get a new address space. We must force unmapping by passing nil to
    81  		// NewAddressSpace if requested. (As in the nil interface object, not a
    82  		// typed nil.)
    83  		mappingsID := (any)(mm)
    84  		if mm.unmapAllOnActivate {
    85  			mappingsID = nil
    86  		}
    87  		as, c, err := mm.p.NewAddressSpace(mappingsID)
    88  		if err != nil {
    89  			mm.activeMu.Unlock()
    90  			return err
    91  		}
    92  		if as == nil {
    93  			// AddressSpace is unavailable, we must wait.
    94  			//
    95  			// activeMu must not be held while waiting, as the user of the address
    96  			// space we are waiting on may attempt to take activeMu.
    97  			mm.activeMu.Unlock()
    98  
    99  			sleep := mm.p.CooperativelySchedulesAddressSpace() && mm.sleepForActivation
   100  			if sleep {
   101  				// Mark this task sleeping while waiting for the address space to
   102  				// prevent the watchdog from reporting it as a stuck task.
   103  				ctx.UninterruptibleSleepStart(false)
   104  			}
   105  			<-c
   106  			if sleep {
   107  				ctx.UninterruptibleSleepFinish(false)
   108  			}
   109  			continue
   110  		}
   111  
   112  		// Okay, we could restore all mappings at this point.
   113  		// But forget that. Let's just let them fault in.
   114  		mm.as = as
   115  
   116  		// Unmapping is done, if necessary.
   117  		mm.unmapAllOnActivate = false
   118  
   119  		// Now that m.as has been assigned, we can set m.active to a non-zero value
   120  		// to enable the fast path.
   121  		mm.active.Store(1)
   122  
   123  		mm.activeMu.Unlock()
   124  		return nil
   125  	}
   126  }
   127  
   128  // Deactivate releases a reference to the MemoryManager.
   129  func (mm *MemoryManager) Deactivate() {
   130  	// Fast path: this is not the last goroutine to deactivate the
   131  	// MemoryManager.
   132  	for {
   133  		active := mm.active.Load()
   134  		if active == 1 {
   135  			// Fall back to the slow path.
   136  			break
   137  		}
   138  		if mm.active.CompareAndSwap(active, active-1) {
   139  			return
   140  		}
   141  	}
   142  
   143  	mm.activeMu.Lock()
   144  	// Same as Activate.
   145  
   146  	// Still active?
   147  	if mm.active.Add(-1) > 0 {
   148  		mm.activeMu.Unlock()
   149  		return
   150  	}
   151  
   152  	// Can we hold on to the address space?
   153  	if !mm.p.CooperativelySchedulesAddressSpace() {
   154  		mm.activeMu.Unlock()
   155  		return
   156  	}
   157  
   158  	// Release the address space.
   159  	mm.as.Release()
   160  
   161  	// Lost it.
   162  	mm.as = nil
   163  	mm.activeMu.Unlock()
   164  }
   165  
   166  // mapASLocked maps addresses in ar into mm.as.
   167  //
   168  // Preconditions:
   169  //   - mm.activeMu must be locked.
   170  //   - mm.as != nil.
   171  //   - ar.Length() != 0.
   172  //   - ar must be page-aligned.
   173  //   - pseg == mm.pmas.LowerBoundSegment(ar.Start).
   174  func (mm *MemoryManager) mapASLocked(pseg pmaIterator, ar hostarch.AddrRange, platformEffect memmap.MMapPlatformEffect) error {
   175  	// By default, map entire pmas at a time, under the assumption that there
   176  	// is no cost to mapping more of a pma than necessary.
   177  	mapAR := hostarch.AddrRange{0, ^hostarch.Addr(hostarch.PageSize - 1)}
   178  	if platformEffect != memmap.PlatformEffectDefault {
   179  		// When explicitly committing, only map ar, since overmapping may incur
   180  		// unexpected resource usage. When explicitly populating, do the same
   181  		// since an underlying device file may be sensitive to the mapped
   182  		// range.
   183  		mapAR = ar
   184  	} else if mapUnit := mm.p.MapUnit(); mapUnit != 0 {
   185  		// Limit the range we map to ar, aligned to mapUnit.
   186  		mapMask := hostarch.Addr(mapUnit - 1)
   187  		mapAR.Start = ar.Start &^ mapMask
   188  		// If rounding ar.End up overflows, just keep the existing mapAR.End.
   189  		if end := (ar.End + mapMask) &^ mapMask; end >= ar.End {
   190  			mapAR.End = end
   191  		}
   192  	}
   193  	if checkInvariants {
   194  		if !mapAR.IsSupersetOf(ar) {
   195  			panic(fmt.Sprintf("mapAR %#v is not a superset of ar %#v", mapAR, ar))
   196  		}
   197  	}
   198  
   199  	// Since this checks ar.End and not mapAR.End, we will never map a pma that
   200  	// is not required.
   201  	for pseg.Ok() && pseg.Start() < ar.End {
   202  		pma := pseg.ValuePtr()
   203  		pmaAR := pseg.Range()
   204  		pmaMapAR := pmaAR.Intersect(mapAR)
   205  		perms := pma.effectivePerms
   206  		if pma.needCOW {
   207  			perms.Write = false
   208  		}
   209  		if perms.Any() { // MapFile precondition
   210  			if err := mm.as.MapFile(pmaMapAR.Start, pma.file, pseg.fileRangeOf(pmaMapAR), perms, platformEffect == memmap.PlatformEffectCommit); err != nil {
   211  				return err
   212  			}
   213  		}
   214  		pseg = pseg.NextSegment()
   215  	}
   216  	return nil
   217  }
   218  
   219  // unmapASLocked removes all AddressSpace mappings for addresses in ar.
   220  //
   221  // Preconditions: mm.activeMu must be locked.
   222  func (mm *MemoryManager) unmapASLocked(ar hostarch.AddrRange) {
   223  	if ar.Length() == 0 {
   224  		return
   225  	}
   226  	if mm.as == nil {
   227  		// No AddressSpace? Force all mappings to be unmapped on the next
   228  		// Activate.
   229  		mm.unmapAllOnActivate = true
   230  		return
   231  	}
   232  
   233  	// unmapASLocked doesn't require vmas or pmas to exist for ar, so it can be
   234  	// passed ranges that include addresses that can't be mapped by the
   235  	// application.
   236  	ar = ar.Intersect(mm.applicationAddrRange())
   237  
   238  	// Note that this AddressSpace may or may not be active. If the
   239  	// platform does not require cooperative sharing of AddressSpaces, they
   240  	// are retained between Deactivate/Activate calls. Despite not being
   241  	// active, it is still valid to perform operations on these address
   242  	// spaces.
   243  	mm.as.Unmap(ar.Start, uint64(ar.Length()))
   244  }