package container
import (
"bytes"
"os"
"path/filepath"
"testing"
mounttypes "github.com/moby/moby/api/types/mount"
"github.com/moby/moby/client"
"github.com/moby/moby/v2/integration/internal/build"
"github.com/moby/moby/v2/integration/internal/container"
"github.com/moby/moby/v2/internal/testutil"
"github.com/moby/moby/v2/internal/testutil/fakecontext"
"gotest.tools/v3/assert"
"gotest.tools/v3/skip"
)
// TestCopyWithAbsoluteSymlinkedMountTarget was introduced as a regression test
// for https://github.com/moby/moby/issues/52653.
//
// The security fix in GHSA-vp62-88p7-qqf5 switched openContainerFS to use
// os.Root for the mount-destination operations.
// os.Root refuses to follow absolute symlinks, but distro images commonly ship
// /var/run as an absolute symlink to /run.
// As a result, any container with a bind mount whose target traversed such a
// symlink (e.g. -v /host/sock:/var/run/docker.sock) made `docker cp` fail.
func TestCopyWithAbsoluteSymlinkedMountTarget(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
ctx := setupTest(t)
apiClient := testEnv.APIClient()
// Build an image with an absolute in-container symlink along the mount
// target path.
// Stock distro images expose this shape via /var/run -> /run, but we set
// up our own /sockets -> /root pair so the test does not depend on any
// particular base image's layout.
buildCtx := fakecontext.New(t, "",
fakecontext.WithDockerfile(`FROM busybox
RUN touch /root/nil && ln -s /root /sockets
`),
)
defer buildCtx.Close()
imgID := build.Do(ctx, t, apiClient, buildCtx, client.ImageBuildOptions{})
// Use testutil.TempDir so the rootless daemon can access the bind-mount
// source: t.TempDir() creates a 0700 parent that the fake-root user
// cannot stat.
srcDir := testutil.TempDir(t)
assert.NilError(t, os.WriteFile(filepath.Join(srcDir, "sock"), nil, 0o644))
cid := container.Create(ctx, t, apiClient,
container.WithImage(imgID),
container.WithMount(mounttypes.Mount{
Type: mounttypes.TypeBind,
Source: filepath.Join(srcDir, "sock"),
Target: "/sockets/docker.sock",
}),
)
_, err := apiClient.CopyToContainer(ctx, cid, client.CopyToContainerOptions{
DestinationPath: "/sockets/",
Content: bytes.NewReader(nil),
})
assert.NilError(t, err)
}