Files
Konstantin BogomolovandgVisor bot f1a883bdde Detect if vvar is not mappable into the guest on ARM.
This adds a check if vvar cannot be mapped into the guest in a throw-away VM on
potentially problematic host kernels.

Linux commits 2a8dfab and 0c67288 make our mapping of vvar into the guest fail
as soon as we try to create it, because it is a VM_PFNMAP region.

Not mapping vvar into the guest severely hinders gVisor performance, because if
we don't map vvar then any time-related host syscall will always force a VM
exit, for example. However, given the alternative if/until a backport is merged
into the affected kernels is to just crash, we don't have much choice.

Note that this also revealed two errors:
- in KERNEL_ENTRY_FROM_EL1, which assumed R18 would not be relevant because
  Go never uses it -- this is no longer the case with the sentry faulting while
  in the VDSO code.
- in the interrupt stack size which was sized too small and thus corrupted
  errorCode and errorType variables when it overflowed. This is a problem with
  this patch specifically, because we now generate a data abort exception every
  time we use vvar, which exposes the bug more frequently.

  This also led to a closer inspection of the EXCEPTION_EL1 macro, which
  revealed a relatively harmless bug of vector not being saved properly.

PiperOrigin-RevId: 968804355
2026-08-21 18:23:37 -07:00

146 lines
3.8 KiB
Go

// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package kvm
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
"gvisor.dev/gvisor/pkg/hostarch"
)
type virtualRegion struct {
region
accessType hostarch.AccessType
shared bool
offset uintptr
filename string
}
// mapsLine matches a single line from /proc/PID/maps.
var mapsLine = regexp.MustCompile(`([0-9a-f]+)-([0-9a-f]+) ([r-][w-][x-][sp]) ([0-9a-f]+) [0-9a-f]{2,3}:[0-9a-f]{2,} [0-9]+\s+(.*)`)
var excludeVvarRegion bool
// excludeVirtualRegion returns true if these regions should be excluded from
// the physical map. Virtual regions need to be excluded if get_user_pages will
// fail on those addresses, preventing KVM from satisfying EPT faults.
//
// This is called by the physical map functions, not applyVirtualRegions.
func excludeVirtualRegion(r virtualRegion) bool {
return excludeVvarRegion && (strings.HasPrefix(r.filename, "[vvar") || strings.HasPrefix(r.filename, "[vdso"))
}
// mmioVirtualRegion returns true if r should be included in the physical map,
// but not actually mapped in the physical address space. This ensures that
// accesses to this region cause MMIO exits.
//
// This is called by the physical map functions, not applyVirtualRegions.
func mmioVirtualRegion(r virtualRegion) bool {
return r.virtual == ioeventfdMMIO.mapping
}
// applyVirtualRegions parses the process maps file.
//
// Takes a function that's called on each virtualRegion. If function returns
// true, stops iteration.
//
// Unlike mappedRegions, these are not consistent over time.
func applyVirtualRegions(fn func(vr virtualRegion) bool) error {
// Open /proc/self/maps.
f, err := os.Open("/proc/self/maps")
if err != nil {
return err
}
defer f.Close()
// Parse all entries.
r := bufio.NewReader(f)
for {
b, err := r.ReadBytes('\n')
if len(b) > 0 {
m := mapsLine.FindSubmatch(b)
if m == nil {
// This should not happen: kernel bug?
return fmt.Errorf("badly formed line: %v", string(b))
}
start, err := strconv.ParseUint(string(m[1]), 16, 64)
if err != nil {
return fmt.Errorf("bad start address: %v", string(b))
}
end, err := strconv.ParseUint(string(m[2]), 16, 64)
if err != nil {
return fmt.Errorf("bad end address: %v", string(b))
}
read := m[3][0] == 'r'
write := m[3][1] == 'w'
execute := m[3][2] == 'x'
shared := m[3][3] == 's'
offset, err := strconv.ParseUint(string(m[4]), 16, 64)
if err != nil {
return fmt.Errorf("bad offset: %v", string(b))
}
if stop := fn(virtualRegion{
region: region{
virtual: uintptr(start),
length: uintptr(end - start),
},
accessType: hostarch.AccessType{
Read: read,
Write: write,
Execute: execute,
},
shared: shared,
offset: uintptr(offset),
filename: string(m[5]),
}); stop {
break
}
}
if err != nil && err == io.EOF {
break
} else if err != nil {
return err
}
}
return nil
}
func findRegionStartingWith(name string) (region, error) {
var r region
var found bool
err := applyVirtualRegions(func(vr virtualRegion) bool {
if strings.HasPrefix(vr.filename, name) {
r = vr.region
found = true
return true
}
return false
})
if err != nil {
return region{}, err
}
if !found {
return region{}, fmt.Errorf("region %s not found", name)
}
return r, nil
}