diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index c24fea0767..947611a68c 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -958,10 +958,11 @@ func execTransferPodman(execUser *user.User, command []string, needToTag bool) ( _ = cmdLogin.Wait() }() - cmd := exec.Command(command[0], command[1:]...) - cmd.Env = []string{"PATH=" + os.Getenv("PATH"), "TERM=" + os.Getenv("TERM")} - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout + // Use the physical path; $PWD may contain symlinks inaccessible to execUser. + cwd, err := syscall.Getwd() + if err != nil { + return nil, err + } uid, err := strconv.ParseInt(execUser.Uid, 10, 32) if err != nil { return nil, err @@ -970,7 +971,7 @@ func execTransferPodman(execUser *user.User, command []string, needToTag bool) ( if err != nil { return nil, err } - cmd.SysProcAttr = &syscall.SysProcAttr{ + sysProcAttr := &syscall.SysProcAttr{ Credential: &syscall.Credential{ Uid: uint32(uid), Gid: uint32(gid), @@ -978,11 +979,28 @@ func execTransferPodman(execUser *user.User, command []string, needToTag bool) ( NoSetGroups: false, }, } - if needToTag { - cmd.Stdout = nil - return cmd.Output() + run := func(dir string) ([]byte, error) { + cmd := exec.Command(command[0], command[1:]...) + // An explicit directory makes exec check access after changing credentials. + cmd.Dir = dir + cmd.Env = []string{"PATH=" + os.Getenv("PATH"), "TERM=" + os.Getenv("TERM")} + cmd.Stderr = os.Stderr + cmd.SysProcAttr = sysProcAttr + if needToTag { + return cmd.Output() + } + cmd.Stdout = os.Stdout + return nil, cmd.Run() } - return nil, cmd.Run() + + out, err := run(cwd) + // Preserve relative paths when possible. Only retry a failure to start; + // a command that has already run must not be executed a second time. + var pathErr *os.PathError + if cwd != "/" && errors.As(err, &pathErr) && pathErr.Op == "fork/exec" && errors.Is(err, fs.ErrPermission) { + return run("/") + } + return out, err } func getSigFilename(sigStoreDirPath string) (string, error) { diff --git a/test/system/120-load.bats b/test/system/120-load.bats index 5656cbcdbc..7d6a9ed901 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -113,12 +113,24 @@ verify_iid_and_name() { if [[ -z "$notme" ]]; then skip "To run this test, set PODMAN_ROOTLESS_USER to a safe username" fi - _sudo() { command sudo -n -u "$notme" "$@"; } + _sudo() ( + cd / + command sudo -n -u "$notme" "$@" + ) fi # If we can't sudo, we can't test. _sudo true || skip "cannot sudo to $notme" + # Verify that save, load, and tag work when the destination user cannot access the caller's working directory. + if ! is_rootless; then + local private_dir=$PODMAN_TMPDIR/scp-cwd + mkdir -m 0700 "$private_dir" + run _sudo test -x "$private_dir" + assert "$status" -eq 1 "destination user cannot access the working directory" + cd "$private_dir" + fi + # Preserve digest of original image; we will compare against it later run_podman image inspect --format '{{.RepoDigests}}' $IMAGE src_digests=$output @@ -178,6 +190,23 @@ verify_iid_and_name() { run_podman rmi foobar:123 } +@test "podman image scp transfer with relative paths" { + skip_if_remote "only applicable under local podman" + skip_if_rootless "tests local transfers between root-owned stores" + + cd "$PODMAN_TMPDIR" + local conf=registries-$(random_string).conf + printf 'unqualified-search-registries = ["example.com"]\n' > "$conf" + + # Both forms must resolve paths relative to the caller's directory. + run_podman --registries-conf "./$conf" image scp root@localhost::$IMAGE root@localhost:: + run_podman --registries-conf="./$conf" image scp root@localhost::$IMAGE root@localhost:: + run_podman --registries-conf "$PODMAN_TMPDIR/$conf" image scp root@localhost::$IMAGE root@localhost:: + + # A command failure must be returned without retrying from another directory. + run_podman 125 --registries-conf "./$conf" image scp root@localhost::example.com/no-such-image:missing root@localhost:: + assert "$output" =~ "image not known" "missing image error is preserved" +} @test "podman load - by image ID" { # FIXME: how to build a simple archive instead? diff --git a/test/tmt/system.fmf b/test/tmt/system.fmf index 89dad8de2e..d1990c29e3 100644 --- a/test/tmt/system.fmf +++ b/test/tmt/system.fmf @@ -23,12 +23,16 @@ adjust+: summary: local rootful test test: bash ./system.sh duration: 60m + require+: + - systemd-container /local-rootless: tag: [ local, rootless ] summary: rootless test test: bash ./system.sh rootless duration: 60m + require+: + - systemd-container /remote-root: tag: [ remote, root ]