Skip to content

Commit 244aa64

Browse files
Merge pull request #27672 from Luap99/workdir
libpod: fix workdir MkdirAll() all check
2 parents f5ea6f1 + d18e44e commit 244aa64

File tree

2 files changed

+45
-49
lines changed

2 files changed

+45
-49
lines changed

libpod/container_internal_common.go

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -847,51 +847,6 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc
847847
return g.Config, cleanupFunc, nil
848848
}
849849

850-
// isWorkDirSymlink returns true if resolved workdir is symlink or a chain of symlinks,
851-
// and final resolved target is present either on volume, mount or inside of container
852-
// otherwise it returns false. Following function is meant for internal use only and
853-
// can change at any point of time.
854-
func (c *Container) isWorkDirSymlink(resolvedPath string) bool {
855-
// We cannot create workdir since explicit --workdir is
856-
// set in config but workdir could also be a symlink.
857-
// If it's a symlink, check if the resolved target is present in the container.
858-
// If so, that's a valid use case: return nil.
859-
860-
maxSymLinks := 0
861-
// Linux only supports a chain of 40 links.
862-
// Reference: https://github.com/torvalds/linux/blob/master/include/linux/namei.h#L13
863-
for maxSymLinks <= 40 {
864-
resolvedSymlink, err := os.Readlink(resolvedPath)
865-
if err != nil {
866-
// End sym-link resolution loop.
867-
break
868-
}
869-
if resolvedSymlink != "" {
870-
_, resolvedSymlinkWorkdir, _, err := c.resolvePath(c.state.Mountpoint, resolvedSymlink)
871-
if isPathOnVolume(c, resolvedSymlinkWorkdir) || isPathOnMount(c, resolvedSymlinkWorkdir) {
872-
// Resolved symlink exists on external volume or mount
873-
return true
874-
}
875-
if err != nil {
876-
// Could not resolve path so end sym-link resolution loop.
877-
break
878-
}
879-
if resolvedSymlinkWorkdir != "" {
880-
resolvedPath = resolvedSymlinkWorkdir
881-
err := fileutils.Exists(resolvedSymlinkWorkdir)
882-
if err == nil {
883-
// Symlink resolved successfully and resolved path exists on container,
884-
// this is a valid use-case so return nil.
885-
logrus.Debugf("Workdir is a symlink with target to %q and resolved symlink exists on container", resolvedSymlink)
886-
return true
887-
}
888-
}
889-
}
890-
maxSymLinks++
891-
}
892-
return false
893-
}
894-
895850
// resolveWorkDir resolves the container's workdir and, depending on the
896851
// configuration, will create it, or error out if it does not exist.
897852
// Note that the container must be mounted before.
@@ -906,7 +861,7 @@ func (c *Container) resolveWorkDir() error {
906861
return nil
907862
}
908863

909-
_, resolvedWorkdir, _, err := c.resolvePath(c.state.Mountpoint, workdir)
864+
resolvedWorkdir, err := securejoin.SecureJoin(c.state.Mountpoint, workdir)
910865
if err != nil {
911866
return err
912867
}
@@ -923,18 +878,27 @@ func (c *Container) resolveWorkDir() error {
923878
// No need to create it (e.g., `--workdir=/foo`), so let's make sure
924879
// the path exists on the container.
925880
if errors.Is(err, os.ErrNotExist) {
926-
// If resolved Workdir path gets marked as a valid symlink,
927-
// return nil cause this is valid use-case.
928-
if c.isWorkDirSymlink(resolvedWorkdir) {
881+
// Check if path is a symlink, securejoin resolves and follows the links
882+
// so the path will be different from the normal join if it is one.
883+
if resolvedWorkdir != filepath.Join(c.state.Mountpoint, workdir) {
884+
// Path must be a symlink to non existing directory.
885+
// It could point to mounts that are only created later so that make
886+
// an assumption here and let's just continue and let the oci runtime
887+
// do its job.
929888
return nil
930889
}
890+
// If they are the same we know there is no symlink/relative path involved.
891+
// We can return a nicer error message without having to go through the OCI runtime.
931892
return fmt.Errorf("workdir %q does not exist on container %s", workdir, c.ID())
932893
}
933894
// This might be a serious error (e.g., permission), so
934895
// we need to return the full error.
935896
return fmt.Errorf("detecting workdir %q on container %s: %w", workdir, c.ID(), err)
936897
}
937898
if err := os.MkdirAll(resolvedWorkdir, 0o755); err != nil {
899+
if errors.Is(err, fs.ErrExist) {
900+
return nil
901+
}
938902
return fmt.Errorf("creating container %s workdir: %w", c.ID(), err)
939903
}
940904

test/e2e/run_working_dir_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
. "github.com/containers/podman/v6/test/utils"
1111
. "github.com/onsi/ginkgo/v2"
1212
. "github.com/onsi/gomega"
13+
"github.com/onsi/gomega/types"
1314
)
1415

1516
var _ = Describe("Podman run", func() {
@@ -56,4 +57,35 @@ WORKDIR /etc/foobar`, ALPINE)
5657
Expect(session).Should(ExitCleanly())
5758
Expect(session.OutputToString()).To(Equal("/home/foobar"))
5859
})
60+
61+
It("podman run on an image with a symlinked workdir", func() {
62+
dockerfile := fmt.Sprintf(`FROM %s
63+
RUN mkdir /A && ln -s /A /B && ln -s /vol/test /link
64+
WORKDIR /B`, ALPINE)
65+
podmanTest.BuildImage(dockerfile, "test", "false")
66+
67+
session := podmanTest.PodmanExitCleanly("run", "test", "pwd")
68+
Expect(session.OutputToString()).To(Equal("/A"))
69+
70+
path := filepath.Join(podmanTest.TempDir, "test")
71+
err := os.Mkdir(path, 0o755)
72+
Expect(err).ToNot(HaveOccurred())
73+
74+
session = podmanTest.PodmanExitCleanly("run", "--workdir=/link", "--volume", podmanTest.TempDir+":/vol", "test", "pwd")
75+
Expect(session.OutputToString()).To(Equal("/vol/test"))
76+
77+
// This will fail in the runtime since the target doesn't exists
78+
session = podmanTest.Podman([]string{"run", "--workdir=/link", "test", "pwd"})
79+
session.WaitWithDefaultTimeout()
80+
var matcher types.GomegaMatcher
81+
if filepath.Base(podmanTest.OCIRuntime) == "crun" {
82+
matcher = ExitWithError(127, "chdir to `/link`: No such file or directory")
83+
} else if filepath.Base(podmanTest.OCIRuntime) == "runc" {
84+
matcher = ExitWithError(126, "mkdir /link: file exists")
85+
} else {
86+
// unknown runtime, just check it failed
87+
matcher = Not(ExitCleanly())
88+
}
89+
Expect(session).Should(matcher)
90+
})
5991
})

0 commit comments

Comments
 (0)