Skip to content

Commit f781267

Browse files
committed
exec: add extra validation for submount sources
While submount paths were already validated there are some cases where the parent mount may not be immutable while the submount is created. Signed-off-by: Tonis Tiigi <[email protected]> (cherry picked from commit 2529ec4121bcd8c35bcd96218083da175c2e5b77) (cherry picked from commit cbc233b3b695918d92fd5b1407b829296c53db70)
1 parent d089e0b commit f781267

File tree

6 files changed

+159
-34
lines changed

6 files changed

+159
-34
lines changed

executor/oci/spec.go

+18-12
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/containerd/containerd/namespaces"
1313
"github.com/containerd/containerd/oci"
1414
"github.com/containerd/containerd/pkg/userns"
15-
"github.com/containerd/continuity/fs"
1615
"github.com/docker/docker/pkg/idtools"
1716
"github.com/mitchellh/hashstructure/v2"
1817
"github.com/moby/buildkit/executor"
@@ -215,6 +214,7 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou
215214
type mountRef struct {
216215
mount mount.Mount
217216
unmount func() error
217+
subRefs map[string]mountRef
218218
}
219219

220220
type submounts struct {
@@ -233,10 +233,17 @@ func (s *submounts) subMount(m mount.Mount, subPath string) (mount.Mount, error)
233233
return mount.Mount{}, err
234234
}
235235
if mr, ok := s.m[h]; ok {
236-
sm, err := sub(mr.mount, subPath)
236+
if sm, ok := mr.subRefs[subPath]; ok {
237+
return sm.mount, nil
238+
}
239+
sm, unmount, err := sub(mr.mount, subPath)
237240
if err != nil {
238241
return mount.Mount{}, err
239242
}
243+
mr.subRefs[subPath] = mountRef{
244+
mount: sm,
245+
unmount: unmount,
246+
}
240247
return sm, nil
241248
}
242249

@@ -261,12 +268,17 @@ func (s *submounts) subMount(m mount.Mount, subPath string) (mount.Mount, error)
261268
Options: opts,
262269
},
263270
unmount: lm.Unmount,
271+
subRefs: map[string]mountRef{},
264272
}
265273

266-
sm, err := sub(s.m[h].mount, subPath)
274+
sm, unmount, err := sub(s.m[h].mount, subPath)
267275
if err != nil {
268276
return mount.Mount{}, err
269277
}
278+
s.m[h].subRefs[subPath] = mountRef{
279+
mount: sm,
280+
unmount: unmount,
281+
}
270282
return sm, nil
271283
}
272284

@@ -276,6 +288,9 @@ func (s *submounts) cleanup() {
276288
for _, m := range s.m {
277289
func(m mountRef) {
278290
go func() {
291+
for _, sm := range m.subRefs {
292+
sm.unmount()
293+
}
279294
m.unmount()
280295
wg.Done()
281296
}()
@@ -284,15 +299,6 @@ func (s *submounts) cleanup() {
284299
wg.Wait()
285300
}
286301

287-
func sub(m mount.Mount, subPath string) (mount.Mount, error) {
288-
src, err := fs.RootPath(m.Source, subPath)
289-
if err != nil {
290-
return mount.Mount{}, err
291-
}
292-
m.Source = src
293-
return m, nil
294-
}
295-
296302
func specMapping(s []idtools.IDMap) []specs.LinuxIDMapping {
297303
var ids []specs.LinuxIDMapping
298304
for _, item := range s {

executor/oci/spec_freebsd.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package oci
2+
3+
import (
4+
"github.com/containerd/containerd/mount"
5+
"github.com/containerd/continuity/fs"
6+
)
7+
8+
func sub(m mount.Mount, subPath string) (mount.Mount, func() error, error) {
9+
src, err := fs.RootPath(m.Source, subPath)
10+
if err != nil {
11+
return mount.Mount{}, nil, err
12+
}
13+
m.Source = src
14+
return m, func() error { return nil }, nil
15+
}

executor/oci/spec_linux.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//go:build linux
2+
// +build linux
3+
4+
package oci
5+
6+
import (
7+
"os"
8+
"strconv"
9+
10+
"github.com/containerd/containerd/mount"
11+
"github.com/containerd/continuity/fs"
12+
"github.com/moby/buildkit/snapshot"
13+
"github.com/pkg/errors"
14+
"golang.org/x/sys/unix"
15+
)
16+
17+
func sub(m mount.Mount, subPath string) (mount.Mount, func() error, error) {
18+
var retries = 10
19+
root := m.Source
20+
for {
21+
src, err := fs.RootPath(root, subPath)
22+
if err != nil {
23+
return mount.Mount{}, nil, err
24+
}
25+
// similar to runc.WithProcfd
26+
fh, err := os.OpenFile(src, unix.O_PATH|unix.O_CLOEXEC, 0)
27+
if err != nil {
28+
return mount.Mount{}, nil, err
29+
}
30+
31+
fdPath := "/proc/self/fd/" + strconv.Itoa(int(fh.Fd()))
32+
if resolved, err := os.Readlink(fdPath); err != nil {
33+
fh.Close()
34+
return mount.Mount{}, nil, err
35+
} else if resolved != src {
36+
retries--
37+
if retries <= 0 {
38+
fh.Close()
39+
return mount.Mount{}, nil, errors.Errorf("unable to safely resolve subpath %s", subPath)
40+
}
41+
fh.Close()
42+
continue
43+
}
44+
45+
m.Source = fdPath
46+
lm := snapshot.LocalMounterWithMounts([]mount.Mount{m}, snapshot.ForceRemount())
47+
mp, err := lm.Mount()
48+
if err != nil {
49+
fh.Close()
50+
return mount.Mount{}, nil, err
51+
}
52+
m.Source = mp
53+
fh.Close() // release the fd, we don't need it anymore
54+
55+
return m, lm.Unmount, nil
56+
}
57+
}

executor/oci/spec_windows.go

+11
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"fmt"
88
"path/filepath"
99

10+
"github.com/containerd/containerd/mount"
1011
"github.com/containerd/containerd/oci"
12+
"github.com/containerd/continuity/fs"
1113
"github.com/docker/docker/pkg/idtools"
1214
"github.com/moby/buildkit/solver/pb"
1315
specs "github.com/opencontainers/runtime-spec/specs-go"
@@ -67,3 +69,12 @@ func getTracingSocket() string {
6769
func cgroupV2NamespaceSupported() bool {
6870
return false
6971
}
72+
73+
func sub(m mount.Mount, subPath string) (mount.Mount, func() error, error) {
74+
src, err := fs.RootPath(m.Source, subPath)
75+
if err != nil {
76+
return mount.Mount{}, nil, err
77+
}
78+
m.Source = src
79+
return m, func() error { return nil }, nil
80+
}

snapshot/localmounter.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,39 @@ type Mounter interface {
1111
Unmount() error
1212
}
1313

14+
type LocalMounterOpt func(*localMounter)
15+
1416
// LocalMounter is a helper for mounting mountfactory to temporary path. In
1517
// addition it can mount binds without privileges
16-
func LocalMounter(mountable Mountable) Mounter {
17-
return &localMounter{mountable: mountable}
18+
func LocalMounter(mountable Mountable, opts ...LocalMounterOpt) Mounter {
19+
lm := &localMounter{mountable: mountable}
20+
for _, opt := range opts {
21+
opt(lm)
22+
}
23+
return lm
1824
}
1925

2026
// LocalMounterWithMounts is a helper for mounting to temporary path. In
2127
// addition it can mount binds without privileges
22-
func LocalMounterWithMounts(mounts []mount.Mount) Mounter {
23-
return &localMounter{mounts: mounts}
28+
func LocalMounterWithMounts(mounts []mount.Mount, opts ...LocalMounterOpt) Mounter {
29+
lm := &localMounter{mounts: mounts}
30+
for _, opt := range opts {
31+
opt(lm)
32+
}
33+
return lm
2434
}
2535

2636
type localMounter struct {
27-
mu sync.Mutex
28-
mounts []mount.Mount
29-
mountable Mountable
30-
target string
31-
release func() error
37+
mu sync.Mutex
38+
mounts []mount.Mount
39+
mountable Mountable
40+
target string
41+
release func() error
42+
forceRemount bool
43+
}
44+
45+
func ForceRemount() LocalMounterOpt {
46+
return func(lm *localMounter) {
47+
lm.forceRemount = true
48+
}
3249
}

snapshot/localmounter_unix.go

+32-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package snapshot
55

66
import (
77
"os"
8+
"path/filepath"
89
"syscall"
910

1011
"github.com/containerd/containerd/mount"
@@ -34,30 +35,48 @@ func (lm *localMounter) Mount() (string, error) {
3435
}
3536
}
3637

38+
var isFile bool
3739
if len(lm.mounts) == 1 && (lm.mounts[0].Type == "bind" || lm.mounts[0].Type == "rbind") {
38-
ro := false
39-
for _, opt := range lm.mounts[0].Options {
40-
if opt == "ro" {
41-
ro = true
42-
break
40+
if !lm.forceRemount {
41+
ro := false
42+
for _, opt := range lm.mounts[0].Options {
43+
if opt == "ro" {
44+
ro = true
45+
break
46+
}
4347
}
48+
if !ro {
49+
return lm.mounts[0].Source, nil
50+
}
51+
}
52+
fi, err := os.Stat(lm.mounts[0].Source)
53+
if err != nil {
54+
return "", err
4455
}
45-
if !ro {
46-
return lm.mounts[0].Source, nil
56+
if !fi.IsDir() {
57+
isFile = true
4758
}
4859
}
4960

50-
dir, err := os.MkdirTemp("", "buildkit-mount")
61+
dest, err := os.MkdirTemp("", "buildkit-mount")
5162
if err != nil {
5263
return "", errors.Wrap(err, "failed to create temp dir")
5364
}
5465

55-
if err := mount.All(lm.mounts, dir); err != nil {
56-
os.RemoveAll(dir)
57-
return "", errors.Wrapf(err, "failed to mount %s: %+v", dir, lm.mounts)
66+
if isFile {
67+
dest = filepath.Join(dest, "file")
68+
if err := os.WriteFile(dest, []byte{}, 0644); err != nil {
69+
os.RemoveAll(dest)
70+
return "", errors.Wrap(err, "failed to create temp file")
71+
}
72+
}
73+
74+
if err := mount.All(lm.mounts, dest); err != nil {
75+
os.RemoveAll(dest)
76+
return "", errors.Wrapf(err, "failed to mount %s: %+v", dest, lm.mounts)
5877
}
59-
lm.target = dir
60-
return dir, nil
78+
lm.target = dest
79+
return dest, nil
6180
}
6281

6382
func (lm *localMounter) Unmount() error {

0 commit comments

Comments
 (0)