github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/fsutil/host_mappable.go (about)

     1  // Copyright 2019 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 fsutil
    16  
    17  import (
    18  	"math"
    19  
    20  	"github.com/SagerNet/gvisor/pkg/context"
    21  	"github.com/SagerNet/gvisor/pkg/hostarch"
    22  	"github.com/SagerNet/gvisor/pkg/safemem"
    23  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    24  	"github.com/SagerNet/gvisor/pkg/sentry/memmap"
    25  	"github.com/SagerNet/gvisor/pkg/sync"
    26  	"github.com/SagerNet/gvisor/pkg/usermem"
    27  )
    28  
    29  // HostMappable implements memmap.Mappable and memmap.File over a
    30  // CachedFileObject.
    31  //
    32  // Lock order (compare the lock order model in mm/mm.go):
    33  //   truncateMu ("fs locks")
    34  //     mu ("memmap.Mappable locks not taken by Translate")
    35  //       ("memmap.File locks")
    36  //   	     backingFile ("CachedFileObject locks")
    37  //
    38  // +stateify savable
    39  type HostMappable struct {
    40  	hostFileMapper *HostFileMapper
    41  
    42  	backingFile CachedFileObject
    43  
    44  	mu sync.Mutex `state:"nosave"`
    45  
    46  	// mappings tracks mappings of the cached file object into
    47  	// memmap.MappingSpaces so it can invalidated upon save. Protected by mu.
    48  	mappings memmap.MappingSet
    49  
    50  	// truncateMu protects writes and truncations. See Truncate() for details.
    51  	truncateMu sync.RWMutex `state:"nosave"`
    52  }
    53  
    54  // NewHostMappable creates a new mappable that maps directly to host FD.
    55  func NewHostMappable(backingFile CachedFileObject) *HostMappable {
    56  	return &HostMappable{
    57  		hostFileMapper: NewHostFileMapper(),
    58  		backingFile:    backingFile,
    59  	}
    60  }
    61  
    62  // AddMapping implements memmap.Mappable.AddMapping.
    63  func (h *HostMappable) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error {
    64  	// Hot path. Avoid defers.
    65  	h.mu.Lock()
    66  	mapped := h.mappings.AddMapping(ms, ar, offset, writable)
    67  	for _, r := range mapped {
    68  		h.hostFileMapper.IncRefOn(r)
    69  	}
    70  	h.mu.Unlock()
    71  	return nil
    72  }
    73  
    74  // RemoveMapping implements memmap.Mappable.RemoveMapping.
    75  func (h *HostMappable) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) {
    76  	// Hot path. Avoid defers.
    77  	h.mu.Lock()
    78  	unmapped := h.mappings.RemoveMapping(ms, ar, offset, writable)
    79  	for _, r := range unmapped {
    80  		h.hostFileMapper.DecRefOn(r)
    81  	}
    82  	h.mu.Unlock()
    83  }
    84  
    85  // CopyMapping implements memmap.Mappable.CopyMapping.
    86  func (h *HostMappable) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error {
    87  	return h.AddMapping(ctx, ms, dstAR, offset, writable)
    88  }
    89  
    90  // Translate implements memmap.Mappable.Translate.
    91  func (h *HostMappable) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) {
    92  	return []memmap.Translation{
    93  		{
    94  			Source: optional,
    95  			File:   h,
    96  			Offset: optional.Start,
    97  			Perms:  hostarch.AnyAccess,
    98  		},
    99  	}, nil
   100  }
   101  
   102  // InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable.
   103  func (h *HostMappable) InvalidateUnsavable(_ context.Context) error {
   104  	h.mu.Lock()
   105  	h.mappings.InvalidateAll(memmap.InvalidateOpts{})
   106  	h.mu.Unlock()
   107  	return nil
   108  }
   109  
   110  // NotifyChangeFD must be called after the file description represented by
   111  // CachedFileObject.FD() changes.
   112  func (h *HostMappable) NotifyChangeFD() error {
   113  	// Update existing sentry mappings to refer to the new file description.
   114  	if err := h.hostFileMapper.RegenerateMappings(h.backingFile.FD()); err != nil {
   115  		return err
   116  	}
   117  
   118  	// Shoot down existing application mappings of the old file description;
   119  	// they will be remapped with the new file description on demand.
   120  	h.mu.Lock()
   121  	defer h.mu.Unlock()
   122  
   123  	h.mappings.InvalidateAll(memmap.InvalidateOpts{})
   124  	return nil
   125  }
   126  
   127  // MapInternal implements memmap.File.MapInternal.
   128  func (h *HostMappable) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) {
   129  	return h.hostFileMapper.MapInternal(fr, h.backingFile.FD(), at.Write)
   130  }
   131  
   132  // FD implements memmap.File.FD.
   133  func (h *HostMappable) FD() int {
   134  	return h.backingFile.FD()
   135  }
   136  
   137  // IncRef implements memmap.File.IncRef.
   138  func (h *HostMappable) IncRef(fr memmap.FileRange) {
   139  	mr := memmap.MappableRange{Start: fr.Start, End: fr.End}
   140  	h.hostFileMapper.IncRefOn(mr)
   141  }
   142  
   143  // DecRef implements memmap.File.DecRef.
   144  func (h *HostMappable) DecRef(fr memmap.FileRange) {
   145  	mr := memmap.MappableRange{Start: fr.Start, End: fr.End}
   146  	h.hostFileMapper.DecRefOn(mr)
   147  }
   148  
   149  // Truncate truncates the file, invalidating any mapping that may have been
   150  // removed after the size change.
   151  //
   152  // Truncation and writes are synchronized to prevent races where writes make the
   153  // file grow between truncation and invalidation below:
   154  //   T1: Calls SetMaskedAttributes and stalls
   155  //   T2: Appends to file causing it to grow
   156  //   T2: Writes to mapped pages and COW happens
   157  //   T1: Continues and wronly invalidates the page mapped in step above.
   158  func (h *HostMappable) Truncate(ctx context.Context, newSize int64, uattr fs.UnstableAttr) error {
   159  	h.truncateMu.Lock()
   160  	defer h.truncateMu.Unlock()
   161  
   162  	mask := fs.AttrMask{Size: true}
   163  	attr := fs.UnstableAttr{Size: newSize}
   164  
   165  	// Truncating a file clears privilege bits.
   166  	if uattr.Perms.HasSetUIDOrGID() {
   167  		mask.Perms = true
   168  		attr.Perms = uattr.Perms
   169  		attr.Perms.DropSetUIDAndMaybeGID()
   170  	}
   171  
   172  	if err := h.backingFile.SetMaskedAttributes(ctx, mask, attr, false); err != nil {
   173  		return err
   174  	}
   175  
   176  	// Invalidate COW mappings that may exist beyond the new size in case the file
   177  	// is being shrunk. Other mappings don't need to be invalidated because
   178  	// translate will just return identical mappings after invalidation anyway,
   179  	// and SIGBUS will be raised and handled when the mappings are touched.
   180  	//
   181  	// Compare Linux's mm/truncate.c:truncate_setsize() =>
   182  	// truncate_pagecache() =>
   183  	// mm/memory.c:unmap_mapping_range(evencows=1).
   184  	h.mu.Lock()
   185  	defer h.mu.Unlock()
   186  	mr := memmap.MappableRange{
   187  		Start: fs.OffsetPageEnd(newSize),
   188  		End:   fs.OffsetPageEnd(math.MaxInt64),
   189  	}
   190  	h.mappings.Invalidate(mr, memmap.InvalidateOpts{InvalidatePrivate: true})
   191  
   192  	return nil
   193  }
   194  
   195  // Allocate reserves space in the backing file.
   196  func (h *HostMappable) Allocate(ctx context.Context, offset int64, length int64) error {
   197  	h.truncateMu.RLock()
   198  	err := h.backingFile.Allocate(ctx, offset, length)
   199  	h.truncateMu.RUnlock()
   200  	return err
   201  }
   202  
   203  // Write writes to the file backing this mappable.
   204  func (h *HostMappable) Write(ctx context.Context, src usermem.IOSequence, offset int64, uattr fs.UnstableAttr) (int64, error) {
   205  	h.truncateMu.RLock()
   206  	defer h.truncateMu.RUnlock()
   207  	n, err := src.CopyInTo(ctx, &writer{ctx: ctx, hostMappable: h, off: offset})
   208  	if n > 0 && uattr.Perms.HasSetUIDOrGID() {
   209  		mask := fs.AttrMask{Perms: true}
   210  		uattr.Perms.DropSetUIDAndMaybeGID()
   211  		if err := h.backingFile.SetMaskedAttributes(ctx, mask, uattr, false); err != nil {
   212  			return n, err
   213  		}
   214  	}
   215  	return n, err
   216  }
   217  
   218  type writer struct {
   219  	ctx          context.Context
   220  	hostMappable *HostMappable
   221  	off          int64
   222  }
   223  
   224  // WriteFromBlocks implements safemem.Writer.WriteFromBlocks.
   225  func (w *writer) WriteFromBlocks(src safemem.BlockSeq) (uint64, error) {
   226  	n, err := w.hostMappable.backingFile.WriteFromBlocksAt(w.ctx, src, uint64(w.off))
   227  	w.off += int64(n)
   228  	return n, err
   229  }