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)
}