Skip to content

Commit 205ae03

Browse files
authored
Merge pull request #4954 from haytok/validate-mount-destination-not-root
fix: reject `/` as the `-v` destination in nerdctl run for Docker com…
2 parents 0899187 + 4d5bb2c commit 205ae03

6 files changed

Lines changed: 101 additions & 0 deletions

File tree

cmd/nerdctl/container/container_run_mount_linux_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/containerd/nerdctl/mod/tigron/tig"
3333

3434
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
35+
"github.com/containerd/nerdctl/v2/pkg/mountutil"
3536
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
3637
"github.com/containerd/nerdctl/v2/pkg/testutil"
3738
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@@ -762,3 +763,30 @@ func TestBindMountWhenHostFolderDoesNotExist(t *testing.T) {
762763
_, err = os.Stat(hp)
763764
assert.ErrorIs(t, err, os.ErrNotExist)
764765
}
766+
767+
func TestRunVolumeWithRootDestination(t *testing.T) {
768+
testCase := nerdtest.Setup()
769+
770+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
771+
helpers.Anyhow("rm", "-f", data.Identifier())
772+
}
773+
774+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
775+
return helpers.Command("run", "-d", "--name", data.Identifier(),
776+
"-v", data.Temp().Dir()+":/", testutil.AlpineImage)
777+
}
778+
779+
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
780+
return &test.Expected{
781+
ExitCode: expect.ExitCodeGenericFail,
782+
Errors: []error{mountutil.ErrVolumeTargetIsRoot},
783+
Output: func(stdout string, t tig.T) {
784+
psOutput := helpers.Capture("ps", "-a", "--format", "{{.Names}}")
785+
assert.Assert(t, !strings.Contains(psOutput, data.Identifier()),
786+
"no container should be created when the volume destination is '/'")
787+
},
788+
}
789+
}
790+
791+
testCase.Run(t)
792+
}

cmd/nerdctl/container/container_run_mount_windows_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,21 @@
1717
package container
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"os"
23+
"strings"
2224
"testing"
2325

2426
"gotest.tools/v3/assert"
2527

28+
"github.com/containerd/nerdctl/mod/tigron/expect"
29+
"github.com/containerd/nerdctl/mod/tigron/test"
30+
"github.com/containerd/nerdctl/mod/tigron/tig"
31+
2632
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
2733
"github.com/containerd/nerdctl/v2/pkg/testutil"
34+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
2835
)
2936

3037
func TestRunMountVolume(t *testing.T) {
@@ -213,3 +220,30 @@ func TestRunMountVolumeSpec(t *testing.T) {
213220
// If -v is an empty string, it will be ignored
214221
base.Cmd("run", "--rm", "-v", "", testutil.CommonImage).AssertOK()
215222
}
223+
224+
func TestRunVolumeWithDriveRootDestination(t *testing.T) {
225+
testCase := nerdtest.Setup()
226+
227+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
228+
helpers.Anyhow("rm", "-f", data.Identifier())
229+
}
230+
231+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
232+
return helpers.Command("run", "-d", "--name", data.Identifier(),
233+
"-v", data.Temp().Dir()+`:C:\.`, testutil.CommonImage)
234+
}
235+
236+
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
237+
return &test.Expected{
238+
ExitCode: expect.ExitCodeGenericFail,
239+
Errors: []error{errors.New("destination path (c:\\\\) cannot be 'c:' or 'c:\\\\'")},
240+
Output: func(stdout string, t tig.T) {
241+
psOutput := helpers.Capture("ps", "-a", "--format", "{{.Names}}")
242+
assert.Assert(t, !strings.Contains(psOutput, data.Identifier()),
243+
"no container should be created when the volume destination is the drive root")
244+
},
245+
}
246+
}
247+
248+
testCase.Run(t)
249+
}

pkg/mountutil/mountutil_linux_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@ func TestProcessFlagV(t *testing.T) {
259259
rawSpec: `/mnt/foo:./foo`,
260260
err: "expected an absolute path, got \"./foo\"",
261261
},
262+
{
263+
rawSpec: `./:/`,
264+
err: "invalid specification: destination can't be '/'",
265+
},
262266
}
263267

264268
for _, tt := range tests {

pkg/mountutil/mountutil_unix.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,26 @@
1616
limitations under the License.
1717
*/
1818

19+
/*
20+
Portions from https://github.com/moby/moby/blob/docker-v29.5.2/daemon/volume/mounts/linux_parser.go
21+
Copyright (C) Docker/Moby authors.
22+
Licensed under the Apache License, Version 2.0
23+
NOTICE: https://github.com/moby/moby/blob/docker-v29.5.2/NOTICE
24+
*/
25+
1926
package mountutil
2027

2128
import (
29+
"errors"
2230
"fmt"
2331
"path/filepath"
2432
"strings"
2533

2634
"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore"
2735
)
2836

37+
var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")
38+
2939
func splitVolumeSpec(s string) ([]string, error) {
3040
s = strings.TrimLeft(s, ":")
3141
split := strings.Split(s, ":")
@@ -48,7 +58,17 @@ func cleanMount(p string) string {
4858
return filepath.Clean(p)
4959
}
5060

61+
func validateNotRoot(p string) error {
62+
if cleanMount(p) == "/" {
63+
return ErrVolumeTargetIsRoot
64+
}
65+
return nil
66+
}
67+
5168
func isValidPath(s string) (bool, error) {
69+
if err := validateNotRoot(s); err != nil {
70+
return false, err
71+
}
5272
if filepath.IsAbs(s) {
5373
return true, nil
5474
}

pkg/mountutil/mountutil_windows.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,18 @@ func cleanMount(p string) string {
161161
return filepath.Clean(p)
162162
}
163163

164+
func validateNotRoot(p string) error {
165+
p = strings.ToLower(cleanMount(p))
166+
if p == "c:" || p == `c:\` {
167+
return fmt.Errorf(`destination path (%v) cannot be 'c:' or 'c:\'`, p)
168+
}
169+
return nil
170+
}
171+
164172
func isValidPath(s string) (bool, error) {
173+
if err := validateNotRoot(s); err != nil {
174+
return false, err
175+
}
165176
if isNamedPipe(s) || filepath.IsAbs(s) {
166177
return true, nil
167178
}

pkg/mountutil/mountutil_windows_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ func TestProcessFlagV(t *testing.T) {
262262
rawSpec: `C:\TestVolume\Path:TestVolume`,
263263
err: "expected an absolute path or a named pipe, got \"TestVolume\"",
264264
},
265+
{
266+
rawSpec: `C:\TestVolume\Path:c:\.`,
267+
err: "destination path (c:\\) cannot be 'c:' or 'c:\\'",
268+
},
265269
}
266270

267271
for _, tt := range tests {

0 commit comments

Comments
 (0)