github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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/nicocha30/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  }