Skip to content

Commit f88f7ce

Browse files
tranji-cloudgvisor-bot
authored andcommitted
Detect if 5-level paging is actually enabled on the host.
The CPUID can indicate support for 5-level paging (57 virtual address bits), but the kernel might not have it enabled. This change adds a check using `mmap` to see if addresses above 2^48 are usable. If not, `VirtualAddressBits` is capped at 48, even if CPUID suggests 57. Fixes #12308 PiperOrigin-RevId: 832518109
1 parent 2d94e66 commit f88f7ce

File tree

5 files changed

+71
-26
lines changed

5 files changed

+71
-26
lines changed

pkg/cpuid/native_amd64.go

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -192,50 +192,88 @@ func HostFeatureSet() FeatureSet {
192192
var (
193193
// cpuFreqMHz is the native CPU frequency.
194194
cpuFreqMHz float64
195+
196+
// IsFiveLevelPagingEnabled is true if 5-level paging is enabled on the host.
197+
IsFiveLevelPagingEnabled bool
195198
)
196199

197-
// Reads max cpu frequency from host /proc/cpuinfo. Must run before syscall
198-
// filter installation. This value is used to create the fake /proc/cpuinfo
199-
// from a FeatureSet.
200-
func readMaxCPUFreq() {
200+
// readCPUInfo reads cpu frequency and checks for 5-level paging support from
201+
// /proc/cpuinfo in a single pass. It sets the global cpuFreqMHz and returns
202+
// whether 5-level paging is enabled.
203+
func readCPUInfo() {
201204
cpuinfoFile, err := os.Open("/proc/cpuinfo")
202205
if err != nil {
203-
// Leave it as 0... the VDSO bails out in the same way.
206+
// Leave cpuFreqMHz as 0... the VDSO bails out in the same way.
207+
// Default to assuming 5-level paging is not enabled.
208+
IsFiveLevelPagingEnabled = false
204209
log.Warningf("Could not open /proc/cpuinfo: %v", err)
205210
return
206211
}
207212
defer cpuinfoFile.Close()
208213

209-
// We get the value straight from host /proc/cpuinfo. On machines with
210-
// frequency scaling enabled, this will only get the current value
211-
// which will likely be inaccurate. This is fine on machines with
212-
// frequency scaling disabled.
214+
var (
215+
foundCPUFreq bool
216+
foundFlags bool
217+
)
218+
213219
s := bufio.NewScanner(cpuinfoFile)
214220
for s.Scan() {
215221
line := s.Bytes()
216-
if bytes.Contains(line, []byte("cpu MHz")) {
222+
// We get the value straight from host /proc/cpuinfo. On machines with
223+
// frequency scaling enabled, this will only get the current value
224+
// which will likely be inaccurate. This is fine on machines with
225+
// frequency scaling disabled.
226+
if !foundCPUFreq && bytes.Contains(line, []byte("cpu MHz")) {
217227
splitMHz := bytes.Split(line, []byte(":"))
218228
if len(splitMHz) < 2 {
219229
log.Warningf("Could not parse /proc/cpuinfo: malformed cpu MHz line: %q", line)
220-
return
230+
} else {
231+
splitMHzStr := string(bytes.TrimSpace(splitMHz[1]))
232+
f64MHz, err := strconv.ParseFloat(splitMHzStr, 64)
233+
if err != nil {
234+
log.Warningf("Could not parse cpu MHz value %q: %v", splitMHzStr, err)
235+
} else {
236+
cpuFreqMHz = f64MHz
237+
}
221238
}
239+
foundCPUFreq = true
240+
}
222241

223-
var err error
224-
splitMHzStr := string(bytes.TrimSpace(splitMHz[1]))
225-
f64MHz, err := strconv.ParseFloat(splitMHzStr, 64)
226-
if err != nil {
227-
log.Warningf("Could not parse cpu MHz value %q: %v", splitMHzStr, err)
228-
return
242+
// https://www.kernel.org/doc/html/v6.0/x86/cpuinfo.html#flags-are-missing-when-one-or-more-of-these-happen
243+
// We check for the "la57" flag instead of "address sizes" because /proc/cpuinfo
244+
// reports "57 bits virtual" for 5lvl-capable CPUs even if 5-level paging is disabled.
245+
// The "la57" flag correctly indicates that it is enabled.
246+
if !foundFlags && bytes.Contains(line, []byte("flags")) {
247+
parts := bytes.SplitN(line, []byte(":"), 2)
248+
if len(parts) >= 2 {
249+
flags := bytes.Fields(parts[1])
250+
for _, flag := range flags {
251+
if bytes.Equal(flag, []byte("la57")) {
252+
IsFiveLevelPagingEnabled = true
253+
break
254+
}
255+
}
229256
}
230-
cpuFreqMHz = f64MHz
231-
return
257+
foundFlags = true
258+
}
259+
260+
if foundCPUFreq && foundFlags {
261+
break
232262
}
233263
}
264+
234265
if err := s.Err(); err != nil {
235266
log.Warningf("Could not read /proc/cpuinfo: %v", err)
236-
return
237267
}
238-
log.Warningf("Could not parse /proc/cpuinfo, it is empty or does not contain cpu MHz")
268+
269+
if !foundCPUFreq {
270+
log.Warningf("Could not parse /proc/cpuinfo, it is empty or does not contain cpu MHz")
271+
}
272+
if !foundFlags {
273+
log.Warningf("Could not parse /proc/cpuinfo, it is empty or does not contain flags")
274+
}
275+
// Default to assuming 5-level paging is not enabled.
276+
IsFiveLevelPagingEnabled = false
239277
}
240278

241279
// xgetbv reads an extended control register.
@@ -247,6 +285,6 @@ func archInitialize() {
247285
Function: &Native{},
248286
}.Fixed()
249287

250-
readMaxCPUFreq()
288+
readCPUInfo()
251289
initHWCap()
252290
}

pkg/ring0/kernel_amd64.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ func (c *CPU) CR4() uint64 {
205205
if hasUMIP {
206206
cr4 |= _CR4_UMIP
207207
}
208-
if hasLA57 {
208+
if hasLA57 && cpuid.IsFiveLevelPagingEnabled {
209209
cr4 |= _CR4_LA57
210210
}
211211
return cr4
@@ -222,6 +222,9 @@ func (c *CPU) EFER() uint64 {
222222
//
223223
//go:nosplit
224224
func IsCanonical(addr uint64) bool {
225+
if hasLA57 && cpuid.IsFiveLevelPagingEnabled {
226+
return addr <= 0x00ffffffffffffff || addr >= 0xff00000000000000
227+
}
225228
return addr <= 0x00007fffffffffff || addr >= 0xffff800000000000
226229
}
227230

pkg/ring0/lib_amd64.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ var (
100100
// another time with a different FeatureSet.
101101
func Init(fs cpuid.FeatureSet) {
102102
// Initialize all sizes.
103-
VirtualAddressBits = uintptr(fs.VirtualAddressBits())
103+
if fs.VirtualAddressBits() > 48 && !cpuid.IsFiveLevelPagingEnabled {
104+
VirtualAddressBits = 48
105+
} else {
106+
VirtualAddressBits = uintptr(fs.VirtualAddressBits())
107+
}
104108
if PhysicalAddressBits == 0 {
105109
PhysicalAddressBits = uintptr(fs.PhysicalAddressBits())
106110
}

pkg/ring0/pagetables/pagetables_amd64.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const (
5454
//go:nosplit
5555
func (p *PageTables) InitArch(allocator Allocator) {
5656
featureSet := cpuid.HostFeatureSet()
57-
if featureSet.HasFeature(cpuid.X86FeatureLA57) {
57+
if featureSet.HasFeature(cpuid.X86FeatureLA57) && cpuid.IsFiveLevelPagingEnabled {
5858
p.largeAddressesEnabled = true
5959
lowerTop = 0x00FFFFFFFFFFFFFF
6060
upperBottom = 0xFF00000000000000

pkg/ring0/pagetables/pagetables_amd64_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ var (
3131

3232
func getLargeAddressesEnabled() bool {
3333
featureSet := cpuid.HostFeatureSet()
34-
return featureSet.HasFeature(cpuid.X86FeatureLA57)
34+
return featureSet.HasFeature(cpuid.X86FeatureLA57) && cpuid.IsFiveLevelPagingEnabled
3535
}
3636

3737
func getLowerTopAligned() uintptr {

0 commit comments

Comments
 (0)