Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fd2633a

Browse files
calaveraTibor Vass
authored andcommitted
Move volume name validation to the local driver.
Delegate validation tasks to the volume drivers. It's up to them to decide whether a name is valid or not. Restrict volume names for the local driver to prevent creating mount points outside docker's volumes directory. Signed-off-by: David Calavera <[email protected]>
1 parent b576f54 commit fd2633a

7 files changed

Lines changed: 82 additions & 20 deletions

File tree

daemon/daemon.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"io/ioutil"
1313
"os"
1414
"path/filepath"
15-
"regexp"
1615
"runtime"
1716
"strings"
1817
"sync"
@@ -50,15 +49,16 @@ import (
5049
"github.com/docker/docker/pkg/truncindex"
5150
"github.com/docker/docker/registry"
5251
"github.com/docker/docker/runconfig"
52+
"github.com/docker/docker/utils"
5353
volumedrivers "github.com/docker/docker/volume/drivers"
5454
"github.com/docker/docker/volume/local"
5555
"github.com/docker/docker/volume/store"
5656
"github.com/docker/libnetwork"
5757
)
5858

5959
var (
60-
validContainerNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
61-
validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`)
60+
validContainerNameChars = utils.RestrictedNameChars
61+
validContainerNamePattern = utils.RestrictedNamePattern
6262

6363
errSystemNotSupported = errors.New("The Docker daemon is not supported on this platform.")
6464
)

daemon/volumes_linux_unit_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ func TestParseBindMount(t *testing.T) {
2424
{"name:/tmp:ro", "local", "/tmp", "", "name", "local", false, false},
2525
{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", true, false},
2626
{"/tmp:tmp", "", "", "", "", "", true, true},
27-
{"./name:/tmp", "", "", "", "", "", true, true},
28-
{"../name:/tmp", "", "", "", "", "", true, true},
29-
{"./:/tmp", "", "", "", "", "", true, true},
30-
{"../:/tmp", "", "", "", "", "", true, true},
3127
}
3228

3329
for _, c := range cases {

daemon/volumes_unix.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"io/ioutil"
77
"os"
88
"path/filepath"
9-
"regexp"
109
"sort"
1110
"strings"
1211

@@ -103,12 +102,6 @@ func parseBindMount(spec, volumeDriver string) (*mountPoint, error) {
103102
}
104103

105104
if len(source) == 0 {
106-
//validate the name of named volume
107-
nameRegex := regexp.MustCompile(`(^.+[^0-9A-Za-z_]+$)|(/)`)
108-
if nameRegex.MatchString(name) {
109-
return nil, derr.ErrorCodeVolumeName.WithArgs(name)
110-
}
111-
112105
bind.Driver = volumeDriver
113106
if len(bind.Driver) == 0 {
114107
bind.Driver = volume.DefaultDriverName

errors/daemon.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -387,10 +387,10 @@ var (
387387

388388
// ErrorCodeVolumeName is generated when the name of named volume isn't valid.
389389
ErrorCodeVolumeName = errcode.Register(errGroup, errcode.ErrorDescriptor{
390-
Value: "VOLUMENAME",
391-
Message: "%s looks like a relative path, but it's taken as a name. And it is not a valid name.",
392-
Description: "The name of named volume is invalid",
393-
HTTPStatusCode: http.StatusInternalServerError,
390+
Value: "VOLUME_NAME_INVALID",
391+
Message: "%s includes invalid characters for a local volume name, only %s are allowed",
392+
Description: "The name of volume is invalid",
393+
HTTPStatusCode: http.StatusBadRequest,
394394
})
395395

396396
// ErrorCodeVolumeFromBlank is generated when path to a volume is blank.

utils/names.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package utils
2+
3+
import "regexp"
4+
5+
// RestrictedNameChars collects the characters allowed to represent a name, normally used to validate container and volume names.
6+
const RestrictedNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
7+
8+
// RestrictedNamePattern is a regular expression to validate names against the collection of restricted characters.
9+
var RestrictedNamePattern = regexp.MustCompile(`^/?` + RestrictedNameChars + `+$`)

volume/local/local.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import (
1111
"path/filepath"
1212
"sync"
1313

14+
derr "github.com/docker/docker/errors"
1415
"github.com/docker/docker/pkg/idtools"
16+
"github.com/docker/docker/utils"
1517
"github.com/docker/docker/volume"
1618
)
1719

@@ -23,8 +25,14 @@ const (
2325
volumesPathName = "volumes"
2426
)
2527

26-
// ErrNotFound is the typed error returned when the requested volume name can't be found
27-
var ErrNotFound = errors.New("volume not found")
28+
var (
29+
// ErrNotFound is the typed error returned when the requested volume name can't be found
30+
ErrNotFound = errors.New("volume not found")
31+
// volumeNameRegex ensures the name asigned for the volume is valid.
32+
// This name is used to create the bind directory, so we need to avoid characters that
33+
// would make the path to escape the root directory.
34+
volumeNameRegex = utils.RestrictedNamePattern
35+
)
2836

2937
// New instantiates a new Root instance with the provided scope. Scope
3038
// is the base path that the Root instance uses to store its
@@ -96,6 +104,10 @@ func (r *Root) Name() string {
96104
// the underlying directory tree required for this volume in the
97105
// process.
98106
func (r *Root) Create(name string, _ map[string]string) (volume.Volume, error) {
107+
if err := r.validateName(name); err != nil {
108+
return nil, err
109+
}
110+
99111
r.m.Lock()
100112
defer r.m.Unlock()
101113

@@ -174,6 +186,13 @@ func (r *Root) Get(name string) (volume.Volume, error) {
174186
return v, nil
175187
}
176188

189+
func (r *Root) validateName(name string) error {
190+
if !volumeNameRegex.MatchString(name) {
191+
return derr.ErrorCodeVolumeName.WithArgs(name, utils.RestrictedNameChars)
192+
}
193+
return nil
194+
}
195+
177196
// localVolume implements the Volume interface from the volume package and
178197
// represents the volumes created by Root.
179198
type localVolume struct {

volume/local/local_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,48 @@ func TestInitializeWithVolumes(t *testing.T) {
7979
t.Fatal("expected to re-initialize root with existing volumes")
8080
}
8181
}
82+
83+
func TestCreate(t *testing.T) {
84+
rootDir, err := ioutil.TempDir("", "local-volume-test")
85+
if err != nil {
86+
t.Fatal(err)
87+
}
88+
defer os.RemoveAll(rootDir)
89+
90+
r, err := New(rootDir, 0, 0)
91+
if err != nil {
92+
t.Fatal(err)
93+
}
94+
95+
cases := map[string]bool{
96+
"name": true,
97+
"name-with-dash": true,
98+
"name_with_underscore": true,
99+
"name/with/slash": false,
100+
"name/with/../../slash": false,
101+
"./name": false,
102+
"../name": false,
103+
"./": false,
104+
"../": false,
105+
"~": false,
106+
".": false,
107+
"..": false,
108+
"...": false,
109+
}
110+
111+
for name, success := range cases {
112+
v, err := r.Create(name, nil)
113+
if success {
114+
if err != nil {
115+
t.Fatal(err)
116+
}
117+
if v.Name() != name {
118+
t.Fatalf("Expected volume with name %s, got %s", name, v.Name())
119+
}
120+
} else {
121+
if err == nil {
122+
t.Fatalf("Expected error creating volume with name %s, got nil", name)
123+
}
124+
}
125+
}
126+
}

0 commit comments

Comments
 (0)