diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index 4db5b4d62f..7e74070fa5 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -1106,7 +1106,16 @@ func killProcessDirectly(container *container.Container) error { } func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []*configs.Device, err error) { - device, err := devices.DeviceFromPath(deviceMapping.PathOnHost, deviceMapping.CgroupPermissions) + resolvedPathOnHost := deviceMapping.PathOnHost + + // check if it is a symbolic link + if src, e := os.Lstat(deviceMapping.PathOnHost); e == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink { + if linkedPathOnHost, e := os.Readlink(deviceMapping.PathOnHost); e == nil { + resolvedPathOnHost = linkedPathOnHost + } + } + + device, err := devices.DeviceFromPath(resolvedPathOnHost, deviceMapping.CgroupPermissions) // if there was no error, return the device if err == nil { device.Path = deviceMapping.PathInContainer @@ -1118,10 +1127,10 @@ func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []*con if err == devices.ErrNotADevice { // check if it is a directory - if src, e := os.Stat(deviceMapping.PathOnHost); e == nil && src.IsDir() { + if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() { // mount the internal devices recursively - filepath.Walk(deviceMapping.PathOnHost, func(dpath string, f os.FileInfo, e error) error { + filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, e error) error { childDevice, e := devices.DeviceFromPath(dpath, deviceMapping.CgroupPermissions) if e != nil { // ignore the device @@ -1129,7 +1138,7 @@ func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []*con } // add the device to userSpecified devices - childDevice.Path = strings.Replace(dpath, deviceMapping.PathOnHost, deviceMapping.PathInContainer, 1) + childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, deviceMapping.PathInContainer, 1) devs = append(devs, childDevice) return nil diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 974249e504..173f6b5bbd 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -919,3 +919,40 @@ func (s *DockerSuite) TestRunSeccompWithDefaultProfile(c *check.C) { c.Assert(err, checker.NotNil, check.Commentf(out)) c.Assert(strings.TrimSpace(out), checker.Equals, "unshare: unshare failed: Operation not permitted") } + +// TestRunDeviceSymlink checks run with device that follows symlink (#13840) +func (s *DockerSuite) TestRunDeviceSymlink(c *check.C) { + testRequires(c, DaemonIsLinux, NotUserNamespace, NotArm, SameHostDaemon) + if _, err := os.Stat("/dev/zero"); err != nil { + c.Skip("Host does not have /dev/zero") + } + + // Create a temporary directory to create symlink + tmpDir, err := ioutil.TempDir("", "docker_device_follow_symlink_tests") + c.Assert(err, checker.IsNil) + + defer os.RemoveAll(tmpDir) + + // Create a symbolic link to /dev/zero + symZero := filepath.Join(tmpDir, "zero") + err = os.Symlink("/dev/zero", symZero) + c.Assert(err, checker.IsNil) + + // Create a temporary file "temp" inside tmpDir, write some data to "tmpDir/temp", + // then create a symlink "tmpDir/file" to the temporary file "tmpDir/temp". + tmpFile := filepath.Join(tmpDir, "temp") + err = ioutil.WriteFile(tmpFile, []byte("temp"), 0666) + c.Assert(err, checker.IsNil) + symFile := filepath.Join(tmpDir, "file") + err = os.Symlink(tmpFile, symFile) + c.Assert(err, checker.IsNil) + + // md5sum of 'dd if=/dev/zero bs=4K count=8' is bb7df04e1b0a2570657527a7e108ae23 + out, _ := dockerCmd(c, "run", "--device", symZero+":/dev/symzero", "busybox", "sh", "-c", "dd if=/dev/symzero bs=4K count=8 | md5sum") + c.Assert(strings.Trim(out, "\r\n"), checker.Contains, "bb7df04e1b0a2570657527a7e108ae23", check.Commentf("expected output bb7df04e1b0a2570657527a7e108ae23")) + + // symlink "tmpDir/file" to a file "tmpDir/temp" will result in an error as it is not a device. + out, _, err = dockerCmdWithError("run", "--device", symFile+":/dev/symzero", "busybox", "sh", "-c", "dd if=/dev/symzero bs=4K count=8 | md5sum") + c.Assert(err, check.NotNil) + c.Assert(strings.Trim(out, "\r\n"), checker.Contains, "not a device node", check.Commentf("expected output 'not a device node'")) +}