Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ require (
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0
github.com/intel/goresctrl v0.13.0
github.com/klauspost/compress v1.18.6
github.com/klauspost/compress v1.19.0
github.com/mdlayher/vsock v1.3.0
github.com/moby/locker v1.0.1
github.com/moby/sys/mountinfo v0.7.2
Expand Down Expand Up @@ -76,11 +76,11 @@ require (
go.opentelemetry.io/otel/sdk v1.44.0
go.opentelemetry.io/otel/trace v1.44.0
golang.org/x/mod v0.37.0
golang.org/x/sync v0.21.0
golang.org/x/sys v0.46.0
golang.org/x/sync v0.22.0
golang.org/x/sys v0.47.0
golang.org/x/time v0.15.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa
google.golang.org/grpc v1.81.1
google.golang.org/grpc v1.82.0
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
k8s.io/apimachinery v0.36.1
k8s.io/client-go v0.36.1
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/knqyf263/go-plugin v0.9.0 h1:CQs2+lOPIlkZVtcb835ZYDEoyyWJWLbSTWeCs0EwTwI=
github.com/knqyf263/go-plugin v0.9.0/go.mod h1:2z5lCO1/pez6qGo8CvCxSlBFSEat4MEp1DrnA+f7w8Q=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
Expand Down Expand Up @@ -437,8 +437,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -460,8 +460,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand Down Expand Up @@ -516,8 +516,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
Expand Down
17 changes: 9 additions & 8 deletions internal/cri/server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,14 +450,8 @@ func (c *criService) introspectRuntimeHandler(ctx context.Context, intro introsp
}

func introspectRuntimeFeatures(ctx context.Context, intro introspection.Service, r criconfig.Runtime) (*features.Features, error) {
if r.Type != plugins.RuntimeRuncV2 {
return nil, fmt.Errorf("introspecting OCI runtime features needs the runtime type to be %q, got %q",
plugins.RuntimeRuncV2, r.Type)
// For other runtimes, typeurl.MarshalAnyToProto will cause nil panic during typeurl dereference
}

rr := &apitypes.RuntimeRequest{
RuntimePath: r.Type, // "io.containerd.runc.v2"
RuntimePath: r.Type, // e.g. "io.containerd.runc.v2" or "io.containerd.runsc.v1"
}
if r.Path != "" {
rr.RuntimePath = r.Path // "/usr/local/bin/crun"
Expand All @@ -466,6 +460,7 @@ func introspectRuntimeFeatures(ctx context.Context, intro introspection.Service,
if err != nil {
return nil, err
}
// options is nil when the runtime has no config section; marshalling a nil interface panics in typeurl.
if options != nil {
rr.Options, err = typeurl.MarshalAnyToProto(options)
if err != nil {
Expand All @@ -477,10 +472,16 @@ func introspectRuntimeFeatures(ctx context.Context, intro introspection.Service,
if err != nil {
return nil, fmt.Errorf("failed to call PluginInfo: %w", err)
}
if infoResp.Extra == nil {
return nil, fmt.Errorf("runtime plugin info has no extra data")
}
var info apitypes.RuntimeInfo
if err := typeurl.UnmarshalTo(infoResp.Extra, &info); err != nil {
return nil, fmt.Errorf("failed to get runtime info from plugin info: %w", err)
}
if info.Features == nil {
return nil, fmt.Errorf("runtime info has no features")
}
featuresX, err := typeurl.UnmarshalAny(info.Features)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal Features (%T): %w", info.Features, err)
Expand All @@ -493,7 +494,7 @@ func introspectRuntimeFeatures(ctx context.Context, intro introspection.Service,
}

func supportsCRIUserns(f *features.Features) bool {
if f == nil {
if f == nil || f.Linux == nil {
return false
}
userns := slices.Contains(f.Linux.Namespaces, "user")
Expand Down
158 changes: 158 additions & 0 deletions internal/cri/server/service_introspect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
Copyright The containerd 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 server

import (
"context"
"fmt"
"testing"

introspectionapi "github.com/containerd/containerd/api/services/introspection/v1"
apitypes "github.com/containerd/containerd/api/types"
"github.com/containerd/typeurl/v2"
"github.com/opencontainers/runtime-spec/specs-go/features"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"

coreintrospection "github.com/containerd/containerd/v2/core/introspection"
criconfig "github.com/containerd/containerd/v2/internal/cri/config"
)

// fakeRuntimeIntrospection records the request it received and replies with a
// canned PluginInfo response, standing in for a shim's "-info" output.
type fakeRuntimeIntrospection struct {
resp *introspectionapi.PluginInfoResponse
err error
gotReq *apitypes.RuntimeRequest
}

var _ coreintrospection.Service = (*fakeRuntimeIntrospection)(nil)

func (f *fakeRuntimeIntrospection) Plugins(context.Context, ...string) (*introspectionapi.PluginsResponse, error) {
return &introspectionapi.PluginsResponse{}, nil
}

func (f *fakeRuntimeIntrospection) Server(context.Context) (*introspectionapi.ServerResponse, error) {
return &introspectionapi.ServerResponse{}, nil
}

func (f *fakeRuntimeIntrospection) PluginInfo(_ context.Context, _ string, _ string, req any) (*introspectionapi.PluginInfoResponse, error) {
rr, ok := req.(*apitypes.RuntimeRequest)
if !ok {
return nil, fmt.Errorf("unexpected request type %T", req)
}
f.gotReq = rr
return f.resp, f.err
}

// runtimeInfoResponse wraps features into the nested Any-in-Any shape that
// introspectRuntimeFeatures expects from the introspection service.
func runtimeInfoResponse(t *testing.T, feat *features.Features) *introspectionapi.PluginInfoResponse {
t.Helper()
featAny, err := typeurl.MarshalAny(feat)
require.NoError(t, err)
infoAny, err := typeurl.MarshalAny(&apitypes.RuntimeInfo{Features: typeurl.MarshalProto(featAny)})
require.NoError(t, err)
return &introspectionapi.PluginInfoResponse{Extra: typeurl.MarshalProto(infoAny)}
}

func boolPtr(b bool) *bool { return &b }

// TestIntrospectRuntimeFeaturesNonRunc verifies that runtimes other than
// io.containerd.runc.v2 are now introspected instead of being rejected, which
// is what lets shims such as containerd-shim-runsc-v1 surface OCI features.
func TestIntrospectRuntimeFeaturesNonRunc(t *testing.T) {
feat := &features.Features{MountOptions: []string{"rro"}}

for _, tc := range []struct {
name string
runtime criconfig.Runtime
wantOptions bool
}{
{
name: "runsc without options",
runtime: criconfig.Runtime{Type: "io.containerd.runsc.v1"},
},
{
// Exercises the options marshalling that the previous runc-only
// guard avoided; generic runtimes use runtimeoptions.Options.
name: "runsc with options",
runtime: criconfig.Runtime{Type: "io.containerd.runsc.v1", Options: map[string]any{"ConfigPath": "/etc/containerd/runsc.toml"}},
wantOptions: true,
},
{
name: "runc still introspected",
runtime: criconfig.Runtime{Type: "io.containerd.runc.v2"},
},
} {
t.Run(tc.name, func(t *testing.T) {
intro := &fakeRuntimeIntrospection{resp: runtimeInfoResponse(t, feat)}

got, err := introspectRuntimeFeatures(context.Background(), intro, tc.runtime)
require.NoError(t, err)
assert.Equal(t, feat.MountOptions, got.MountOptions)

require.NotNil(t, intro.gotReq)
assert.Equal(t, tc.runtime.Type, intro.gotReq.RuntimePath)
assert.Equal(t, tc.wantOptions, intro.gotReq.Options != nil)
})
}
}

// TestIntrospectRuntimeHandlerUserns checks that a non-runc handler advertises
// CRI user namespace support only when the shim reports both the user namespace
// and idmap mounts, matching kubelet's RuntimeHandlerFeatures expectations.
func TestIntrospectRuntimeHandlerUserns(t *testing.T) {
for _, tc := range []struct {
name string
feat *features.Features
want bool
}{
{
name: "userns and idmap",
feat: &features.Features{Linux: &features.Linux{
Namespaces: []string{"user"},
MountExtensions: &features.MountExtensions{IDMap: &features.IDMap{Enabled: boolPtr(true)}},
}},
want: true,
},
{
name: "userns without idmap",
feat: &features.Features{Linux: &features.Linux{Namespaces: []string{"user"}}},
want: false,
},
{
name: "features without linux block",
feat: &features.Features{MountOptions: []string{"rro"}},
want: false,
},
} {
t.Run(tc.name, func(t *testing.T) {
c := &criService{runtimeHandlers: make(map[string]*runtime.RuntimeHandler)}
intro := &fakeRuntimeIntrospection{resp: runtimeInfoResponse(t, tc.feat)}

err := c.introspectRuntimeHandler(context.Background(), intro, "runsc", criconfig.Runtime{Type: "io.containerd.runsc.v1"})
require.NoError(t, err)

h := c.runtimeHandlers["runsc"]
require.NotNil(t, h)
require.NotNil(t, h.Features)
assert.Equal(t, tc.want, h.Features.UserNamespaces)
})
}
}
12 changes: 7 additions & 5 deletions script/go-test-fuzz.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Running Go 1.18's fuzzing for 60 seconds each. While this would be too
# Running Go's fuzzing for 50,000 executions. While this would be too
# short to actually find issues, we want to make sure that these fuzzing
# tests are not fundamentally broken.
# tests are not fundamentally broken. Using an execution limit rather than a
# duration also avoids spurious failures when a duration expires
# (https://go.dev/issue/75804).

set -euo pipefail
set -x

fuzztime=60s
fuzzlimit=50000x
# keep the filename and Fuzz function name so we can run every fuzz test
# in a package separately (`go test -fuzz` only supports single fuzz function)
pkgs=$(git grep 'func Fuzz.*testing\.F' | grep -o '.*testing\.F)' | grep -v -E "vendor" | sort | uniq)
Expand All @@ -31,5 +33,5 @@ for pkg in $pkgs
do
pkg_path=$(echo $pkg | grep -o '.*/')
fuzz_name=$(echo $pkg | grep -o 'Fuzz[^(]*')
go test -fuzz=$fuzz_name ./$pkg_path -fuzztime=$fuzztime
done
go test -fuzz=$fuzz_name ./$pkg_path -fuzztime=$fuzzlimit
done
24 changes: 21 additions & 3 deletions vendor/github.com/klauspost/compress/flate/dict_decoder.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading