Skip to content

Commit 93333d2

Browse files
committed
runsc: add support to control CPU features exposed to user apps
Currently, runsc compares the host feature set between the machine used for checkpointing and the machine used for restoring. If the former feature set is not a subset of the latter, the restore will fail, as user apps might mistakenly use unsupported instructions. Thanks to cpuid faulting support, it is possible to intercept the cpuid instruction from user apps and generate results referring to the host feature set recorded during sandbox boot. This patch adds support to expose only specified CPU features by removing features from the list when setting the feature set of kernel. This makes it possible to checkpoint and restore on machines with different CPU features. The list of enabled features is passed via annotation. It should be noted that currently this feature is not supported on ARM64, as there is no ARM64 equivalent to cpuid.Static.Remove(). Updates #11486 Signed-off-by: Tianyu Zhou <[email protected]> Reviewed-by: Jamie Liu <[email protected]>
1 parent 85e7611 commit 93333d2

File tree

4 files changed

+51
-1
lines changed

4 files changed

+51
-1
lines changed

pkg/cpuid/native_amd64.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,23 @@ func (fs FeatureSet) query(fn cpuidFunction) (uint32, uint32, uint32, uint32) {
163163
return out.Eax, out.Ebx, out.Ecx, out.Edx
164164
}
165165

166+
// Intersect returns the intersection of features between self and allowedFeatures.
167+
func (fs FeatureSet) Intersect(allowedFeatures map[Feature]struct{}) (FeatureSet, error) {
168+
hs := fs.ToStatic()
169+
170+
// only keep features inside allowedFeatures.
171+
for f, _ := range allFeatures {
172+
if fs.HasFeature(f) {
173+
if _, ok := allowedFeatures[f]; !ok {
174+
log.Infof("Removing CPU feature %v as it is not allowed.", f)
175+
hs.Remove(f)
176+
}
177+
}
178+
}
179+
180+
return hs.ToFeatureSet(), nil
181+
}
182+
166183
var hostFeatureSet FeatureSet
167184

168185
// HostFeatureSet returns a host CPUID.

pkg/cpuid/native_arm64.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package cpuid
1919

2020
import (
21+
"fmt"
2122
"os"
2223
"runtime"
2324
"strconv"
@@ -41,6 +42,13 @@ func (fs FeatureSet) Fixed() FeatureSet {
4142
return fs
4243
}
4344

45+
// Intersect returns the intersection of features between self and allowedFeatures.
46+
//
47+
// Just return error as there is no ARM64 equivalent to cpuid.Static.Remove().
48+
func (fs FeatureSet) Intersect(allowedFeatures map[Feature]struct{}) (FeatureSet, error) {
49+
return FeatureSet{}, fmt.Errorf("FeatureSet intersection is not supported on ARM64")
50+
}
51+
4452
// Reads CPU information from host /proc/cpuinfo.
4553
//
4654
// Must run before syscall filter installation. This value is used to create

runsc/boot/loader.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"os"
2222
"runtime"
2323
"strconv"
24+
"strings"
2425
gtime "time"
2526

2627
"github.com/moby/sys/capability"
@@ -597,6 +598,26 @@ func New(args Args) (*Loader, error) {
597598
log.Infof("Setting total memory to %.2f GB", float64(args.TotalMem)/(1<<30))
598599
}
599600

601+
cpufs := cpuid.HostFeatureSet()
602+
if value, ok := args.Spec.Annotations[specutils.AnnotationCpuFeatures]; ok {
603+
allowedFeatures := make(map[cpuid.Feature]struct{})
604+
for str := range strings.SplitSeq(value, ",") {
605+
if str == "" {
606+
continue // ignore empty feature name rather than return error
607+
}
608+
f, ok := cpuid.FeatureFromString(str)
609+
if !ok {
610+
return nil, fmt.Errorf("annotation %s contains unknown feature %s", specutils.AnnotationCpuFeatures, str)
611+
}
612+
allowedFeatures[f] = struct{}{}
613+
}
614+
afs, err := cpufs.Intersect(allowedFeatures) // err only needed for now because this isn't implemented on ARM64 yet
615+
if err != nil {
616+
return nil, err
617+
}
618+
cpufs = afs
619+
}
620+
600621
maxFDLimit := kernel.MaxFdLimit
601622
if args.Spec.Linux != nil && args.Spec.Linux.Sysctl != nil {
602623
if val, ok := args.Spec.Linux.Sysctl["fs.nr_open"]; ok {
@@ -616,7 +637,7 @@ func New(args Args) (*Loader, error) {
616637
DisconnectOnSave: args.Conf.NetDisconnectOk,
617638
}
618639
if err = l.k.Init(kernel.InitKernelArgs{
619-
FeatureSet: cpuid.HostFeatureSet().Fixed(),
640+
FeatureSet: cpufs.Fixed(),
620641
Timekeeper: tk,
621642
RootUserNamespace: creds.UserNamespace,
622643
RootNetworkNamespace: netns,

runsc/specutils/specutils.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ const (
7070

7171
// AnnotationTPU is the annotation used to enable TPU proxy on a pod.
7272
AnnotationTPU = "dev.gvisor.internal.tpuproxy"
73+
74+
// AnnotationCpuFeatures is the annotation used to control cpu features
75+
// that exposed to user apps.
76+
AnnotationCpuFeatures = "dev.gvisor.internal.cpufeatures"
7377
)
7478

7579
// ExePath must point to runsc binary, which is normally the same binary. It's

0 commit comments

Comments
 (0)