Skip to content

Commit ef12da2

Browse files
authored
Merge pull request containerd#9781 from kinvolk/rata/userns-use-pluginInfo
core/runtime: Check shim PluginInfo to enforce idmap support
2 parents 857dc6f + f1e265b commit ef12da2

File tree

3 files changed

+79
-94
lines changed

3 files changed

+79
-94
lines changed

cmd/containerd-shim-runc-v2/process/init.go

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ package process
2121
import (
2222
"context"
2323
"encoding/json"
24-
"errors"
2524
"fmt"
2625
"io"
2726
"os"
@@ -142,12 +141,6 @@ func (p *Init) Create(ctx context.Context, r *CreateConfig) error {
142141
opts.ConsoleSocket = socket
143142
}
144143

145-
// runc ignores silently features it doesn't know about, so for things that this is
146-
// problematic let's check if this runc version supports them.
147-
if err := p.validateRuncFeatures(ctx, r.Bundle); err != nil {
148-
return fmt.Errorf("failed to detect OCI runtime features: %w", err)
149-
}
150-
151144
if err := p.runtime.Create(ctx, r.ID, r.Bundle, opts); err != nil {
152145
return p.runtimeError(err, "OCI runtime create failed")
153146
}
@@ -181,60 +174,6 @@ func (p *Init) Create(ctx context.Context, r *CreateConfig) error {
181174
return nil
182175
}
183176

184-
func (p *Init) validateRuncFeatures(ctx context.Context, bundle string) error {
185-
// TODO: We should remove the logic from here and rebase on #8509.
186-
// This way we can avoid the call to readConfig() here and the call to p.runtime.Features()
187-
// in validateIDMapMounts().
188-
// But that PR is not yet merged nor it is clear if it will be refactored.
189-
// Do this contained hack for now.
190-
spec, err := readConfig(bundle)
191-
if err != nil {
192-
return fmt.Errorf("failed to read config: %w", err)
193-
}
194-
195-
if err := p.validateIDMapMounts(ctx, spec); err != nil {
196-
return fmt.Errorf("OCI runtime doesn't support idmap mounts: %w", err)
197-
}
198-
199-
return nil
200-
}
201-
202-
func (p *Init) validateIDMapMounts(ctx context.Context, spec *specs.Spec) error {
203-
var used bool
204-
for _, m := range spec.Mounts {
205-
if m.UIDMappings != nil || m.GIDMappings != nil {
206-
used = true
207-
break
208-
}
209-
if sliceContainsStr(m.Options, "idmap") || sliceContainsStr(m.Options, "ridmap") {
210-
used = true
211-
break
212-
}
213-
}
214-
215-
if !used {
216-
return nil
217-
}
218-
219-
// From here onwards, we require idmap mounts. So if we fail to check, we return an error.
220-
features, err := p.runtime.Features(ctx)
221-
if err != nil {
222-
// If the features command is not implemented, then runc is too old.
223-
return fmt.Errorf("features command failed: %w", err)
224-
225-
}
226-
227-
if features.Linux.MountExtensions == nil || features.Linux.MountExtensions.IDMap == nil {
228-
return errors.New("missing `mountExtensions.idmap` entry in `features` command")
229-
}
230-
231-
if enabled := features.Linux.MountExtensions.IDMap.Enabled; enabled == nil || !*enabled {
232-
return errors.New("idmap mounts not supported")
233-
}
234-
235-
return nil
236-
}
237-
238177
func (p *Init) openStdin(path string) error {
239178
sc, err := fifo.OpenFifo(context.Background(), path, unix.O_WRONLY|unix.O_NONBLOCK, 0)
240179
if err != nil {
@@ -556,12 +495,3 @@ func withConditionalIO(c stdio.Stdio) runc.IOOpt {
556495
o.OpenStderr = c.Stderr != ""
557496
}
558497
}
559-
560-
func sliceContainsStr(s []string, str string) bool {
561-
for _, s := range s {
562-
if s == str {
563-
return true
564-
}
565-
}
566-
return false
567-
}

cmd/containerd-shim-runc-v2/process/utils.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ package process
2121
import (
2222
"context"
2323
"encoding/json"
24-
"errors"
2524
"fmt"
2625
"io"
2726
"os"
@@ -32,7 +31,6 @@ import (
3231

3332
"github.com/containerd/errdefs"
3433
runc "github.com/containerd/go-runc"
35-
specs "github.com/opencontainers/runtime-spec/specs-go"
3634
"golang.org/x/sys/unix"
3735
)
3836

@@ -41,8 +39,6 @@ const (
4139
RuncRoot = "/run/containerd/runc"
4240
// InitPidFile name of the file that contains the init pid
4341
InitPidFile = "init.pid"
44-
// configFile is the name of the runc config file
45-
configFile = "config.json"
4642
)
4743

4844
// safePid is a thread safe wrapper for pid.
@@ -188,23 +184,3 @@ func stateName(v interface{}) string {
188184
}
189185
panic(fmt.Errorf("invalid state %v", v))
190186
}
191-
192-
func readConfig(path string) (spec *specs.Spec, err error) {
193-
cfg := filepath.Join(path, configFile)
194-
f, err := os.Open(cfg)
195-
if err != nil {
196-
if os.IsNotExist(err) {
197-
return nil, fmt.Errorf("JSON specification file %s not found", cfg)
198-
}
199-
return nil, err
200-
}
201-
defer f.Close()
202-
203-
if err = json.NewDecoder(f).Decode(&spec); err != nil {
204-
return nil, fmt.Errorf("failed to parse config: %w", err)
205-
}
206-
if spec == nil {
207-
return nil, errors.New("config cannot be null")
208-
}
209-
return spec, nil
210-
}

core/runtime/v2/task_manager.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,18 @@ package v2
1919
import (
2020
"bytes"
2121
"context"
22+
"errors"
2223
"fmt"
2324
"os"
2425
"os/exec"
26+
"slices"
2527

2628
"github.com/containerd/errdefs"
2729
"github.com/containerd/plugin"
2830
"github.com/containerd/plugin/registry"
31+
"github.com/containerd/typeurl/v2"
32+
"github.com/opencontainers/runtime-spec/specs-go"
33+
"github.com/opencontainers/runtime-spec/specs-go/features"
2934

3035
apitypes "github.com/containerd/containerd/api/types"
3136
"github.com/containerd/containerd/v2/core/runtime"
@@ -111,6 +116,12 @@ func (m *TaskManager) Create(ctx context.Context, taskID string, opts runtime.Cr
111116
return nil, err
112117
}
113118

119+
// runc ignores silently features it doesn't know about, so for things that this is
120+
// problematic let's check if this runc version supports them.
121+
if err := m.validateRuntimeFeatures(ctx, opts); err != nil {
122+
return nil, fmt.Errorf("failed to validate OCI runtime features: %w", err)
123+
}
124+
114125
t, err := shimTask.Create(ctx, opts)
115126
if err != nil {
116127
// NOTE: ctx contains required namespace information.
@@ -224,3 +235,71 @@ func (m *TaskManager) PluginInfo(ctx context.Context, request interface{}) (inte
224235
}
225236
return &info, nil
226237
}
238+
239+
func (m *TaskManager) validateRuntimeFeatures(ctx context.Context, opts runtime.CreateOpts) error {
240+
var spec specs.Spec
241+
if err := typeurl.UnmarshalTo(opts.Spec, &spec); err != nil {
242+
return fmt.Errorf("unmarshal spec: %w", err)
243+
}
244+
245+
// Only ask for the PluginInfo if idmap mounts are used.
246+
if !usesIDMapMounts(spec) {
247+
return nil
248+
}
249+
250+
pInfo, err := m.PluginInfo(ctx, &apitypes.RuntimeRequest{RuntimePath: opts.Runtime})
251+
if err != nil {
252+
return fmt.Errorf("runtime info: %w", err)
253+
}
254+
255+
pluginInfo, ok := pInfo.(*apitypes.RuntimeInfo)
256+
if !ok {
257+
return fmt.Errorf("invalid runtime info type: %T", pInfo)
258+
}
259+
260+
feat, err := typeurl.UnmarshalAny(pluginInfo.Features)
261+
if err != nil {
262+
return fmt.Errorf("unmarshal runtime features: %w", err)
263+
}
264+
265+
// runc-compatible runtimes silently ignores features it doesn't know about. But ignoring
266+
// our request to use idmap mounts can break permissions in the volume, so let's make sure
267+
// it supports it. For more info, see:
268+
// https://github.com/opencontainers/runtime-spec/pull/1219
269+
//
270+
features, ok := feat.(*features.Features)
271+
if !ok {
272+
// Leave alone non runc-compatible runtimes that don't provide the features info,
273+
// they might not be affected by this.
274+
return nil
275+
}
276+
277+
if err := supportsIDMapMounts(features); err != nil {
278+
return fmt.Errorf("idmap mounts not supported: %w", err)
279+
}
280+
281+
return nil
282+
}
283+
284+
func usesIDMapMounts(spec specs.Spec) bool {
285+
for _, m := range spec.Mounts {
286+
if m.UIDMappings != nil || m.GIDMappings != nil {
287+
return true
288+
}
289+
if slices.Contains(m.Options, "idmap") || slices.Contains(m.Options, "ridmap") {
290+
return true
291+
}
292+
293+
}
294+
return false
295+
}
296+
297+
func supportsIDMapMounts(features *features.Features) error {
298+
if features.Linux.MountExtensions == nil || features.Linux.MountExtensions.IDMap == nil {
299+
return errors.New("missing `mountExtensions.idmap` entry in `features` command")
300+
}
301+
if enabled := features.Linux.MountExtensions.IDMap.Enabled; enabled == nil || !*enabled {
302+
return errors.New("entry `mountExtensions.idmap.Enabled` in `features` command not present or disabled")
303+
}
304+
return nil
305+
}

0 commit comments

Comments
 (0)