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 }