github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/ring0/pagetables/pagetables.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 pagetables provides a generic implementation of pagetables. 16 // 17 // The core functions must be safe to call from a nosplit context. Furthermore, 18 // this pagetables implementation goes to lengths to ensure that all functions 19 // are free from runtime allocation. Calls to NewPTEs/FreePTEs may be made 20 // during walks, but these can be cached elsewhere if required. 21 package pagetables 22 23 import ( 24 "github.com/ttpreport/gvisor-ligolo/pkg/hostarch" 25 ) 26 27 // PageTables is a set of page tables. 28 type PageTables struct { 29 // Allocator is used to allocate nodes. 30 Allocator Allocator 31 32 // root is the pagetable root. 33 // 34 // For same archs such as amd64, the upper of the PTEs is cloned 35 // from and owned by upperSharedPageTables which are shared among 36 // many PageTables if upperSharedPageTables is not nil. 37 root *PTEs 38 39 // rootPhysical is the cached physical address of the root. 40 // 41 // This is saved only to prevent constant translation. 42 rootPhysical uintptr 43 44 // archPageTables includes architecture-specific features. 45 archPageTables 46 47 // upperSharedPageTables represents a read-only shared upper 48 // of the Pagetable. When it is not nil, the upper is not 49 // allowed to be modified. 50 upperSharedPageTables *PageTables 51 52 // upperStart is the start address of the upper portion that 53 // are shared from upperSharedPageTables 54 upperStart uintptr 55 56 // readOnlyShared indicates the Pagetables are read-only and 57 // own the ranges that are shared with other Pagetables. 58 readOnlyShared bool 59 } 60 61 // Init initializes a set of PageTables. 62 // 63 // +checkescape:hard,stack 64 // 65 //go:nosplit 66 func (p *PageTables) Init(allocator Allocator) { 67 p.Allocator = allocator 68 p.root = p.Allocator.NewPTEs() 69 p.rootPhysical = p.Allocator.PhysicalFor(p.root) 70 } 71 72 // NewWithUpper returns new PageTables. 73 // 74 // upperSharedPageTables are used for mapping the upper of addresses, 75 // starting at upperStart. These pageTables should not be touched (as 76 // invalidations may be incorrect) after they are passed as an 77 // upperSharedPageTables. Only when all dependent PageTables are gone 78 // may they be used. The intenteded use case is for kernel page tables, 79 // which are static and fixed. 80 // 81 // Precondition: upperStart must be between canonical ranges. 82 // Precondition: upperStart must be pgdSize aligned. 83 // precondition: upperSharedPageTables must be marked read-only shared. 84 func NewWithUpper(a Allocator, upperSharedPageTables *PageTables, upperStart uintptr) *PageTables { 85 p := new(PageTables) 86 p.Init(a) 87 88 if upperSharedPageTables != nil { 89 if !upperSharedPageTables.readOnlyShared { 90 panic("Only read-only shared pagetables can be used as upper") 91 } 92 p.upperSharedPageTables = upperSharedPageTables 93 p.upperStart = upperStart 94 } 95 96 p.InitArch(a) 97 return p 98 } 99 100 // New returns new PageTables. 101 func New(a Allocator) *PageTables { 102 return NewWithUpper(a, nil, 0) 103 } 104 105 // mapVisitor is used for map. 106 type mapVisitor struct { 107 target uintptr // Input. 108 physical uintptr // Input. 109 opts MapOpts // Input. 110 prev bool // Output. 111 } 112 113 // visit is used for map. 114 // 115 //go:nosplit 116 func (v *mapVisitor) visit(start uintptr, pte *PTE, align uintptr) bool { 117 p := v.physical + (start - uintptr(v.target)) 118 if pte.Valid() && (pte.Address() != p || pte.Opts() != v.opts) { 119 v.prev = true 120 } 121 if p&align != 0 { 122 // We will install entries at a smaller granulaity if we don't 123 // install a valid entry here, however we must zap any existing 124 // entry to ensure this happens. 125 pte.Clear() 126 return true 127 } 128 pte.Set(p, v.opts) 129 return true 130 } 131 132 //go:nosplit 133 func (*mapVisitor) requiresAlloc() bool { return true } 134 135 //go:nosplit 136 func (*mapVisitor) requiresSplit() bool { return true } 137 138 // Map installs a mapping with the given physical address. 139 // 140 // True is returned iff there was a previous mapping in the range. 141 // 142 // Precondition: addr & length must be page-aligned, their sum must not overflow. 143 // 144 // +checkescape:hard,stack 145 // 146 //go:nosplit 147 func (p *PageTables) Map(addr hostarch.Addr, length uintptr, opts MapOpts, physical uintptr) bool { 148 if p.readOnlyShared { 149 panic("Should not modify read-only shared pagetables.") 150 } 151 if uintptr(addr)+length < uintptr(addr) { 152 panic("addr & length overflow") 153 } 154 if p.upperSharedPageTables != nil { 155 // ignore change to the read-only upper shared portion. 156 if uintptr(addr) >= p.upperStart { 157 return false 158 } 159 if uintptr(addr)+length > p.upperStart { 160 length = p.upperStart - uintptr(addr) 161 } 162 } 163 w := mapWalker{ 164 pageTables: p, 165 visitor: mapVisitor{ 166 target: uintptr(addr), 167 physical: physical, 168 opts: opts, 169 }, 170 } 171 w.iterateRange(uintptr(addr), uintptr(addr)+length) 172 return w.visitor.prev 173 } 174 175 // unmapVisitor is used for unmap. 176 type unmapVisitor struct { 177 count int 178 } 179 180 //go:nosplit 181 func (*unmapVisitor) requiresAlloc() bool { return false } 182 183 //go:nosplit 184 func (*unmapVisitor) requiresSplit() bool { return true } 185 186 // visit unmaps the given entry. 187 // 188 //go:nosplit 189 func (v *unmapVisitor) visit(start uintptr, pte *PTE, align uintptr) bool { 190 pte.Clear() 191 v.count++ 192 return true 193 } 194 195 // Unmap unmaps the given range. 196 // 197 // True is returned iff there was a previous mapping in the range. 198 // 199 // Precondition: addr & length must be page-aligned, their sum must not overflow. 200 // 201 // +checkescape:hard,stack 202 // 203 //go:nosplit 204 func (p *PageTables) Unmap(addr hostarch.Addr, length uintptr) bool { 205 if p.readOnlyShared { 206 panic("Should not modify read-only shared pagetables.") 207 } 208 if uintptr(addr)+length < uintptr(addr) { 209 panic("addr & length overflow") 210 } 211 if p.upperSharedPageTables != nil { 212 // ignore change to the read-only upper shared portion. 213 if uintptr(addr) >= p.upperStart { 214 return false 215 } 216 if uintptr(addr)+length > p.upperStart { 217 length = p.upperStart - uintptr(addr) 218 } 219 } 220 w := unmapWalker{ 221 pageTables: p, 222 visitor: unmapVisitor{ 223 count: 0, 224 }, 225 } 226 w.iterateRange(uintptr(addr), uintptr(addr)+length) 227 return w.visitor.count > 0 228 } 229 230 // emptyVisitor is used for emptiness checks. 231 type emptyVisitor struct { 232 count int 233 } 234 235 //go:nosplit 236 func (*emptyVisitor) requiresAlloc() bool { return false } 237 238 //go:nosplit 239 func (*emptyVisitor) requiresSplit() bool { return false } 240 241 // visit unmaps the given entry. 242 // 243 //go:nosplit 244 func (v *emptyVisitor) visit(start uintptr, pte *PTE, align uintptr) bool { 245 v.count++ 246 return true 247 } 248 249 // IsEmpty checks if the given range is empty. 250 // 251 // Precondition: addr & length must be page-aligned. 252 // 253 // +checkescape:hard,stack 254 // 255 //go:nosplit 256 func (p *PageTables) IsEmpty(addr hostarch.Addr, length uintptr) bool { 257 w := emptyWalker{ 258 pageTables: p, 259 } 260 w.iterateRange(uintptr(addr), uintptr(addr)+length) 261 return w.visitor.count == 0 262 } 263 264 // lookupVisitor is used for lookup. 265 type lookupVisitor struct { 266 target uintptr // Input & Output. 267 findFirst bool // Input. 268 physical uintptr // Output. 269 size uintptr // Output. 270 opts MapOpts // Output. 271 } 272 273 // visit matches the given address. 274 // 275 //go:nosplit 276 func (v *lookupVisitor) visit(start uintptr, pte *PTE, align uintptr) bool { 277 if !pte.Valid() { 278 // If looking for the first, then we just keep iterating until 279 // we find a valid entry. 280 return v.findFirst 281 } 282 // Is this within the current range? 283 v.target = start 284 v.physical = pte.Address() 285 v.size = (align + 1) 286 v.opts = pte.Opts() 287 return false 288 } 289 290 //go:nosplit 291 func (*lookupVisitor) requiresAlloc() bool { return false } 292 293 //go:nosplit 294 func (*lookupVisitor) requiresSplit() bool { return false } 295 296 // Lookup returns the physical address for the given virtual address. 297 // 298 // If findFirst is true, then the next valid address after addr is returned. 299 // If findFirst is false, then only a mapping for addr will be returned. 300 // 301 // Note that if size is zero, then no matching entry was found. 302 // 303 // +checkescape:hard,stack 304 // 305 //go:nosplit 306 func (p *PageTables) Lookup(addr hostarch.Addr, findFirst bool) (virtual hostarch.Addr, physical, size uintptr, opts MapOpts) { 307 mask := uintptr(hostarch.PageSize - 1) 308 addr &^= hostarch.Addr(mask) 309 w := lookupWalker{ 310 pageTables: p, 311 visitor: lookupVisitor{ 312 target: uintptr(addr), 313 findFirst: findFirst, 314 }, 315 } 316 end := ^hostarch.Addr(0) &^ hostarch.Addr(mask) 317 if !findFirst { 318 end = addr + 1 319 } 320 w.iterateRange(uintptr(addr), uintptr(end)) 321 return hostarch.Addr(w.visitor.target), w.visitor.physical, w.visitor.size, w.visitor.opts 322 } 323 324 // MarkReadOnlyShared marks the pagetables read-only and can be shared. 325 // 326 // It is usually used on the pagetables that are used as the upper 327 func (p *PageTables) MarkReadOnlyShared() { 328 p.readOnlyShared = true 329 }