Due to the importance of path safety, the internal sanitisation wrappers
for volumes and containers should be exposed so other parts of Docker
can benefit from proper path sanitisation.
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> (github: cyphar)
... | ... |
@@ -211,12 +211,37 @@ func (container *Container) LogEvent(action string) { |
211 | 211 |
) |
212 | 212 |
} |
213 | 213 |
|
214 |
-func (container *Container) getResourcePath(path string) (string, error) { |
|
214 |
+// Evaluates `path` in the scope of the container's basefs, with proper path |
|
215 |
+// sanitisation. Symlinks are all scoped to the basefs of the container, as |
|
216 |
+// though the container's basefs was `/`. |
|
217 |
+// |
|
218 |
+// The basefs of a container is the host-facing path which is bind-mounted as |
|
219 |
+// `/` inside the container. This method is essentially used to access a |
|
220 |
+// particular path inside the container as though you were a process in that |
|
221 |
+// container. |
|
222 |
+// |
|
223 |
+// NOTE: The returned path is *only* safely scoped inside the container's basefs |
|
224 |
+// if no component of the returned path changes (such as a component |
|
225 |
+// symlinking to a different path) between using this method and using the |
|
226 |
+// path. See symlink.FollowSymlinkInScope for more details. |
|
227 |
+func (container *Container) GetResourcePath(path string) (string, error) { |
|
215 | 228 |
cleanPath := filepath.Join("/", path) |
216 | 229 |
return symlink.FollowSymlinkInScope(filepath.Join(container.basefs, cleanPath), container.basefs) |
217 | 230 |
} |
218 | 231 |
|
219 |
-func (container *Container) getRootResourcePath(path string) (string, error) { |
|
232 |
+// Evaluates `path` in the scope of the container's root, with proper path |
|
233 |
+// sanitisation. Symlinks are all scoped to the root of the container, as |
|
234 |
+// though the container's root was `/`. |
|
235 |
+// |
|
236 |
+// The root of a container is the host-facing configuration metadata directory. |
|
237 |
+// Only use this method to safely access the container's `container.json` or |
|
238 |
+// other metadata files. If in doubt, use container.GetResourcePath. |
|
239 |
+// |
|
240 |
+// NOTE: The returned path is *only* safely scoped inside the container's root |
|
241 |
+// if no component of the returned path changes (such as a component |
|
242 |
+// symlinking to a different path) between using this method and using the |
|
243 |
+// path. See symlink.FollowSymlinkInScope for more details. |
|
244 |
+func (container *Container) GetRootResourcePath(path string) (string, error) { |
|
220 | 245 |
cleanPath := filepath.Join("/", path) |
221 | 246 |
return symlink.FollowSymlinkInScope(filepath.Join(container.root, cleanPath), container.root) |
222 | 247 |
} |
... | ... |
@@ -515,7 +540,7 @@ func (streamConfig *StreamConfig) StderrLogPipe() io.ReadCloser { |
515 | 515 |
} |
516 | 516 |
|
517 | 517 |
func (container *Container) buildHostnameFile() error { |
518 |
- hostnamePath, err := container.getRootResourcePath("hostname") |
|
518 |
+ hostnamePath, err := container.GetRootResourcePath("hostname") |
|
519 | 519 |
if err != nil { |
520 | 520 |
return err |
521 | 521 |
} |
... | ... |
@@ -529,7 +554,7 @@ func (container *Container) buildHostnameFile() error { |
529 | 529 |
|
530 | 530 |
func (container *Container) buildHostsFiles(IP string) error { |
531 | 531 |
|
532 |
- hostsPath, err := container.getRootResourcePath("hosts") |
|
532 |
+ hostsPath, err := container.GetRootResourcePath("hosts") |
|
533 | 533 |
if err != nil { |
534 | 534 |
return err |
535 | 535 |
} |
... | ... |
@@ -895,7 +920,7 @@ func (container *Container) Unmount() error { |
895 | 895 |
} |
896 | 896 |
|
897 | 897 |
func (container *Container) logPath(name string) (string, error) { |
898 |
- return container.getRootResourcePath(fmt.Sprintf("%s-%s.log", container.ID, name)) |
|
898 |
+ return container.GetRootResourcePath(fmt.Sprintf("%s-%s.log", container.ID, name)) |
|
899 | 899 |
} |
900 | 900 |
|
901 | 901 |
func (container *Container) ReadLog(name string) (io.Reader, error) { |
... | ... |
@@ -907,11 +932,11 @@ func (container *Container) ReadLog(name string) (io.Reader, error) { |
907 | 907 |
} |
908 | 908 |
|
909 | 909 |
func (container *Container) hostConfigPath() (string, error) { |
910 |
- return container.getRootResourcePath("hostconfig.json") |
|
910 |
+ return container.GetRootResourcePath("hostconfig.json") |
|
911 | 911 |
} |
912 | 912 |
|
913 | 913 |
func (container *Container) jsonPath() (string, error) { |
914 |
- return container.getRootResourcePath("config.json") |
|
914 |
+ return container.GetRootResourcePath("config.json") |
|
915 | 915 |
} |
916 | 916 |
|
917 | 917 |
// This method must be exported to be used from the lxc template |
... | ... |
@@ -981,7 +1006,7 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { |
981 | 981 |
} |
982 | 982 |
}() |
983 | 983 |
|
984 |
- basePath, err := container.getResourcePath(resource) |
|
984 |
+ basePath, err := container.GetResourcePath(resource) |
|
985 | 985 |
if err != nil { |
986 | 986 |
return nil, err |
987 | 987 |
} |
... | ... |
@@ -1083,7 +1108,7 @@ func (container *Container) setupContainerDns() error { |
1083 | 1083 |
if err != nil { |
1084 | 1084 |
return err |
1085 | 1085 |
} |
1086 |
- container.ResolvConfPath, err = container.getRootResourcePath("resolv.conf") |
|
1086 |
+ container.ResolvConfPath, err = container.GetRootResourcePath("resolv.conf") |
|
1087 | 1087 |
if err != nil { |
1088 | 1088 |
return err |
1089 | 1089 |
} |
... | ... |
@@ -1244,7 +1269,7 @@ func (container *Container) initializeNetworking() error { |
1244 | 1244 |
return err |
1245 | 1245 |
} |
1246 | 1246 |
|
1247 |
- hostsPath, err := container.getRootResourcePath("hosts") |
|
1247 |
+ hostsPath, err := container.GetRootResourcePath("hosts") |
|
1248 | 1248 |
if err != nil { |
1249 | 1249 |
return err |
1250 | 1250 |
} |
... | ... |
@@ -1375,7 +1400,7 @@ func (container *Container) setupWorkingDirectory() error { |
1375 | 1375 |
if container.Config.WorkingDir != "" { |
1376 | 1376 |
container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) |
1377 | 1377 |
|
1378 |
- pth, err := container.getResourcePath(container.Config.WorkingDir) |
|
1378 |
+ pth, err := container.GetResourcePath(container.Config.WorkingDir) |
|
1379 | 1379 |
if err != nil { |
1380 | 1380 |
return err |
1381 | 1381 |
} |
... | ... |
@@ -47,7 +47,7 @@ func (container *Container) createVolumes() error { |
47 | 47 |
continue |
48 | 48 |
} |
49 | 49 |
|
50 |
- realPath, err := container.getResourcePath(path) |
|
50 |
+ realPath, err := container.GetResourcePath(path) |
|
51 | 51 |
if err != nil { |
52 | 52 |
return err |
53 | 53 |
} |
... | ... |
@@ -336,7 +336,7 @@ func (container *Container) mountVolumes() error { |
336 | 336 |
return fmt.Errorf("could not find volume for %s:%s, impossible to mount", source, dest) |
337 | 337 |
} |
338 | 338 |
|
339 |
- destPath, err := container.getResourcePath(dest) |
|
339 |
+ destPath, err := container.GetResourcePath(dest) |
|
340 | 340 |
if err != nil { |
341 | 341 |
return err |
342 | 342 |
} |
... | ... |
@@ -347,7 +347,7 @@ func (container *Container) mountVolumes() error { |
347 | 347 |
} |
348 | 348 |
|
349 | 349 |
for _, mnt := range container.specialMounts() { |
350 |
- destPath, err := container.getResourcePath(mnt.Destination) |
|
350 |
+ destPath, err := container.GetResourcePath(mnt.Destination) |
|
351 | 351 |
if err != nil { |
352 | 352 |
return err |
353 | 353 |
} |
... | ... |
@@ -360,7 +360,7 @@ func (container *Container) mountVolumes() error { |
360 | 360 |
|
361 | 361 |
func (container *Container) unmountVolumes() { |
362 | 362 |
for dest := range container.Volumes { |
363 |
- destPath, err := container.getResourcePath(dest) |
|
363 |
+ destPath, err := container.GetResourcePath(dest) |
|
364 | 364 |
if err != nil { |
365 | 365 |
logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) |
366 | 366 |
continue |
... | ... |
@@ -372,7 +372,7 @@ func (container *Container) unmountVolumes() { |
372 | 372 |
} |
373 | 373 |
|
374 | 374 |
for _, mnt := range container.specialMounts() { |
375 |
- destPath, err := container.getResourcePath(mnt.Destination) |
|
375 |
+ destPath, err := container.GetResourcePath(mnt.Destination) |
|
376 | 376 |
if err != nil { |
377 | 377 |
logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) |
378 | 378 |
continue |
... | ... |
@@ -114,14 +114,38 @@ func (v *Volume) FromDisk() error { |
114 | 114 |
} |
115 | 115 |
|
116 | 116 |
func (v *Volume) jsonPath() (string, error) { |
117 |
- return v.getRootResourcePath("config.json") |
|
117 |
+ return v.GetRootResourcePath("config.json") |
|
118 | 118 |
} |
119 |
-func (v *Volume) getRootResourcePath(path string) (string, error) { |
|
119 |
+ |
|
120 |
+// Evalutes `path` in the scope of the volume's root path, with proper path |
|
121 |
+// sanitisation. Symlinks are all scoped to the root of the volume, as |
|
122 |
+// though the volume's root was `/`. |
|
123 |
+// |
|
124 |
+// The volume's root path is the host-facing path of the root of the volume's |
|
125 |
+// mountpoint inside a container. |
|
126 |
+// |
|
127 |
+// NOTE: The returned path is *only* safely scoped inside the volume's root |
|
128 |
+// if no component of the returned path changes (such as a component |
|
129 |
+// symlinking to a different path) between using this method and using the |
|
130 |
+// path. See symlink.FollowSymlinkInScope for more details. |
|
131 |
+func (v *Volume) GetResourcePath(path string) (string, error) { |
|
120 | 132 |
cleanPath := filepath.Join("/", path) |
121 |
- return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath) |
|
133 |
+ return symlink.FollowSymlinkInScope(filepath.Join(v.Path, cleanPath), v.Path) |
|
122 | 134 |
} |
123 | 135 |
|
124 |
-func (v *Volume) getResourcePath(path string) (string, error) { |
|
136 |
+// Evalutes `path` in the scope of the volume's config path, with proper path |
|
137 |
+// sanitisation. Symlinks are all scoped to the root of the config path, as |
|
138 |
+// though the config path was `/`. |
|
139 |
+// |
|
140 |
+// The config path of a volume is not exposed to the container and is just used |
|
141 |
+// to store volume configuration options and other internal information. If in |
|
142 |
+// doubt, you probably want to just use v.GetResourcePath. |
|
143 |
+// |
|
144 |
+// NOTE: The returned path is *only* safely scoped inside the volume's config |
|
145 |
+// path if no component of the returned path changes (such as a component |
|
146 |
+// symlinking to a different path) between using this method and using the |
|
147 |
+// path. See symlink.FollowSymlinkInScope for more details. |
|
148 |
+func (v *Volume) GetRootResourcePath(path string) (string, error) { |
|
125 | 149 |
cleanPath := filepath.Join("/", path) |
126 |
- return symlink.FollowSymlinkInScope(filepath.Join(v.Path, cleanPath), v.Path) |
|
150 |
+ return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath) |
|
127 | 151 |
} |