Integrate runsc features command with runsc shim

Refactored the `runsc features` logic into specutils
Support direct in-memory lookup in the shim and avoid the fork/exec roundtrip

Test result: http://sponge2/4543eb2c-c239-4459-8700-bf89ef081088

PiperOrigin-RevId: 919150093
This commit is contained in:
Rex Ren
2026-05-21 11:11:53 -07:00
committed by gVisor bot
parent 80569583f9
commit 27de98508e
7 changed files with 195 additions and 97 deletions
+13 -1
View File
@@ -1,4 +1,4 @@
load("//tools:defs.bzl", "go_library")
load("//tools:defs.bzl", "go_library", "go_test")
package(
default_applicable_licenses = ["//:license"],
@@ -23,6 +23,7 @@ go_library(
"//pkg/shim/v1/runsc:hibernate_go_proto",
"//pkg/shim/v1/runsccmd",
"//pkg/sync",
"//runsc/specutils",
"@com_github_burntsushi_toml//:go_default_library",
"@com_github_containerd_containerd_api//runtime/task/v2:go_default_library",
"@com_github_containerd_containerd_api//types:go_default_library",
@@ -35,6 +36,17 @@ go_library(
"@com_github_containerd_errdefs_pkg//errgrpc:go_default_library",
"@com_github_containerd_log//:go_default_library",
"@com_github_containerd_ttrpc//:go_default_library",
"@com_github_containerd_typeurl_v2//:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
go_test(
name = "v1_test",
srcs = ["manager_test.go"],
library = ":v1",
deps = [
"@com_github_containerd_typeurl_v2//:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go/features:go_default_library",
],
)
+17 -5
View File
@@ -18,12 +18,11 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"time"
"io"
"github.com/BurntSushi/toml"
types "github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/v2/core/mount"
@@ -31,11 +30,13 @@ import (
"github.com/containerd/containerd/v2/pkg/shim"
"github.com/containerd/containerd/v2/pkg/sys"
"github.com/containerd/log"
typeurl "github.com/containerd/typeurl/v2"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/shim/v1/proc"
"gvisor.dev/gvisor/pkg/shim/v1/runsc"
"gvisor.dev/gvisor/pkg/shim/v1/runsccmd"
"gvisor.dev/gvisor/runsc/specutils"
)
const (
@@ -249,12 +250,23 @@ func getEnableGrouping() bool {
}
func (m *manager) Info(ctx context.Context, optionsR io.Reader) (*types.RuntimeInfo, error) {
return &types.RuntimeInfo{
Name: "io.containerd.runsc.v1",
info := &types.RuntimeInfo{
Name: m.name,
Version: &types.RuntimeVersion{
Version: "v1.0.0",
},
}, nil
}
feat := specutils.Features()
if feat != nil {
var err error
info.Features, err = typeurl.MarshalAnyToProto(feat)
if err != nil {
return nil, fmt.Errorf("failed to marshal %T: %w", feat, err)
}
}
return info, nil
}
func writeAddress(path, address string) error {
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2026 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
//
// https://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 v1
import (
"bytes"
"context"
typeurl "github.com/containerd/typeurl/v2"
"github.com/opencontainers/runtime-spec/specs-go/features"
"testing"
)
// TestManagerInfo verifies that the Info() call returns the expected information
// about the runtime.
func TestManagerInfo(t *testing.T) {
m := NewShimManager("io.containerd.runsc.v1")
info, err := m.Info(context.Background(), bytes.NewReader(nil))
if err != nil {
t.Fatalf("Standalone ShimManager::Info call returned unexpected error: %v", err)
}
if info == nil {
t.Fatalf("ShimManager::Info got nil, want non-nil RuntimeInfo")
}
if info.Features == nil {
t.Fatalf("ShimManager::Info got nil, want populated Features struct from specutils")
}
decoded, err := typeurl.UnmarshalAny(info.Features)
if err != nil {
t.Fatalf("Failed to deserialize info.Features Any proto: %v", err)
}
_, ok := decoded.(*features.Features)
if !ok {
t.Fatalf("Decoded features is type %T, want *features.Features", decoded)
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ go_library(
"runtimeoptions.go",
"runtimeoptions_cri.go",
],
visibility = ["//pkg/shim/v1/runsc:__pkg__"],
visibility = ["//pkg/shim/v1:__subpackages__"],
deps = ["@com_github_gogo_protobuf//proto:go_default_library"],
)
+1 -88
View File
@@ -18,15 +18,12 @@ import (
"context"
"encoding/json"
"os"
"sort"
"github.com/google/subcommands"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-spec/specs-go/features"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/flag"
"gvisor.dev/gvisor/runsc/specutils"
"gvisor.dev/gvisor/runsc/specutils/seccomp"
)
// Features implements subcommands.Command for the "features" command.
@@ -58,91 +55,7 @@ func (*Features) FetchSpec(_ *config.Config, _ *flag.FlagSet) (string, *specs.Sp
// Execute implements subcommands.Command.Execute.
func (*Features) Execute(_ context.Context, _ *flag.FlagSet, args ...any) subcommands.ExitStatus {
feat := features.Features{
OCIVersionMin: "1.0.0",
OCIVersionMax: specs.Version,
Hooks: []string{
"prestart",
"createRuntime",
"createContainer",
"startContainer",
"poststart",
"poststop",
},
MountOptions: specutils.KnownMountOptions(),
Linux: &features.Linux{
Namespaces: specutils.KnownNamespaces(),
Capabilities: specutils.AllCapabilities().Bounding,
Cgroup: &features.Cgroup{
V1: boolPtr(true),
V2: boolPtr(false),
Systemd: boolPtr(false),
SystemdUser: boolPtr(false),
Rdma: boolPtr(false),
},
Seccomp: &features.Seccomp{
Enabled: boolPtr(true),
Actions: seccomp.KnownActions(),
Operators: seccomp.KnownOperators(),
Archs: seccomp.KnownArchs(),
KnownFlags: seccomp.KnownFlags(),
SupportedFlags: seccomp.SupportedFlags(),
},
Apparmor: &features.Apparmor{
Enabled: boolPtr(false),
},
Selinux: &features.Selinux{
Enabled: boolPtr(false),
},
IntelRdt: &features.IntelRdt{
Enabled: boolPtr(false),
},
MountExtensions: &features.MountExtensions{
IDMap: &features.IDMap{
Enabled: boolPtr(false),
},
},
},
}
tmpFs := flag.NewFlagSet("tmp", flag.ContinueOnError)
config.RegisterFlags(tmpFs)
annotations := make(map[string]string)
tmpFs.VisitAll(func(f *flag.Flag) {
key := "dev.gvisor.flag." + f.Name
annotations[key] = f.DefValue
})
// LINT.IfChange
annotations["dev.gvisor.container-name-remap."] = ""
annotations[specutils.AnnotationRootfsUpperTar] = ""
annotations["dev.gvisor.internal.seccomp."] = ""
annotations["dev.gvisor.internal.seccomp.cont"] = "RuntimeDefault"
annotations[specutils.AnnotationTPU] = ""
annotations[specutils.AnnotationCPUFeatures] = ""
// LINT.ThenChange(../specutils/specutils.go)
feat.Annotations = annotations
var unsafeAnnotations []string
tmpFs.VisitAll(func(f *flag.Flag) {
if !config.IsFlagSafeToOverride(f.Name) {
unsafeAnnotations = append(unsafeAnnotations, "dev.gvisor.flag."+f.Name)
}
})
// LINT.IfChange
unsafeAnnotations = append(unsafeAnnotations,
"dev.gvisor.container-name-remap.",
specutils.AnnotationRootfsUpperTar,
"dev.gvisor.internal.seccomp.",
specutils.AnnotationTPU,
specutils.AnnotationCPUFeatures,
)
// LINT.ThenChange(../specutils/specutils.go)
sort.Strings(unsafeAnnotations)
feat.PotentiallyUnsafeConfigAnnotations = unsafeAnnotations
feat := specutils.Features()
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
+2
View File
@@ -26,10 +26,12 @@ go_library(
"//pkg/sentry/kernel/auth",
"//runsc/config",
"//runsc/flag",
"//runsc/specutils/seccomp",
"@com_github_cenkalti_backoff//:go_default_library",
"@com_github_moby_sys_capability//:go_default_library",
"@com_github_mohae_deepcopy//:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go/features:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
+109 -2
View File
@@ -24,22 +24,26 @@ import (
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/cenkalti/backoff"
"github.com/mohae/deepcopy"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-spec/specs-go/features"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/flag"
"gvisor.dev/gvisor/runsc/specutils/seccomp"
)
// LINT.IfChange
// LINT.IfChange(features_annotations)
const (
// annotationFlagPrefix allows annotations to change the value of flags.
//
@@ -85,7 +89,7 @@ const (
AnnotationCPUFeatures = "dev.gvisor.internal.cpufeatures"
)
// LINT.ThenChange(../cmd/features.go)
// LINT.ThenChange(:Features)
// ExePath must point to runsc binary, which is normally the same binary. It's
// changed in tests that aren't linked in the same binary.
@@ -94,6 +98,11 @@ var ExePath = "/proc/self/exe"
// Version is the supported spec version.
var Version = specs.Version
var (
globalFeatures *features.Features
featuresOnce sync.Once
)
// LogSpecDebug writes the spec in a human-friendly format to the debug log.
func LogSpecDebug(orig *specs.Spec, logSeccomp bool) {
if log.IsLogging(log.Debug) {
@@ -906,3 +915,101 @@ func AnnotationToBool(spec *specs.Spec, annotation string) bool {
}
return ret
}
func boolPtr(b bool) *bool {
return &b
}
// Features returns the features supported by the runtime.
func Features() *features.Features {
featuresOnce.Do(func() {
feat := features.Features{
OCIVersionMin: "1.0.0",
OCIVersionMax: specs.Version,
Hooks: []string{
"prestart",
"createRuntime",
"createContainer",
"startContainer",
"poststart",
"poststop",
},
MountOptions: KnownMountOptions(),
Linux: &features.Linux{
Namespaces: KnownNamespaces(),
Capabilities: AllCapabilities().Bounding,
Cgroup: &features.Cgroup{
V1: boolPtr(true),
V2: boolPtr(false),
Systemd: boolPtr(false),
SystemdUser: boolPtr(false),
Rdma: boolPtr(false),
},
Seccomp: &features.Seccomp{
Enabled: boolPtr(true),
Actions: seccomp.KnownActions(),
Operators: seccomp.KnownOperators(),
Archs: seccomp.KnownArchs(),
KnownFlags: seccomp.KnownFlags(),
SupportedFlags: seccomp.SupportedFlags(),
},
Apparmor: &features.Apparmor{
Enabled: boolPtr(false),
},
Selinux: &features.Selinux{
Enabled: boolPtr(false),
},
IntelRdt: &features.IntelRdt{
Enabled: boolPtr(false),
},
MountExtensions: &features.MountExtensions{
IDMap: &features.IDMap{
Enabled: boolPtr(false),
},
},
},
}
tmpFs := flag.NewFlagSet("tmp", flag.ContinueOnError)
config.RegisterFlags(tmpFs)
annotations := make(map[string]string)
tmpFs.VisitAll(func(f *flag.Flag) {
key := "dev.gvisor.flag." + f.Name
annotations[key] = f.DefValue
})
// LINT.IfChange
annotations["dev.gvisor.container-name-remap."] = ""
annotations[AnnotationRootfsUpperTar] = ""
annotations["dev.gvisor.internal.seccomp."] = ""
annotations["dev.gvisor.internal.seccomp.cont"] = "RuntimeDefault"
annotations[AnnotationTPU] = ""
annotations[AnnotationCPUFeatures] = ""
// LINT.ThenChange(:features_annotations)
feat.Annotations = annotations
var unsafeAnnotations []string
tmpFs.VisitAll(func(f *flag.Flag) {
if !config.IsFlagSafeToOverride(f.Name) {
unsafeAnnotations = append(unsafeAnnotations, "dev.gvisor.flag."+f.Name)
}
})
// LINT.IfChange
unsafeAnnotations = append(unsafeAnnotations,
"dev.gvisor.container-name-remap.",
AnnotationRootfsUpperTar,
"dev.gvisor.internal.seccomp.",
AnnotationTPU,
AnnotationCPUFeatures,
)
// LINT.ThenChange(:features_annotations)
sort.Strings(unsafeAnnotations)
feat.PotentiallyUnsafeConfigAnnotations = unsafeAnnotations
globalFeatures = &feat
})
return globalFeatures
}