podman/0001-vendor-bump-buildah-to-1.37.6-CVE-2024-11218.patch

2194 lines
88 KiB
Diff

From bf768e9a1fd103bb84503bcec4187b9e7cff10bd Mon Sep 17 00:00:00 2001
From: Danish Prakash <contact@danishpraka.sh>
Date: Mon, 17 Feb 2025 14:21:30 +0530
Subject: [PATCH] vendor: bump buildah to 1.37.6; CVE-2024-11218
Fix a time-of-check/time-of-use error when mounting type=bind and
type=cache directories that use a "src" flag. A hostile writer could
use a concurrently-running stage or build to replace that "src" location
between the point when we had resolved possible symbolic links and when
runc/crun/whatever actually went to create the bind mount
(CVE-2024-11218).
Stop ignoring the "src" option for cache mounts when there's no "from"
option.
Fixes: CVE-2024-11218 (bsc#1236270)
Signed-off-by: Danish Prakash <contact@danishpraka.sh>
---
go.mod | 2 +-
go.sum | 4 +-
.../github.com/containers/buildah/.cirrus.yml | 2 +-
.../containers/buildah/CHANGELOG.md | 20 +-
.../containers/buildah/changelog.txt | 19 +-
.../containers/buildah/define/types.go | 42 +-
.../buildah/imagebuildah/stage_executor.go | 25 +-
.../containers/buildah/internal/open/open.go | 39 ++
.../buildah/internal/open/open_linux.go | 88 ++++
.../buildah/internal/open/open_types.go | 28 ++
.../buildah/internal/open/open_unix.go | 168 +++++++
.../buildah/internal/open/open_unsupported.go | 7 +
.../containers/buildah/internal/types.go | 8 +-
.../buildah/internal/volumes/bind_linux.go | 102 +++++
.../buildah/internal/volumes/bind_notlinux.go | 15 +
.../buildah/internal/volumes/volumes.go | 428 +++++++++++++-----
.../containers/buildah/pkg/overlay/overlay.go | 13 +
.../buildah/pkg/overlay/overlay_linux.go | 16 +
vendor/github.com/containers/buildah/run.go | 12 +-
.../containers/buildah/run_common.go | 174 ++++---
.../containers/buildah/run_freebsd.go | 11 +-
.../containers/buildah/run_linux.go | 40 +-
vendor/modules.txt | 3 +-
23 files changed, 1031 insertions(+), 235 deletions(-)
create mode 100644 vendor/github.com/containers/buildah/internal/open/open.go
create mode 100644 vendor/github.com/containers/buildah/internal/open/open_linux.go
create mode 100644 vendor/github.com/containers/buildah/internal/open/open_types.go
create mode 100644 vendor/github.com/containers/buildah/internal/open/open_unix.go
create mode 100644 vendor/github.com/containers/buildah/internal/open/open_unsupported.go
create mode 100644 vendor/github.com/containers/buildah/internal/volumes/bind_linux.go
create mode 100644 vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go
diff --git a/go.mod b/go.mod
index 3081783c8c25..9624f7f694e1 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
github.com/checkpoint-restore/checkpointctl v1.2.1
github.com/checkpoint-restore/go-criu/v7 v7.1.0
github.com/containernetworking/plugins v1.5.1
- github.com/containers/buildah v1.37.5
+ github.com/containers/buildah v1.37.6
github.com/containers/common v0.60.4
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/gvisor-tap-vsock v0.7.4
diff --git a/go.sum b/go.sum
index 9effccfb855f..c422ae9f52e2 100644
--- a/go.sum
+++ b/go.sum
@@ -77,8 +77,8 @@ github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8F
github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M=
github.com/containernetworking/plugins v1.5.1 h1:T5ji+LPYjjgW0QM+KyrigZbLsZ8jaX+E5J/EcKOE4gQ=
github.com/containernetworking/plugins v1.5.1/go.mod h1:MIQfgMayGuHYs0XdNudf31cLLAC+i242hNm6KuDGqCM=
-github.com/containers/buildah v1.37.5 h1:oJ+cVbtgxB3ZHux4No9rKbWfcd7uoDpk8r7wcbm+Vbo=
-github.com/containers/buildah v1.37.5/go.mod h1:kiNTdC/78ek5XfqX6xUAq5aR8HNVy+CQ4ODjUNbiPJM=
+github.com/containers/buildah v1.37.6 h1:kr6KXZYdfc9SjKVgFsAoXJYRcbKm0jwA/zu3hX5PPoA=
+github.com/containers/buildah v1.37.6/go.mod h1:kiNTdC/78ek5XfqX6xUAq5aR8HNVy+CQ4ODjUNbiPJM=
github.com/containers/common v0.60.4 h1:H5+LAMHPZEqX6vVNOQ+IguVsaFl8kbO/SZ/VPXjxhy0=
github.com/containers/common v0.60.4/go.mod h1:I0upBi1qJX3QmzGbUOBN1LVP6RvkKhd3qQpZbQT+Q54=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
diff --git a/vendor/github.com/containers/buildah/.cirrus.yml b/vendor/github.com/containers/buildah/.cirrus.yml
index 29b62eada576..f7ba01b903b9 100644
--- a/vendor/github.com/containers/buildah/.cirrus.yml
+++ b/vendor/github.com/containers/buildah/.cirrus.yml
@@ -6,7 +6,7 @@ env:
#### Global variables used for all tasks
####
# Name of the ultimate destination branch for this CI run, PR or post-merge.
- DEST_BRANCH: "main"
+ DEST_BRANCH: "release-1.37"
GOPATH: "/var/tmp/go"
GOSRC: "${GOPATH}/src/github.com/containers/buildah"
# Overrides default location (/tmp/cirrus) for repo clone
diff --git a/vendor/github.com/containers/buildah/CHANGELOG.md b/vendor/github.com/containers/buildah/CHANGELOG.md
index b2c1808959ae..278b4dc6f6b2 100644
--- a/vendor/github.com/containers/buildah/CHANGELOG.md
+++ b/vendor/github.com/containers/buildah/CHANGELOG.md
@@ -2,10 +2,24 @@
# Changelog
+## v1.37.6 (2025-01-20)
+
+ Fix TOCTOU error when bind and cache mounts use "src" values
+ define.TempDirForURL(): always use an intermediate subdirectory
+ internal/volume.GetBindMount(): discard writes in bind mounts
+ pkg/overlay: add a MountLabel flag to Options
+ pkg/overlay: add a ForceMount flag to Options
+ Add internal/volumes.bindFromChroot()
+ Add an internal/open package
+ Allow cache mounts to be stages or additional build contexts
+ [release-1.37][CI:DOCS] Touch up changelogs
+ [release-1.37][CI:DOCS] touchup changelog
+ Update CHANGELOG.md
+
## v1.37.5 (2024-10-17)
- Bump the containers/storage library to v1.55.1
- Properly validate cache IDs and sources
+ Bump the containers/storage library to v1.55.1 - Resolves CVE-2024-9676
+ Properly validate cache IDs and sources - Resolves CVE-2024-9675
Packit: constrain koji job to fedora package to avoid dupes
## v1.37.4 (2024-10-07)
@@ -29,7 +43,7 @@
[release-1.37] Bump c/common v0.60.1, c/image v5.32.1
-## vv1.37.0 (2024-07-26)
+## v1.37.0 (2024-07-26)
Bump c/storage, c/image, c/common for v1.37.0
"build with basename resolving user arg" tests: correct ARG use
diff --git a/vendor/github.com/containers/buildah/changelog.txt b/vendor/github.com/containers/buildah/changelog.txt
index 4fe7dbf1f8ef..eda2b860950e 100644
--- a/vendor/github.com/containers/buildah/changelog.txt
+++ b/vendor/github.com/containers/buildah/changelog.txt
@@ -1,6 +1,19 @@
+- Changelog for v1.37.6 (2025-01-20)
+ * Fix TOCTOU error when bind and cache mounts use "src" values
+ * define.TempDirForURL(): always use an intermediate subdirectory
+ * internal/volume.GetBindMount(): discard writes in bind mounts
+ * pkg/overlay: add a MountLabel flag to Options
+ * pkg/overlay: add a ForceMount flag to Options
+ * Add internal/volumes.bindFromChroot()
+ * Add an internal/open package
+ * Allow cache mounts to be stages or additional build contexts
+ * [release-1.37][CI:DOCS] Touch up changelogs
+ * [release-1.37][CI:DOCS] touchup changelog
+ * Update CHANGELOG.md
+
- Changelog for v1.37.5 (2024-10-17)
- * Bump the containers/storage library to v1.55.1
- * Properly validate cache IDs and sources
+ * Bump the containers/storage library to v1.55.1 - Resolves CVE-2024-9676
+ * Properly validate cache IDs and sources - Resolves CVE-2024-9675
* Packit: constrain koji job to fedora package to avoid dupes
- Changelog for v1.37.4 (2024-10-07)
@@ -20,7 +33,7 @@
- Changelog for v1.37.1 (2024-08-12)
* [release-1.37] Bump c/common v0.60.1, c/image v5.32.1
-- Changelog for vv1.37.0 (2024-07-26)
+- Changelog for v1.37.0 (2024-07-26)
* Bump c/storage, c/image, c/common for v1.37.0
* "build with basename resolving user arg" tests: correct ARG use
* bud-multiple-platform-no-run test: correct ARG use
diff --git a/vendor/github.com/containers/buildah/define/types.go b/vendor/github.com/containers/buildah/define/types.go
index 5267d08729e8..cd3fa4d50a64 100644
--- a/vendor/github.com/containers/buildah/define/types.go
+++ b/vendor/github.com/containers/buildah/define/types.go
@@ -29,7 +29,7 @@ const (
// identify working containers.
Package = "buildah"
// Version for the Package. Also used by .packit.sh for Packit builds.
- Version = "1.37.5"
+ Version = "1.37.6"
// DefaultRuntime if containers.conf fails.
DefaultRuntime = "runc"
@@ -169,13 +169,13 @@ type SBOMScanOptions struct {
MergeStrategy SBOMMergeStrategy // how to merge the outputs of multiple scans
}
-// TempDirForURL checks if the passed-in string looks like a URL or -. If it is,
-// TempDirForURL creates a temporary directory, arranges for its contents to be
-// the contents of that URL, and returns the temporary directory's path, along
-// with the name of a subdirectory which should be used as the build context
-// (which may be empty or "."). Removal of the temporary directory is the
-// responsibility of the caller. If the string doesn't look like a URL,
-// TempDirForURL returns empty strings and a nil error code.
+// TempDirForURL checks if the passed-in string looks like a URL or "-". If it
+// is, TempDirForURL creates a temporary directory, arranges for its contents
+// to be the contents of that URL, and returns the temporary directory's path,
+// along with the relative name of a subdirectory which should be used as the
+// build context (which may be empty or "."). Removal of the temporary
+// directory is the responsibility of the caller. If the string doesn't look
+// like a URL or "-", TempDirForURL returns empty strings and a nil error code.
func TempDirForURL(dir, prefix, url string) (name string, subdir string, err error) {
if !strings.HasPrefix(url, "http://") &&
!strings.HasPrefix(url, "https://") &&
@@ -188,19 +188,24 @@ func TempDirForURL(dir, prefix, url string) (name string, subdir string, err err
if err != nil {
return "", "", fmt.Errorf("creating temporary directory for %q: %w", url, err)
}
+ downloadDir := filepath.Join(name, "download")
+ if err = os.MkdirAll(downloadDir, 0o700); err != nil {
+ return "", "", fmt.Errorf("creating directory %q for %q: %w", downloadDir, url, err)
+ }
urlParsed, err := urlpkg.Parse(url)
if err != nil {
return "", "", fmt.Errorf("parsing url %q: %w", url, err)
}
if strings.HasPrefix(url, "git://") || strings.HasSuffix(urlParsed.Path, ".git") {
- combinedOutput, gitSubDir, err := cloneToDirectory(url, name)
+ combinedOutput, gitSubDir, err := cloneToDirectory(url, downloadDir)
if err != nil {
if err2 := os.RemoveAll(name); err2 != nil {
logrus.Debugf("error removing temporary directory %q: %v", name, err2)
}
return "", "", fmt.Errorf("cloning %q to %q:\n%s: %w", url, name, string(combinedOutput), err)
}
- return name, gitSubDir, nil
+ logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, gitSubDir))
+ return name, filepath.Join(filepath.Base(downloadDir), gitSubDir), nil
}
if strings.HasPrefix(url, "github.com/") {
ghurl := url
@@ -209,28 +214,29 @@ func TempDirForURL(dir, prefix, url string) (name string, subdir string, err err
subdir = path.Base(ghurl) + "-master"
}
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
- err = downloadToDirectory(url, name)
+ err = downloadToDirectory(url, downloadDir)
if err != nil {
if err2 := os.RemoveAll(name); err2 != nil {
logrus.Debugf("error removing temporary directory %q: %v", name, err2)
}
- return "", subdir, err
+ return "", "", err
}
- return name, subdir, nil
+ logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, subdir))
+ return name, filepath.Join(filepath.Base(downloadDir), subdir), nil
}
if url == "-" {
- err = stdinToDirectory(name)
+ err = stdinToDirectory(downloadDir)
if err != nil {
if err2 := os.RemoveAll(name); err2 != nil {
logrus.Debugf("error removing temporary directory %q: %v", name, err2)
}
- return "", subdir, err
+ return "", "", err
}
- logrus.Debugf("Build context is at %q", name)
- return name, subdir, nil
+ logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, subdir))
+ return name, filepath.Join(filepath.Base(downloadDir), subdir), nil
}
logrus.Debugf("don't know how to retrieve %q", url)
- if err2 := os.Remove(name); err2 != nil {
+ if err2 := os.RemoveAll(name); err2 != nil {
logrus.Debugf("error removing temporary directory %q: %v", name, err2)
}
return "", "", errors.New("unreachable code reached")
diff --git a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go
index 34e836ad2fe9..a73f078c7fe4 100644
--- a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go
+++ b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go
@@ -635,7 +635,12 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
// to `mountPoint` replaced from additional
// build-context. Reason: Parser will use this
// `from` to refer from stageMountPoints map later.
- stageMountPoints[from] = internal.StageMountDetails{IsStage: false, DidExecute: true, MountPoint: mountPoint}
+ stageMountPoints[from] = internal.StageMountDetails{
+ IsAdditionalBuildContext: true,
+ IsImage: true,
+ DidExecute: true,
+ MountPoint: mountPoint,
+ }
break
} else {
// Most likely this points to path on filesystem
@@ -667,7 +672,11 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
mountPoint = additionalBuildContext.DownloadedCache
}
}
- stageMountPoints[from] = internal.StageMountDetails{IsStage: true, DidExecute: true, MountPoint: mountPoint}
+ stageMountPoints[from] = internal.StageMountDetails{
+ IsAdditionalBuildContext: true,
+ DidExecute: true,
+ MountPoint: mountPoint,
+ }
break
}
}
@@ -678,14 +687,22 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
return nil, err
}
if otherStage, ok := s.executor.stages[from]; ok && otherStage.index < s.index {
- stageMountPoints[from] = internal.StageMountDetails{IsStage: true, DidExecute: otherStage.didExecute, MountPoint: otherStage.mountPoint}
+ stageMountPoints[from] = internal.StageMountDetails{
+ IsStage: true,
+ DidExecute: otherStage.didExecute,
+ MountPoint: otherStage.mountPoint,
+ }
break
} else {
mountPoint, err := s.getImageRootfs(s.ctx, from)
if err != nil {
return nil, fmt.Errorf("%s from=%s: no stage or image found with that name", flag, from)
}
- stageMountPoints[from] = internal.StageMountDetails{IsStage: false, DidExecute: true, MountPoint: mountPoint}
+ stageMountPoints[from] = internal.StageMountDetails{
+ IsImage: true,
+ DidExecute: true,
+ MountPoint: mountPoint,
+ }
break
}
default:
diff --git a/vendor/github.com/containers/buildah/internal/open/open.go b/vendor/github.com/containers/buildah/internal/open/open.go
new file mode 100644
index 000000000000..863dc79f2130
--- /dev/null
+++ b/vendor/github.com/containers/buildah/internal/open/open.go
@@ -0,0 +1,39 @@
+package open
+
+import (
+ "errors"
+ "fmt"
+ "syscall"
+)
+
+// InChroot opens the file at `path` after chrooting to `root` and then
+// changing its working directory to `wd`. Both `wd` and `path` are evaluated
+// in the chroot.
+// Returns a file handle, an Errno value if there was an error and the
+// underlying error was a standard library error code, and a non-empty error if
+// one was detected.
+func InChroot(root, wd, path string, mode int, perm uint32) (fd int, errno syscall.Errno, err error) {
+ requests := requests{
+ Root: root,
+ Wd: wd,
+ Open: []request{
+ {
+ Path: path,
+ Mode: mode,
+ Perms: perm,
+ },
+ },
+ }
+ results := inChroot(requests)
+ if len(results.Open) != 1 {
+ return -1, 0, fmt.Errorf("got %d results back instead of 1", len(results.Open))
+ }
+ if results.Open[0].Err != "" {
+ if results.Open[0].Errno != 0 {
+ err = fmt.Errorf("%s: %w", results.Open[0].Err, results.Open[0].Errno)
+ } else {
+ err = errors.New(results.Open[0].Err)
+ }
+ }
+ return int(results.Open[0].Fd), results.Open[0].Errno, err
+}
diff --git a/vendor/github.com/containers/buildah/internal/open/open_linux.go b/vendor/github.com/containers/buildah/internal/open/open_linux.go
new file mode 100644
index 000000000000..3d9d608b5f66
--- /dev/null
+++ b/vendor/github.com/containers/buildah/internal/open/open_linux.go
@@ -0,0 +1,88 @@
+package open
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/containers/storage/pkg/reexec"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+const (
+ bindFdToPathCommand = "buildah-bind-fd-to-path"
+)
+
+func init() {
+ reexec.Register(bindFdToPathCommand, bindFdToPathMain)
+}
+
+// BindFdToPath creates a bind mount from the open file (which is actually a
+// directory) to the specified location. If it succeeds, the caller will need
+// to unmount the targetPath when it's finished using it. Regardless, it
+// closes the passed-in descriptor.
+func BindFdToPath(fd uintptr, targetPath string) error {
+ f := os.NewFile(fd, "passed-in directory descriptor")
+ defer func() {
+ if err := f.Close(); err != nil {
+ logrus.Debugf("closing descriptor %d after attempting to bind to %q: %v", fd, targetPath, err)
+ }
+ }()
+ pipeReader, pipeWriter, err := os.Pipe()
+ if err != nil {
+ return err
+ }
+ cmd := reexec.Command(bindFdToPathCommand)
+ cmd.Stdin = pipeReader
+ var stdout bytes.Buffer
+ var stderr bytes.Buffer
+ cmd.Stdout, cmd.Stderr = &stdout, &stderr
+ cmd.ExtraFiles = append(cmd.ExtraFiles, f)
+
+ err = cmd.Start()
+ pipeReader.Close()
+ if err != nil {
+ pipeWriter.Close()
+ return fmt.Errorf("starting child: %w", err)
+ }
+
+ encoder := json.NewEncoder(pipeWriter)
+ if err := encoder.Encode(&targetPath); err != nil {
+ return fmt.Errorf("sending target path to child: %w", err)
+ }
+ pipeWriter.Close()
+ err = cmd.Wait()
+ trimmedOutput := strings.TrimSpace(stdout.String()) + strings.TrimSpace(stderr.String())
+ if err != nil {
+ if len(trimmedOutput) > 0 {
+ err = fmt.Errorf("%s: %w", trimmedOutput, err)
+ }
+ } else {
+ if len(trimmedOutput) > 0 {
+ err = errors.New(trimmedOutput)
+ }
+ }
+ return err
+}
+
+func bindFdToPathMain() {
+ var targetPath string
+ decoder := json.NewDecoder(os.Stdin)
+ if err := decoder.Decode(&targetPath); err != nil {
+ fmt.Fprintf(os.Stderr, "error decoding target path")
+ os.Exit(1)
+ }
+ if err := unix.Fchdir(3); err != nil {
+ fmt.Fprintf(os.Stderr, "fchdir(): %v", err)
+ os.Exit(1)
+ }
+ if err := unix.Mount(".", targetPath, "bind", unix.MS_BIND, ""); err != nil {
+ fmt.Fprintf(os.Stderr, "bind-mounting passed-in directory to %q: %v", targetPath, err)
+ os.Exit(1)
+ }
+ os.Exit(0)
+}
diff --git a/vendor/github.com/containers/buildah/internal/open/open_types.go b/vendor/github.com/containers/buildah/internal/open/open_types.go
new file mode 100644
index 000000000000..11dbe38db97f
--- /dev/null
+++ b/vendor/github.com/containers/buildah/internal/open/open_types.go
@@ -0,0 +1,28 @@
+package open
+
+import (
+ "syscall"
+)
+
+type request struct {
+ Path string
+ Mode int
+ Perms uint32
+}
+
+type requests struct {
+ Root string
+ Wd string
+ Open []request
+}
+
+type result struct {
+ Fd uintptr // as returned by open()
+ Err string // if err was not `nil`, err.Error()
+ Errno syscall.Errno // if err was not `nil` and included a syscall.Errno, its value
+}
+
+type results struct {
+ Err string
+ Open []result
+}
diff --git a/vendor/github.com/containers/buildah/internal/open/open_unix.go b/vendor/github.com/containers/buildah/internal/open/open_unix.go
new file mode 100644
index 000000000000..fd254e8745c8
--- /dev/null
+++ b/vendor/github.com/containers/buildah/internal/open/open_unix.go
@@ -0,0 +1,168 @@
+//go:build linux || freebsd || darwin
+
+package open
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "syscall"
+
+ "github.com/containers/storage/pkg/reexec"
+ "golang.org/x/sys/unix"
+)
+
+const (
+ inChrootCommand = "buildah-open-in-chroot"
+)
+
+func init() {
+ reexec.Register(inChrootCommand, inChrootMain)
+}
+
+func inChroot(requests requests) results {
+ sock, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0)
+ if err != nil {
+ return results{Err: fmt.Errorf("creating socket pair: %w", err).Error()}
+ }
+ parentSock := sock[0]
+ childSock := sock[1]
+ parentEnd := os.NewFile(uintptr(parentSock), "parent end of socket pair")
+ childEnd := os.NewFile(uintptr(childSock), "child end of socket pair")
+ cmd := reexec.Command(inChrootCommand)
+ cmd.ExtraFiles = append(cmd.ExtraFiles, childEnd)
+ err = cmd.Start()
+ childEnd.Close()
+ defer parentEnd.Close()
+ if err != nil {
+ return results{Err: err.Error()}
+ }
+ encoder := json.NewEncoder(parentEnd)
+ if err := encoder.Encode(&requests); err != nil {
+ return results{Err: fmt.Errorf("sending request down socket: %w", err).Error()}
+ }
+ if err := unix.Shutdown(parentSock, unix.SHUT_WR); err != nil {
+ return results{Err: fmt.Errorf("finishing sending request down socket: %w", err).Error()}
+ }
+ b := make([]byte, 65536)
+ oob := make([]byte, 65536)
+ n, oobn, _, _, err := unix.Recvmsg(parentSock, b, oob, 0)
+ if err != nil {
+ return results{Err: fmt.Errorf("receiving message: %w", err).Error()}
+ }
+ if err := unix.Shutdown(parentSock, unix.SHUT_RD); err != nil {
+ return results{Err: fmt.Errorf("finishing socket: %w", err).Error()}
+ }
+ if n > len(b) {
+ return results{Err: fmt.Errorf("too much regular data: %d > %d", n, len(b)).Error()}
+ }
+ if oobn > len(oob) {
+ return results{Err: fmt.Errorf("too much OOB data: %d > %d", oobn, len(oob)).Error()}
+ }
+ scms, err := unix.ParseSocketControlMessage(oob[:oobn])
+ if err != nil {
+ return results{Err: fmt.Errorf("parsing control message: %w", err).Error()}
+ }
+ var receivedFds []int
+ for i := range scms {
+ fds, err := unix.ParseUnixRights(&scms[i])
+ if err != nil {
+ return results{Err: fmt.Errorf("parsing rights message %d: %w", i, err).Error()}
+ }
+ receivedFds = append(receivedFds, fds...)
+ }
+ decoder := json.NewDecoder(bytes.NewReader(b[:n]))
+ var result results
+ if err := decoder.Decode(&result); err != nil {
+ return results{Err: fmt.Errorf("decoding results: %w", err).Error()}
+ }
+ j := 0
+ for i := range result.Open {
+ if result.Open[i].Err == "" {
+ if j >= len(receivedFds) {
+ for _, fd := range receivedFds {
+ unix.Close(fd)
+ }
+ return results{Err: fmt.Errorf("didn't receive enough FDs").Error()}
+ }
+ result.Open[i].Fd = uintptr(receivedFds[j])
+ j++
+ }
+ }
+ return result
+}
+
+func inChrootMain() {
+ var theseRequests requests
+ var theseResults results
+ sockFd := 3
+ sock := os.NewFile(uintptr(sockFd), "socket connection to parent process")
+ defer sock.Close()
+ encoder := json.NewEncoder(sock)
+ decoder := json.NewDecoder(sock)
+ if err := decoder.Decode(&theseRequests); err != nil {
+ if err := encoder.Encode(results{Err: fmt.Errorf("decoding request: %w", err).Error()}); err != nil {
+ os.Exit(1)
+ }
+ }
+ if theseRequests.Root != "" {
+ if err := os.Chdir(theseRequests.Root); err != nil {
+ if err := encoder.Encode(results{Err: fmt.Errorf("changing to %q: %w", theseRequests.Root, err).Error()}); err != nil {
+ os.Exit(1)
+ }
+ os.Exit(1)
+ }
+ if err := unix.Chroot(theseRequests.Root); err != nil {
+ if err := encoder.Encode(results{Err: fmt.Errorf("chrooting to %q: %w", theseRequests.Root, err).Error()}); err != nil {
+ os.Exit(1)
+ }
+ os.Exit(1)
+ }
+ if err := os.Chdir("/"); err != nil {
+ if err := encoder.Encode(results{Err: fmt.Errorf("changing to new root: %w", err).Error()}); err != nil {
+ os.Exit(1)
+ }
+ os.Exit(1)
+ }
+ }
+ if theseRequests.Wd != "" {
+ if err := os.Chdir(theseRequests.Wd); err != nil {
+ if err := encoder.Encode(results{Err: fmt.Errorf("changing to %q in chroot: %w", theseRequests.Wd, err).Error()}); err != nil {
+ os.Exit(1)
+ }
+ os.Exit(1)
+ }
+ }
+ var fds []int
+ for _, request := range theseRequests.Open {
+ fd, err := unix.Open(request.Path, request.Mode, request.Perms)
+ thisResult := result{Fd: uintptr(fd)}
+ if err == nil {
+ fds = append(fds, fd)
+ } else {
+ var errno syscall.Errno
+ thisResult.Err = err.Error()
+ if errors.As(err, &errno) {
+ thisResult.Errno = errno
+ }
+ }
+ theseResults.Open = append(theseResults.Open, thisResult)
+ }
+ rights := unix.UnixRights(fds...)
+ inband, err := json.Marshal(&theseResults)
+ if err != nil {
+ if err := encoder.Encode(results{Err: fmt.Errorf("sending response: %w", err).Error()}); err != nil {
+ os.Exit(1)
+ }
+ os.Exit(1)
+ }
+ if err := unix.Sendmsg(sockFd, inband, rights, nil, 0); err != nil {
+ if err := encoder.Encode(results{Err: fmt.Errorf("sending response: %w", err).Error()}); err != nil {
+ os.Exit(1)
+ }
+ os.Exit(1)
+ }
+ os.Exit(0)
+}
diff --git a/vendor/github.com/containers/buildah/internal/open/open_unsupported.go b/vendor/github.com/containers/buildah/internal/open/open_unsupported.go
new file mode 100644
index 000000000000..111056a18bdf
--- /dev/null
+++ b/vendor/github.com/containers/buildah/internal/open/open_unsupported.go
@@ -0,0 +1,7 @@
+//go:build !linux && !freebsd && !darwin
+
+package open
+
+func inChroot(requests requests) results {
+ return results{Err: "open-in-chroot not available on this platform"}
+}
diff --git a/vendor/github.com/containers/buildah/internal/types.go b/vendor/github.com/containers/buildah/internal/types.go
index ee87eca2255c..45aac0b016cf 100644
--- a/vendor/github.com/containers/buildah/internal/types.go
+++ b/vendor/github.com/containers/buildah/internal/types.go
@@ -12,7 +12,9 @@ const (
// StageExecutor has ability to mount stages/images in current context and
// automatically clean them up.
type StageMountDetails struct {
- DidExecute bool // tells if the stage which is being mounted was freshly executed or was part of older cache
- IsStage bool // tells if mountpoint returned from stage executor is stage or image
- MountPoint string // mountpoint of stage/image
+ DidExecute bool // true if this is a freshly-executed stage, or an image, possibly from a non-local cache
+ IsStage bool // true if the mountpoint is a stage's rootfs
+ IsImage bool // true if the mountpoint is an image's rootfs
+ IsAdditionalBuildContext bool // true if the mountpoint is an additional build context
+ MountPoint string // mountpoint of the stage or image's root directory or path of the additional build context
}
diff --git a/vendor/github.com/containers/buildah/internal/volumes/bind_linux.go b/vendor/github.com/containers/buildah/internal/volumes/bind_linux.go
new file mode 100644
index 000000000000..f8723eb080cd
--- /dev/null
+++ b/vendor/github.com/containers/buildah/internal/volumes/bind_linux.go
@@ -0,0 +1,102 @@
+package volumes
+
+import (
+ "errors"
+ "fmt"
+ "os"
+
+ "github.com/containers/buildah/internal/open"
+ "github.com/containers/storage/pkg/mount"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
+)
+
+// bindFromChroot opens "path" inside of "root" using a chrooted subprocess
+// that returns a descriptor, then creates a uniquely-named temporary directory
+// or file under "tmp" and bind-mounts the opened descriptor to it, returning
+// the path of the temporary file or directory. The caller is responsible for
+// unmounting and removing the temporary.
+func bindFromChroot(root, path, tmp string) (string, error) {
+ fd, _, err := open.InChroot(root, "", path, unix.O_DIRECTORY|unix.O_RDONLY, 0)
+ if err != nil {
+ if !errors.Is(err, unix.ENOTDIR) {
+ return "", fmt.Errorf("opening directory %q under %q: %w", path, root, err)
+ }
+ fd, _, err = open.InChroot(root, "", path, unix.O_RDWR, 0)
+ if err != nil {
+ return "", fmt.Errorf("opening non-directory %q under %q: %w", path, root, err)
+ }
+ }
+ defer func() {
+ if err := unix.Close(fd); err != nil {
+ logrus.Debugf("closing %q under %q: %v", path, root, err)
+ }
+ }()
+
+ succeeded := false
+ var dest string
+ var destF *os.File
+ defer func() {
+ if !succeeded {
+ if destF != nil {
+ if err := destF.Close(); err != nil {
+ logrus.Debugf("closing bind target %q: %v", dest, err)
+ }
+ }
+ if dest != "" {
+ if err := os.Remove(dest); err != nil {
+ logrus.Debugf("removing bind target %q: %v", dest, err)
+ }
+ }
+ }
+ }()
+
+ var st unix.Stat_t
+ if err = unix.Fstat(fd, &st); err != nil {
+ return "", fmt.Errorf("checking if %q under %q was a directory: %w", path, root, err)
+ }
+
+ if st.Mode&unix.S_IFDIR == unix.S_IFDIR {
+ if dest, err = os.MkdirTemp(tmp, "bind"); err != nil {
+ return "", fmt.Errorf("creating a bind target directory: %w", err)
+ }
+ } else {
+ if destF, err = os.CreateTemp(tmp, "bind"); err != nil {
+ return "", fmt.Errorf("creating a bind target non-directory: %w", err)
+ }
+ if err := destF.Close(); err != nil {
+ logrus.Debugf("closing bind target %q: %v", dest, err)
+ }
+ dest = destF.Name()
+ }
+ defer func() {
+ if !succeeded {
+ if err := os.Remove(dest); err != nil {
+ logrus.Debugf("removing bind target %q: %v", dest, err)
+ }
+ }
+ }()
+
+ if err := unix.Mount(fmt.Sprintf("/proc/self/fd/%d", fd), dest, "bind", unix.MS_BIND, ""); err != nil {
+ return "", fmt.Errorf("bind-mounting passed-in descriptor to %q: %w", dest, err)
+ }
+ defer func() {
+ if !succeeded {
+ if err := mount.Unmount(dest); err != nil {
+ logrus.Debugf("unmounting bound target %q: %v", dest, err)
+ }
+ }
+ }()
+
+ var st2 unix.Stat_t
+ if err = unix.Stat(dest, &st2); err != nil {
+ return "", fmt.Errorf("looking up device/inode of newly-bind-mounted %q: %w", dest, err)
+ }
+
+ if st2.Dev != st.Dev || st2.Ino != st.Ino {
+ return "", fmt.Errorf("device/inode weren't what we expected after bind mounting: %w", err)
+ }
+
+ succeeded = true
+ return dest, nil
+}
diff --git a/vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go b/vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go
new file mode 100644
index 000000000000..d9340c188f13
--- /dev/null
+++ b/vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go
@@ -0,0 +1,15 @@
+//go:build !linux
+
+package volumes
+
+import "errors"
+
+// bindFromChroot would open "path" inside of "root" using a chrooted
+// subprocess that returns a descriptor, then would create a uniquely-named
+// temporary directory or file under "tmp" and bind-mount the opened descriptor
+// to it, returning the path of the temporary file or directory. The caller
+// would be responsible for unmounting and removing the temporary. For now,
+// this just returns an error because it is not implemented for this platform.
+func bindFromChroot(root, path, tmp string) (string, error) {
+ return "", errors.New("not available on this system")
+}
diff --git a/vendor/github.com/containers/buildah/internal/volumes/volumes.go b/vendor/github.com/containers/buildah/internal/volumes/volumes.go
index 610e9fcf11b2..1014a470ef65 100644
--- a/vendor/github.com/containers/buildah/internal/volumes/volumes.go
+++ b/vendor/github.com/containers/buildah/internal/volumes/volumes.go
@@ -17,15 +17,19 @@ import (
internalParse "github.com/containers/buildah/internal/parse"
"github.com/containers/buildah/internal/tmpdir"
internalUtil "github.com/containers/buildah/internal/util"
+ "github.com/containers/buildah/pkg/overlay"
"github.com/containers/common/pkg/parse"
"github.com/containers/image/v5/types"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/lockfile"
+ "github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/unshare"
digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/runtime-spec/specs-go"
selinux "github.com/opencontainers/selinux/go-selinux"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/exp/slices"
)
const (
@@ -56,18 +60,84 @@ func CacheParent() string {
return filepath.Join(tmpdir.GetTempDir(), buildahCacheDir+"-"+strconv.Itoa(unshare.GetRootlessUID()))
}
+func mountIsReadWrite(m specs.Mount) bool {
+ // in case of conflicts, the last one wins, so it's not enough
+ // to check for the presence of either "rw" or "ro" anywhere
+ // with e.g. slices.Contains()
+ rw := true
+ for _, option := range m.Options {
+ switch option {
+ case "rw":
+ rw = true
+ case "ro":
+ rw = false
+ }
+ }
+ return rw
+}
+
+func convertToOverlay(m specs.Mount, store storage.Store, mountLabel, tmpDir string, uid, gid int) (specs.Mount, string, error) {
+ overlayDir, err := overlay.TempDir(tmpDir, uid, gid)
+ if err != nil {
+ return specs.Mount{}, "", fmt.Errorf("setting up overlay for %q: %w", m.Destination, err)
+ }
+ options := overlay.Options{GraphOpts: slices.Clone(store.GraphOptions()), ForceMount: true, MountLabel: mountLabel}
+ fileInfo, err := os.Stat(m.Source)
+ if err != nil {
+ return specs.Mount{}, "", fmt.Errorf("setting up overlay of %q: %w", m.Source, err)
+ }
+ // we might be trying to "overlay" for a non-directory, and the kernel doesn't like that very much
+ var mountThisInstead specs.Mount
+ if fileInfo.IsDir() {
+ // do the normal thing of mounting this directory as a lower with a temporary upper
+ mountThisInstead, err = overlay.MountWithOptions(overlayDir, m.Source, m.Destination, &options)
+ if err != nil {
+ return specs.Mount{}, "", fmt.Errorf("setting up overlay of %q: %w", m.Source, err)
+ }
+ } else {
+ // mount the parent directory as the lower with a temporary upper, and return a
+ // bind mount from the non-directory in the merged directory to the destination
+ sourceDir := filepath.Dir(m.Source)
+ sourceBase := filepath.Base(m.Source)
+ destination := m.Destination
+ mountedOverlay, err := overlay.MountWithOptions(overlayDir, sourceDir, destination, &options)
+ if err != nil {
+ return specs.Mount{}, "", fmt.Errorf("setting up overlay of %q: %w", sourceDir, err)
+ }
+ if mountedOverlay.Type != define.TypeBind {
+ if err2 := overlay.RemoveTemp(overlayDir); err2 != nil {
+ return specs.Mount{}, "", fmt.Errorf("cleaning up after failing to set up overlay: %v, while setting up overlay for %q: %w", err2, destination, err)
+ }
+ return specs.Mount{}, "", fmt.Errorf("setting up overlay for %q at %q: %w", mountedOverlay.Source, destination, err)
+ }
+ mountThisInstead = mountedOverlay
+ mountThisInstead.Source = filepath.Join(mountedOverlay.Source, sourceBase)
+ mountThisInstead.Destination = destination
+ }
+ return mountThisInstead, overlayDir, nil
+}
+
// FIXME: this code needs to be merged with pkg/parse/parse.go ValidateVolumeOpts
+//
// GetBindMount parses a single bind mount entry from the --mount flag.
-// Returns specifiedMount and a string which contains name of image that we mounted otherwise its empty.
-// Caller is expected to perform unmount of any mounted images
-func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, store storage.Store, imageMountLabel string, additionalMountPoints map[string]internal.StageMountDetails, workDir string) (specs.Mount, string, error) {
+//
+// Returns a Mount to add to the runtime spec's list of mounts, the ID of the
+// image we mounted if we mounted one, the path of a mounted location if one
+// needs to be unmounted and removed, and the path of an overlay mount if one
+// needs to be cleaned up, or an error.
+//
+// The caller is expected to, after the command which uses the mount exits,
+// clean up the overlay filesystem (if we provided a path to it), unmount and
+// remove the mountpoint for the mounted filesystem (if we provided the path to
+// its mountpoint), and then unmount the image (if we mounted one).
+func GetBindMount(sys *types.SystemContext, args []string, contextDir string, store storage.Store, mountLabel string, additionalMountPoints map[string]internal.StageMountDetails, workDir, tmpDir string) (specs.Mount, string, string, string, error) {
newMount := specs.Mount{
Type: define.TypeBind,
}
- setRelabel := false
- mountReadability := false
- setDest := false
+ setRelabel := ""
+ mountReadability := ""
+ setDest := ""
bindNonRecursive := false
fromImage := ""
@@ -80,86 +150,85 @@ func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, st
case "bind-nonrecursive":
newMount.Options = append(newMount.Options, "bind")
bindNonRecursive = true
- case "ro", "nosuid", "nodev", "noexec":
+ case "nosuid", "nodev", "noexec":
// TODO: detect duplication of these options.
// (Is this necessary?)
newMount.Options = append(newMount.Options, argName)
- mountReadability = true
case "rw", "readwrite":
newMount.Options = append(newMount.Options, "rw")
- mountReadability = true
- case "readonly":
- // Alias for "ro"
+ mountReadability = "rw"
+ case "ro", "readonly":
newMount.Options = append(newMount.Options, "ro")
- mountReadability = true
+ mountReadability = "ro"
case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z", "U", "no-dereference":
if hasArgValue {
- return newMount, "", fmt.Errorf("%v: %w", val, errBadOptionArg)
+ return newMount, "", "", "", fmt.Errorf("%v: %w", val, errBadOptionArg)
}
newMount.Options = append(newMount.Options, argName)
case "from":
if !hasArgValue {
- return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
fromImage = argValue
case "bind-propagation":
if !hasArgValue {
- return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
switch argValue {
default:
- return newMount, "", fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption)
+ return newMount, "", "", "", fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption)
case "shared", "rshared", "private", "rprivate", "slave", "rslave":
// this should be the relevant parts of the same list of options we accepted above
}
newMount.Options = append(newMount.Options, argValue)
case "src", "source":
if !hasArgValue {
- return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
newMount.Source = argValue
case "target", "dst", "destination":
if !hasArgValue {
- return newMount, "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
targetPath := argValue
+ setDest = targetPath
if !path.IsAbs(targetPath) {
targetPath = filepath.Join(workDir, targetPath)
}
if err := parse.ValidateVolumeCtrDir(targetPath); err != nil {
- return newMount, "", err
+ return newMount, "", "", "", err
}
newMount.Destination = targetPath
- setDest = true
case "relabel":
- if setRelabel {
- return newMount, "", fmt.Errorf("cannot pass 'relabel' option more than once: %w", errBadOptionArg)
+ if setRelabel != "" {
+ return newMount, "", "", "", fmt.Errorf("cannot pass 'relabel' option more than once: %w", errBadOptionArg)
}
- setRelabel = true
+ setRelabel = argValue
switch argValue {
case "private":
newMount.Options = append(newMount.Options, "Z")
case "shared":
newMount.Options = append(newMount.Options, "z")
default:
- return newMount, "", fmt.Errorf("%s mount option must be 'private' or 'shared': %w", argName, errBadMntOption)
+ return newMount, "", "", "", fmt.Errorf("%s mount option must be 'private' or 'shared': %w", argName, errBadMntOption)
}
case "consistency":
// Option for OS X only, has no meaning on other platforms
// and can thus be safely ignored.
// See also the handling of the equivalent "delegated" and "cached" in ValidateVolumeOpts
default:
- return newMount, "", fmt.Errorf("%v: %w", argName, errBadMntOption)
+ return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadMntOption)
}
}
// default mount readability is always readonly
- if !mountReadability {
+ if mountReadability == "" {
newMount.Options = append(newMount.Options, "ro")
}
// Following variable ensures that we return imagename only if we did additional mount
- isImageMounted := false
+ succeeded := false
+ mountedImage := ""
if fromImage != "" {
mountPoint := ""
if additionalMountPoints != nil {
@@ -170,16 +239,23 @@ func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, st
// if mountPoint of image was not found in additionalMap
// or additionalMap was nil, try mounting image
if mountPoint == "" {
- image, err := internalUtil.LookupImage(ctx, store, fromImage)
+ image, err := internalUtil.LookupImage(sys, store, fromImage)
if err != nil {
- return newMount, "", err
+ return newMount, "", "", "", err
}
- mountPoint, err = image.Mount(context.Background(), nil, imageMountLabel)
+ mountPoint, err = image.Mount(context.Background(), nil, mountLabel)
if err != nil {
- return newMount, "", err
- }
- isImageMounted = true
+ return newMount, "", "", "", err
+ }
+ mountedImage = image.ID()
+ defer func() {
+ if !succeeded {
+ if _, err := store.UnmountImage(mountedImage, false); err != nil {
+ logrus.Debugf("unmounting bind-mounted image %q: %v", fromImage, err)
+ }
+ }
+ }()
}
contextDir = mountPoint
}
@@ -190,48 +266,73 @@ func GetBindMount(ctx *types.SystemContext, args []string, contextDir string, st
newMount.Options = append(newMount.Options, "rbind")
}
- if !setDest {
- return newMount, fromImage, errBadVolDest
+ if setDest == "" {
+ return newMount, "", "", "", errBadVolDest
}
// buildkit parity: support absolute path for sources from current build context
if contextDir != "" {
// path should be /contextDir/specified path
- evaluated, err := copier.Eval(contextDir, newMount.Source, copier.EvalOptions{})
+ evaluated, err := copier.Eval(contextDir, contextDir+string(filepath.Separator)+newMount.Source, copier.EvalOptions{})
if err != nil {
- return newMount, "", err
+ return newMount, "", "", "", err
}
newMount.Source = evaluated
} else {
// looks like its coming from `build run --mount=type=bind` allow using absolute path
// error out if no source is set
if newMount.Source == "" {
- return newMount, "", errBadVolSrc
+ return newMount, "", "", "", errBadVolSrc
}
if err := parse.ValidateVolumeHostDir(newMount.Source); err != nil {
- return newMount, "", err
+ return newMount, "", "", "", err
}
}
opts, err := parse.ValidateVolumeOpts(newMount.Options)
if err != nil {
- return newMount, fromImage, err
+ return newMount, "", "", "", err
}
newMount.Options = opts
- if !isImageMounted {
- // we don't want any cleanups if image was not mounted explicitly
- // so dont return anything
- fromImage = ""
+ var intermediateMount string
+ if contextDir != "" && newMount.Source != contextDir {
+ rel, err := filepath.Rel(contextDir, newMount.Source)
+ if err != nil {
+ return newMount, "", "", "", fmt.Errorf("computing pathname of bind subdirectory: %w", err)
+ }
+ if rel != "." && rel != "/" {
+ mnt, err := bindFromChroot(contextDir, rel, tmpDir)
+ if err != nil {
+ return newMount, "", "", "", fmt.Errorf("sanitizing bind subdirectory %q: %w", newMount.Source, err)
+ }
+ logrus.Debugf("bind-mounted %q under %q to %q", rel, contextDir, mnt)
+ intermediateMount = mnt
+ newMount.Source = intermediateMount
+ }
}
- return newMount, fromImage, nil
+ overlayDir := ""
+ if mountedImage != "" || mountIsReadWrite(newMount) {
+ if newMount, overlayDir, err = convertToOverlay(newMount, store, mountLabel, tmpDir, 0, 0); err != nil {
+ return newMount, "", "", "", err
+ }
+ }
+
+ succeeded = true
+ return newMount, mountedImage, intermediateMount, overlayDir, nil
}
// GetCacheMount parses a single cache mount entry from the --mount flag.
//
-// If this function succeeds and returns a non-nil *lockfile.LockFile, the caller must unlock it (when??).
-func GetCacheMount(args []string, store storage.Store, imageMountLabel string, additionalMountPoints map[string]internal.StageMountDetails, workDir string) (specs.Mount, *lockfile.LockFile, error) {
+// Returns a Mount to add to the runtime spec's list of mounts, the path of a
+// mounted filesystem if one needs to be unmounted, and an optional lock that
+// needs to be released, or an error.
+//
+// The caller is expected to, after the command which uses the mount exits,
+// unmount and remove the mountpoint of the mounted filesystem (if we provided
+// the path to its mountpoint) and release the lock (if we took one).
+func GetCacheMount(args []string, additionalMountPoints map[string]internal.StageMountDetails, workDir, tmpDir string) (specs.Mount, string, *lockfile.LockFile, error) {
var err error
var mode uint64
var buildahLockFilesDir string
@@ -282,69 +383,69 @@ func GetCacheMount(args []string, store storage.Store, imageMountLabel string, a
sharing = argValue
case "bind-propagation":
if !hasArgValue {
- return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
switch argValue {
default:
- return newMount, nil, fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption)
+ return newMount, "", nil, fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption)
case "shared", "rshared", "private", "rprivate", "slave", "rslave":
// this should be the relevant parts of the same list of options we accepted above
}
newMount.Options = append(newMount.Options, argValue)
case "id":
if !hasArgValue {
- return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
id = argValue
case "from":
if !hasArgValue {
- return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
fromStage = argValue
case "target", "dst", "destination":
if !hasArgValue {
- return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
targetPath := argValue
if !path.IsAbs(targetPath) {
targetPath = filepath.Join(workDir, targetPath)
}
if err := parse.ValidateVolumeCtrDir(targetPath); err != nil {
- return newMount, nil, err
+ return newMount, "", nil, err
}
newMount.Destination = targetPath
setDest = true
case "src", "source":
if !hasArgValue {
- return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
newMount.Source = argValue
case "mode":
if !hasArgValue {
- return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
mode, err = strconv.ParseUint(argValue, 8, 32)
if err != nil {
- return newMount, nil, fmt.Errorf("unable to parse cache mode: %w", err)
+ return newMount, "", nil, fmt.Errorf("unable to parse cache mode: %w", err)
}
case "uid":
if !hasArgValue {
- return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
uid, err = strconv.Atoi(argValue)
if err != nil {
- return newMount, nil, fmt.Errorf("unable to parse cache uid: %w", err)
+ return newMount, "", nil, fmt.Errorf("unable to parse cache uid: %w", err)
}
case "gid":
if !hasArgValue {
- return newMount, nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
+ return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg)
}
gid, err = strconv.Atoi(argValue)
if err != nil {
- return newMount, nil, fmt.Errorf("unable to parse cache gid: %w", err)
+ return newMount, "", nil, fmt.Errorf("unable to parse cache gid: %w", err)
}
default:
- return newMount, nil, fmt.Errorf("%v: %w", argName, errBadMntOption)
+ return newMount, "", nil, fmt.Errorf("%v: %w", argName, errBadMntOption)
}
}
@@ -355,16 +456,18 @@ func GetCacheMount(args []string, store storage.Store, imageMountLabel string, a
}
if !setDest {
- return newMount, nil, errBadVolDest
+ return newMount, "", nil, errBadVolDest
}
+ thisCacheRoot := ""
if fromStage != "" {
- // do not create cache on host
- // instead use read-only mounted stage as cache
+ // do not create and use a cache directory on the host,
+ // instead use the location in the mounted stage or
+ // temporary directory as the cache
mountPoint := ""
if additionalMountPoints != nil {
if val, ok := additionalMountPoints[fromStage]; ok {
- if val.IsStage {
+ if !val.IsImage {
mountPoint = val.MountPoint
}
}
@@ -372,14 +475,9 @@ func GetCacheMount(args []string, store storage.Store, imageMountLabel string, a
// Cache does not supports using image so if not stage found
// return with error
if mountPoint == "" {
- return newMount, nil, fmt.Errorf("no stage found with name %s", fromStage)
+ return newMount, "", nil, fmt.Errorf("no stage or additional build context found with name %s", fromStage)
}
- // path should be /contextDir/specified path
- evaluated, err := copier.Eval(mountPoint, string(filepath.Separator)+newMount.Source, copier.EvalOptions{})
- if err != nil {
- return newMount, nil, err
- }
- newMount.Source = evaluated
+ thisCacheRoot = mountPoint
} else {
// we need to create cache on host if no image is being used
@@ -389,64 +487,73 @@ func GetCacheMount(args []string, store storage.Store, imageMountLabel string, a
// cache parent directory: creates separate cache parent for each user.
cacheParent := CacheParent()
+
// create cache on host if not present
err = os.MkdirAll(cacheParent, os.FileMode(0755))
if err != nil {
- return newMount, nil, fmt.Errorf("unable to create build cache directory: %w", err)
+ return newMount, "", nil, fmt.Errorf("unable to create build cache directory: %w", err)
}
if id != "" {
// Don't let the user control where we place the directory.
dirID := digest.FromString(id).Encoded()[:16]
- newMount.Source = filepath.Join(cacheParent, dirID)
+ thisCacheRoot = filepath.Join(cacheParent, dirID)
buildahLockFilesDir = filepath.Join(BuildahCacheLockfileDir, dirID)
} else {
// Don't let the user control where we place the directory.
dirID := digest.FromString(newMount.Destination).Encoded()[:16]
- newMount.Source = filepath.Join(cacheParent, dirID)
+ thisCacheRoot = filepath.Join(cacheParent, dirID)
buildahLockFilesDir = filepath.Join(BuildahCacheLockfileDir, dirID)
}
+
idPair := idtools.IDPair{
UID: uid,
GID: gid,
}
- // buildkit parity: change uid and gid if specified otheriwise keep `0`
- err = idtools.MkdirAllAndChownNew(newMount.Source, os.FileMode(mode), idPair)
+ // buildkit parity: change uid and gid if specified, otherwise keep `0`
+ err = idtools.MkdirAllAndChownNew(thisCacheRoot, os.FileMode(mode), idPair)
if err != nil {
- return newMount, nil, fmt.Errorf("unable to change uid,gid of cache directory: %w", err)
+ return newMount, "", nil, fmt.Errorf("unable to change uid,gid of cache directory: %w", err)
}
// create a subdirectory inside `cacheParent` just to store lockfiles
buildahLockFilesDir = filepath.Join(cacheParent, buildahLockFilesDir)
err = os.MkdirAll(buildahLockFilesDir, os.FileMode(0700))
if err != nil {
- return newMount, nil, fmt.Errorf("unable to create build cache lockfiles directory: %w", err)
+ return newMount, "", nil, fmt.Errorf("unable to create build cache lockfiles directory: %w", err)
}
}
- var targetLock *lockfile.LockFile // = nil
+ // path should be /mountPoint/specified path
+ evaluated, err := copier.Eval(thisCacheRoot, thisCacheRoot+string(filepath.Separator)+newMount.Source, copier.EvalOptions{})
+ if err != nil {
+ return newMount, "", nil, err
+ }
+ newMount.Source = evaluated
+
succeeded := false
- defer func() {
- if !succeeded && targetLock != nil {
- targetLock.Unlock()
- }
- }()
+ var targetLock *lockfile.LockFile
switch sharing {
case "locked":
// lock parent cache
lockfile, err := lockfile.GetLockFile(filepath.Join(buildahLockFilesDir, BuildahCacheLockfile))
if err != nil {
- return newMount, nil, fmt.Errorf("unable to acquire lock when sharing mode is locked: %w", err)
+ return newMount, "", nil, fmt.Errorf("unable to acquire lock when sharing mode is locked: %w", err)
}
// Will be unlocked after the RUN step is executed.
lockfile.Lock()
targetLock = lockfile
+ defer func() {
+ if !succeeded {
+ targetLock.Unlock()
+ }
+ }()
case "shared":
// do nothing since default is `shared`
break
default:
// error out for unknown values
- return newMount, nil, fmt.Errorf("unrecognized value %q for field `sharing`: %w", sharing, err)
+ return newMount, "", nil, fmt.Errorf("unrecognized value %q for field `sharing`: %w", sharing, err)
}
// buildkit parity: default sharing should be shared
@@ -464,12 +571,29 @@ func GetCacheMount(args []string, store storage.Store, imageMountLabel string, a
opts, err := parse.ValidateVolumeOpts(newMount.Options)
if err != nil {
- return newMount, nil, err
+ return newMount, "", nil, err
}
newMount.Options = opts
+ var intermediateMount string
+ if newMount.Source != thisCacheRoot {
+ rel, err := filepath.Rel(thisCacheRoot, newMount.Source)
+ if err != nil {
+ return newMount, "", nil, fmt.Errorf("computing pathname of cache subdirectory: %w", err)
+ }
+ if rel != "." && rel != "/" {
+ mnt, err := bindFromChroot(thisCacheRoot, rel, tmpDir)
+ if err != nil {
+ return newMount, "", nil, fmt.Errorf("sanitizing cache subdirectory %q: %w", newMount.Source, err)
+ }
+ logrus.Debugf("bind-mounted %q under %q to %q", rel, thisCacheRoot, mnt)
+ intermediateMount = mnt
+ newMount.Source = intermediateMount
+ }
+ }
+
succeeded = true
- return newMount, targetLock, nil
+ return newMount, intermediateMount, targetLock, nil
}
func getVolumeMounts(volumes []string) (map[string]specs.Mount, error) {
@@ -495,27 +619,53 @@ func UnlockLockArray(locks []*lockfile.LockFile) {
}
}
-// GetVolumes gets the volumes from --volume and --mount
+// GetVolumes gets the volumes from --volume and --mount flags.
//
-// If this function succeeds, the caller must unlock the returned *lockfile.LockFile s if any (when??).
-func GetVolumes(ctx *types.SystemContext, store storage.Store, volumes []string, mounts []string, contextDir string, workDir string) ([]specs.Mount, []string, []*lockfile.LockFile, error) {
- unifiedMounts, mountedImages, targetLocks, err := getMounts(ctx, store, mounts, contextDir, workDir)
+// Returns a slice of Mounts to add to the runtime spec's list of mounts, the
+// IDs of any images we mounted, a slice of bind-mounted paths, a slice of
+// overlay directories and a slice of locks that we acquired, or an error.
+//
+// The caller is expected to, after the command which uses the mounts and
+// volumes exits, clean up the overlay directories, unmount and remove the
+// mountpoints for the bind-mounted paths, unmount any images we mounted, and
+// release the locks we returned (either using UnlockLockArray() or by
+// iterating over them and unlocking them).
+func GetVolumes(ctx *types.SystemContext, store storage.Store, mountLabel string, volumes []string, mounts []string, contextDir, workDir, tmpDir string) ([]specs.Mount, []string, []string, []string, []*lockfile.LockFile, error) {
+ unifiedMounts, mountedImages, intermediateMounts, overlayMounts, targetLocks, err := getMounts(ctx, store, mountLabel, mounts, contextDir, workDir, tmpDir)
if err != nil {
- return nil, mountedImages, nil, err
+ return nil, nil, nil, nil, nil, err
}
succeeded := false
defer func() {
if !succeeded {
+ for _, overlayMount := range overlayMounts {
+ if err := overlay.RemoveTemp(overlayMount); err != nil {
+ logrus.Debugf("unmounting overlay at %q: %v", overlayMount, err)
+ }
+ }
+ for _, intermediateMount := range intermediateMounts {
+ if err := mount.Unmount(intermediateMount); err != nil {
+ logrus.Debugf("unmounting intermediate mount point %q: %v", intermediateMount, err)
+ }
+ if err := os.Remove(intermediateMount); err != nil {
+ logrus.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err)
+ }
+ }
+ for _, image := range mountedImages {
+ if _, err := store.UnmountImage(image, false); err != nil {
+ logrus.Debugf("unmounting image %q: %v", image, err)
+ }
+ }
UnlockLockArray(targetLocks)
}
}()
volumeMounts, err := getVolumeMounts(volumes)
if err != nil {
- return nil, mountedImages, nil, err
+ return nil, nil, nil, nil, nil, err
}
for dest, mount := range volumeMounts {
if _, ok := unifiedMounts[dest]; ok {
- return nil, mountedImages, nil, fmt.Errorf("%v: %w", dest, errDuplicateDest)
+ return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", dest, errDuplicateDest)
}
unifiedMounts[dest] = mount
}
@@ -525,24 +675,53 @@ func GetVolumes(ctx *types.SystemContext, store storage.Store, volumes []string,
finalMounts = append(finalMounts, mount)
}
succeeded = true
- return finalMounts, mountedImages, targetLocks, nil
+ return finalMounts, mountedImages, intermediateMounts, overlayMounts, targetLocks, nil
}
-// getMounts takes user-provided input from the --mount flag and creates OCI
-// spec mounts.
-// buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
-// buildah run --mount type=tmpfs,target=/dev/shm ...
+// getMounts takes user-provided inputs from the --mount flag and returns a
+// slice of OCI spec mounts, a slice of mounted image IDs, a slice of other
+// mount locations, a slice of overlay mounts, and a slice of locks, or an
+// error.
//
-// If this function succeeds, the caller must unlock the returned *lockfile.LockFile s if any (when??).
-func getMounts(ctx *types.SystemContext, store storage.Store, mounts []string, contextDir string, workDir string) (map[string]specs.Mount, []string, []*lockfile.LockFile, error) {
+// buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
+// buildah run --mount type=cache,target=/var/cache ...
+// buildah run --mount type=tmpfs,target=/dev/shm ...
+//
+// The caller is expected to, after the command which uses the mounts exits,
+// unmount the overlay filesystems (if we mounted any), unmount the other
+// mounted filesystems and remove their mountpoints (if we provided any paths
+// to mountpoints), unmount any mounted images (if we provided the IDs of any),
+// and then unlock the locks we returned (either using UnlockLockArray() or by
+// iterating over them and unlocking them).
+func getMounts(ctx *types.SystemContext, store storage.Store, mountLabel string, mounts []string, contextDir, workDir, tmpDir string) (map[string]specs.Mount, []string, []string, []string, []*lockfile.LockFile, error) {
// If `type` is not set default to "bind"
mountType := define.TypeBind
- finalMounts := make(map[string]specs.Mount)
- mountedImages := make([]string, 0)
- targetLocks := make([]*lockfile.LockFile, 0)
+ finalMounts := make(map[string]specs.Mount, len(mounts))
+ mountedImages := make([]string, 0, len(mounts))
+ intermediateMounts := make([]string, 0, len(mounts))
+ overlayMounts := make([]string, 0, len(mounts))
+ targetLocks := make([]*lockfile.LockFile, 0, len(mounts))
succeeded := false
defer func() {
if !succeeded {
+ for _, overlayDir := range overlayMounts {
+ if err := overlay.RemoveTemp(overlayDir); err != nil {
+ logrus.Debugf("unmounting overlay mount at %q: %v", overlayDir, err)
+ }
+ }
+ for _, intermediateMount := range intermediateMounts {
+ if err := mount.Unmount(intermediateMount); err != nil {
+ logrus.Debugf("unmounting intermediate mount point %q: %v", intermediateMount, err)
+ }
+ if err := os.Remove(intermediateMount); err != nil {
+ logrus.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err)
+ }
+ }
+ for _, image := range mountedImages {
+ if _, err := store.UnmountImage(image, false); err != nil {
+ logrus.Debugf("unmounting image %q: %v", image, err)
+ }
+ }
UnlockLockArray(targetLocks)
}
}()
@@ -555,56 +734,67 @@ func getMounts(ctx *types.SystemContext, store storage.Store, mounts []string, c
for _, mount := range mounts {
tokens := strings.Split(mount, ",")
if len(tokens) < 2 {
- return nil, mountedImages, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax)
+ return nil, nil, nil, nil, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax)
}
for _, field := range tokens {
if strings.HasPrefix(field, "type=") {
kv := strings.Split(field, "=")
if len(kv) != 2 {
- return nil, mountedImages, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax)
+ return nil, nil, nil, nil, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax)
}
mountType = kv[1]
}
}
switch mountType {
case define.TypeBind:
- mount, image, err := GetBindMount(ctx, tokens, contextDir, store, "", nil, workDir)
+ mount, image, intermediateMount, overlayMount, err := GetBindMount(ctx, tokens, contextDir, store, mountLabel, nil, workDir, tmpDir)
if err != nil {
- return nil, mountedImages, nil, err
+ return nil, nil, nil, nil, nil, err
+ }
+ if image != "" {
+ mountedImages = append(mountedImages, image)
+ }
+ if intermediateMount != "" {
+ intermediateMounts = append(intermediateMounts, intermediateMount)
+ }
+ if overlayMount != "" {
+ overlayMounts = append(overlayMounts, overlayMount)
}
if _, ok := finalMounts[mount.Destination]; ok {
- return nil, mountedImages, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest)
+ return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest)
}
finalMounts[mount.Destination] = mount
- mountedImages = append(mountedImages, image)
case TypeCache:
- mount, tl, err := GetCacheMount(tokens, store, "", nil, workDir)
+ mount, intermediateMount, tl, err := GetCacheMount(tokens, nil, workDir, tmpDir)
if err != nil {
- return nil, mountedImages, nil, err
+ return nil, nil, nil, nil, nil, err
+ }
+ if intermediateMount != "" {
+ intermediateMounts = append(intermediateMounts, intermediateMount)
}
if tl != nil {
targetLocks = append(targetLocks, tl)
}
if _, ok := finalMounts[mount.Destination]; ok {
- return nil, mountedImages, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest)
+ return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest)
}
finalMounts[mount.Destination] = mount
case TypeTmpfs:
mount, err := GetTmpfsMount(tokens)
if err != nil {
- return nil, mountedImages, nil, err
+ return nil, nil, nil, nil, nil, err
}
if _, ok := finalMounts[mount.Destination]; ok {
- return nil, mountedImages, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest)
+ return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest)
}
finalMounts[mount.Destination] = mount
default:
- return nil, mountedImages, nil, fmt.Errorf("invalid filesystem type %q", mountType)
+ return nil, nil, nil, nil, nil, fmt.Errorf("invalid filesystem type %q", mountType)
}
}
succeeded = true
- return finalMounts, mountedImages, targetLocks, nil
+ return finalMounts, mountedImages, intermediateMounts, overlayMounts, targetLocks, nil
}
// GetTmpfsMount parses a single tmpfs mount entry from the --mount flag
diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go
index 666a0a01ae24..1320c6d020f0 100644
--- a/vendor/github.com/containers/buildah/pkg/overlay/overlay.go
+++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go
@@ -11,6 +11,7 @@ import (
"errors"
"github.com/containers/storage/pkg/idtools"
+ "github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/system"
"github.com/containers/storage/pkg/unshare"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -49,6 +50,12 @@ type Options struct {
RootUID int
// RootGID is not used yet but keeping it here for legacy reasons.
RootGID int
+ // Force overlay mounting and return a bind mount, rather than
+ // attempting to optimize by having the runtime actually mount and
+ // manage the overlay filesystem.
+ ForceMount bool
+ // MountLabel is a label to force for the overlay filesystem.
+ MountLabel string
}
// TempDir generates an overlay Temp directory in the container content
@@ -145,6 +152,12 @@ func mountWithMountProgram(mountProgram, overlayOptions, mergeDir string) error
return nil
}
+// mountNatively mounts an overlay at mergeDir using the kernel's mount()
+// system call.
+func mountNatively(overlayOptions, mergeDir string) error {
+ return mount.Mount("overlay", mergeDir, "overlay", overlayOptions)
+}
+
// Convert ":" to "\:", the path which will be overlay mounted need to be escaped
func escapeColon(source string) string {
return strings.ReplaceAll(source, ":", "\\:")
diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go
index 46d0c44aa1fc..aa6ce9ba6642 100644
--- a/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go
+++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/storage/pkg/unshare"
"github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/opencontainers/selinux/go-selinux/label"
)
// MountWithOptions creates a subdir of the contentDir based on the source directory
@@ -55,6 +56,9 @@ func MountWithOptions(contentDir, source, dest string, opts *Options) (mount spe
}
overlayOptions = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s,private", escapeColon(source), upperDir, workDir)
}
+ if opts.MountLabel != "" {
+ overlayOptions = overlayOptions + "," + label.FormatMountLabel("", opts.MountLabel)
+ }
mountProgram := findMountProgram(opts.GraphOpts)
if mountProgram != "" {
@@ -79,5 +83,17 @@ func MountWithOptions(contentDir, source, dest string, opts *Options) (mount spe
mount.Type = "overlay"
mount.Options = strings.Split(overlayOptions, ",")
+ if opts.ForceMount {
+ if err := mountNatively(overlayOptions, mergeDir); err != nil {
+ return mount, err
+ }
+
+ mount.Source = mergeDir
+ mount.Destination = dest
+ mount.Type = "bind"
+ mount.Options = []string{"bind", "slave"}
+ return mount, nil
+ }
+
return mount, nil
}
diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go
index fa5a1c2c9d21..e1eb32859d92 100644
--- a/vendor/github.com/containers/buildah/run.go
+++ b/vendor/github.com/containers/buildah/run.go
@@ -180,18 +180,22 @@ type RunOptions struct {
// RunMountArtifacts are the artifacts created when using a run mount.
type runMountArtifacts struct {
- // RunMountTargets are the run mount targets inside the container
+ // RunMountTargets are the run mount targets inside the container which should be removed
RunMountTargets []string
+ // RunOverlayDirs are overlay directories which will need to be cleaned up using overlay.RemoveTemp()
+ RunOverlayDirs []string
// TmpFiles are artifacts that need to be removed outside the container
TmpFiles []string
- // Any external images which were mounted inside container
+ // Any images which were mounted, which should be unmounted
MountedImages []string
- // Agents are the ssh agents started
+ // Agents are the ssh agents started, which should have their Shutdown() methods called
Agents []*sshagent.AgentServer
// SSHAuthSock is the path to the ssh auth sock inside the container
SSHAuthSock string
- // TargetLocks to be unlocked if there are any.
+ // Lock files, which should have their Unlock() methods called
TargetLocks []*lockfile.LockFile
+ // Intermediate mount points, which should be Unmount()ed and Removed()d
+ IntermediateMounts []string
}
// RunMountInfo are the available run mounts for this run
diff --git a/vendor/github.com/containers/buildah/run_common.go b/vendor/github.com/containers/buildah/run_common.go
index 0076b71eb9d7..6a60a2873550 100644
--- a/vendor/github.com/containers/buildah/run_common.go
+++ b/vendor/github.com/containers/buildah/run_common.go
@@ -27,7 +27,6 @@ import (
"github.com/containers/buildah/copier"
"github.com/containers/buildah/define"
"github.com/containers/buildah/internal"
- internalUtil "github.com/containers/buildah/internal/util"
"github.com/containers/buildah/internal/volumes"
"github.com/containers/buildah/pkg/overlay"
"github.com/containers/buildah/pkg/sshagent"
@@ -40,15 +39,14 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/subscriptions"
"github.com/containers/image/v5/types"
- imageTypes "github.com/containers/image/v5/types"
"github.com/containers/storage"
"github.com/containers/storage/pkg/fileutils"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/lockfile"
+ "github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/reexec"
"github.com/containers/storage/pkg/unshare"
- storageTypes "github.com/containers/storage/types"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
@@ -1304,7 +1302,9 @@ func init() {
reexec.Register(runUsingRuntimeCommand, runUsingRuntimeMain)
}
-// If this succeeds, the caller must call cleanupMounts().
+// If this succeeds, after the command which uses the spec finishes running,
+// the caller must call b.cleanupRunMounts() on the returned runMountArtifacts
+// structure.
func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes []string, compatBuiltinVolumes types.OptionalBool, volumeMounts []string, runFileMounts []string, runMountInfo runMountInfo) (*runMountArtifacts, error) {
// Start building a new list of mounts.
var mounts []specs.Mount
@@ -1363,14 +1363,16 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st
processGID: int(processGID),
}
// Get the list of mounts that are just for this Run() call.
- runMounts, mountArtifacts, err := b.runSetupRunMounts(mountPoint, runFileMounts, runMountInfo, idMaps)
+ runMounts, mountArtifacts, err := b.runSetupRunMounts(mountPoint, bundlePath, runFileMounts, runMountInfo, idMaps)
if err != nil {
return nil, err
}
succeeded := false
defer func() {
if !succeeded {
- volumes.UnlockLockArray(mountArtifacts.TargetLocks)
+ if err := b.cleanupRunMounts(mountPoint, mountArtifacts); err != nil {
+ b.Logger.Debugf("cleaning up run mounts: %v", err)
+ }
}
}()
// Add temporary copies of the contents of volume locations at the
@@ -1525,28 +1527,61 @@ func checkIfMountDestinationPreExists(root string, dest string) (bool, error) {
// runSetupRunMounts sets up mounts that exist only in this RUN, not in subsequent runs
//
-// If this function succeeds, the caller must unlock runMountArtifacts.TargetLocks (when??)
-func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources runMountInfo, idMaps IDMaps) ([]specs.Mount, *runMountArtifacts, error) {
- mountTargets := make([]string, 0, 10)
+// If this function succeeds, the caller must free the returned
+// runMountArtifacts by calling b.cleanupRunMounts() after the command being
+// executed with those mounts has finished.
+func (b *Builder) runSetupRunMounts(mountPoint, bundlePath string, mounts []string, sources runMountInfo, idMaps IDMaps) ([]specs.Mount, *runMountArtifacts, error) {
+ mountTargets := make([]string, 0, len(mounts))
tmpFiles := make([]string, 0, len(mounts))
- mountImages := make([]string, 0, 10)
+ mountImages := make([]string, 0, len(mounts))
+ intermediateMounts := make([]string, 0, len(mounts))
finalMounts := make([]specs.Mount, 0, len(mounts))
agents := make([]*sshagent.AgentServer, 0, len(mounts))
- sshCount := 0
defaultSSHSock := ""
targetLocks := []*lockfile.LockFile{}
+ var overlayDirs []string
succeeded := false
defer func() {
if !succeeded {
+ for _, agent := range agents {
+ servePath := agent.ServePath()
+ if err := agent.Shutdown(); err != nil {
+ b.Logger.Errorf("shutting down SSH agent at %q: %v", servePath, err)
+ }
+ }
+ for _, overlayDir := range overlayDirs {
+ if err := overlay.RemoveTemp(overlayDir); err != nil {
+ b.Logger.Error(err.Error())
+ }
+ }
+ for _, intermediateMount := range intermediateMounts {
+ if err := mount.Unmount(intermediateMount); err != nil {
+ b.Logger.Errorf("unmounting %q: %v", intermediateMount, err)
+ }
+ if err := os.Remove(intermediateMount); err != nil {
+ b.Logger.Errorf("removing should-be-empty directory %q: %v", intermediateMount, err)
+ }
+ }
+ for _, mountImage := range mountImages {
+ if _, err := b.store.UnmountImage(mountImage, false); err != nil {
+ b.Logger.Error(err.Error())
+ }
+ }
+ for _, tmpFile := range tmpFiles {
+ if err := os.Remove(tmpFile); err != nil && !errors.Is(err, os.ErrNotExist) {
+ b.Logger.Error(err.Error())
+ }
+ }
volumes.UnlockLockArray(targetLocks)
}
}()
for _, mount := range mounts {
var mountSpec *specs.Mount
var err error
- var envFile, image string
+ var envFile, image, bundleMountsDir, overlayDir, intermediateMount string
var agent *sshagent.AgentServer
var tl *lockfile.LockFile
+
tokens := strings.Split(mount, ",")
// If `type` is not set default to TypeBind
@@ -1574,29 +1609,37 @@ func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources
}
}
case "ssh":
- mountSpec, agent, err = b.getSSHMount(tokens, sshCount, sources.SSHSources, idMaps)
+ mountSpec, agent, err = b.getSSHMount(tokens, len(agents), sources.SSHSources, idMaps)
if err != nil {
return nil, nil, err
}
if mountSpec != nil {
finalMounts = append(finalMounts, *mountSpec)
- agents = append(agents, agent)
- if sshCount == 0 {
+ if len(agents) == 0 {
defaultSSHSock = mountSpec.Destination
}
- // Count is needed as the default destination of the ssh sock inside the container is /run/buildkit/ssh_agent.{i}
- sshCount++
+ agents = append(agents, agent)
}
case define.TypeBind:
- mountSpec, image, err = b.getBindMount(tokens, sources.SystemContext, sources.ContextDir, sources.StageMountPoints, idMaps, sources.WorkDir)
+ if bundleMountsDir == "" {
+ if bundleMountsDir, err = os.MkdirTemp(bundlePath, "mounts"); err != nil {
+ return nil, nil, err
+ }
+ }
+ mountSpec, image, intermediateMount, overlayDir, err = b.getBindMount(tokens, sources.SystemContext, sources.ContextDir, sources.StageMountPoints, idMaps, sources.WorkDir, bundleMountsDir)
if err != nil {
return nil, nil, err
}
- finalMounts = append(finalMounts, *mountSpec)
- // only perform cleanup if image was mounted ignore everything else
if image != "" {
mountImages = append(mountImages, image)
}
+ if overlayDir != "" {
+ overlayDirs = append(overlayDirs, overlayDir)
+ }
+ if intermediateMount != "" {
+ intermediateMounts = append(intermediateMounts, intermediateMount)
+ }
+ finalMounts = append(finalMounts, *mountSpec)
case "tmpfs":
mountSpec, err = b.getTmpfsMount(tokens, idMaps)
if err != nil {
@@ -1604,14 +1647,22 @@ func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources
}
finalMounts = append(finalMounts, *mountSpec)
case "cache":
- mountSpec, tl, err = b.getCacheMount(tokens, sources.StageMountPoints, idMaps, sources.WorkDir)
+ if bundleMountsDir == "" {
+ if bundleMountsDir, err = os.MkdirTemp(bundlePath, "mounts"); err != nil {
+ return nil, nil, err
+ }
+ }
+ mountSpec, intermediateMount, tl, err = b.getCacheMount(tokens, sources.StageMountPoints, idMaps, sources.WorkDir, bundleMountsDir)
if err != nil {
return nil, nil, err
}
- finalMounts = append(finalMounts, *mountSpec)
+ if intermediateMount != "" {
+ intermediateMounts = append(intermediateMounts, intermediateMount)
+ }
if tl != nil {
targetLocks = append(targetLocks, tl)
}
+ finalMounts = append(finalMounts, *mountSpec)
default:
return nil, nil, fmt.Errorf("invalid mount type %q", mountType)
}
@@ -1631,31 +1682,33 @@ func (b *Builder) runSetupRunMounts(mountPoint string, mounts []string, sources
}
succeeded = true
artifacts := &runMountArtifacts{
- RunMountTargets: mountTargets,
- TmpFiles: tmpFiles,
- Agents: agents,
- MountedImages: mountImages,
- SSHAuthSock: defaultSSHSock,
- TargetLocks: targetLocks,
+ RunMountTargets: mountTargets,
+ RunOverlayDirs: overlayDirs,
+ TmpFiles: tmpFiles,
+ Agents: agents,
+ MountedImages: mountImages,
+ SSHAuthSock: defaultSSHSock,
+ TargetLocks: targetLocks,
+ IntermediateMounts: intermediateMounts,
}
return finalMounts, artifacts, nil
}
-func (b *Builder) getBindMount(tokens []string, context *imageTypes.SystemContext, contextDir string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir string) (*specs.Mount, string, error) {
+func (b *Builder) getBindMount(tokens []string, sys *types.SystemContext, contextDir string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir, tmpDir string) (*specs.Mount, string, string, string, error) {
if contextDir == "" {
- return nil, "", errors.New("Context Directory for current run invocation is not configured")
+ return nil, "", "", "", errors.New("context directory for current run invocation is not configured")
}
var optionMounts []specs.Mount
- mount, image, err := volumes.GetBindMount(context, tokens, contextDir, b.store, b.MountLabel, stageMountPoints, workDir)
+ mount, image, intermediateMount, overlayMount, err := volumes.GetBindMount(sys, tokens, contextDir, b.store, b.MountLabel, stageMountPoints, workDir, tmpDir)
if err != nil {
- return nil, image, err
+ return nil, "", "", "", err
}
optionMounts = append(optionMounts, mount)
volumes, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps)
if err != nil {
- return nil, image, err
+ return nil, "", "", "", err
}
- return &volumes[0], image, nil
+ return &volumes[0], image, intermediateMount, overlayMount, nil
}
func (b *Builder) getTmpfsMount(tokens []string, idMaps IDMaps) (*specs.Mount, error) {
@@ -1932,52 +1985,53 @@ func (b *Builder) cleanupTempVolumes() {
}
// cleanupRunMounts cleans up run mounts so they only appear in this run.
-func (b *Builder) cleanupRunMounts(context *imageTypes.SystemContext, mountpoint string, artifacts *runMountArtifacts) error {
+func (b *Builder) cleanupRunMounts(mountpoint string, artifacts *runMountArtifacts) error {
for _, agent := range artifacts.Agents {
- err := agent.Shutdown()
- if err != nil {
+ servePath := agent.ServePath()
+ if err := agent.Shutdown(); err != nil {
+ return fmt.Errorf("shutting down SSH agent at %q: %v", servePath, err)
+ }
+ }
+ // clean up any overlays we mounted
+ for _, overlayDirectory := range artifacts.RunOverlayDirs {
+ if err := overlay.RemoveTemp(overlayDirectory); err != nil {
return err
}
}
-
- //cleanup any mounted images for this run
+ // unmount anything that needs unmounting
+ for _, intermediateMount := range artifacts.IntermediateMounts {
+ if err := mount.Unmount(intermediateMount); err != nil && !errors.Is(err, os.ErrNotExist) {
+ return fmt.Errorf("unmounting %q: %w", intermediateMount, err)
+ }
+ if err := os.Remove(intermediateMount); err != nil && !errors.Is(err, os.ErrNotExist) {
+ return fmt.Errorf("removing should-be-empty directory %q: %w", intermediateMount, err)
+ }
+ }
+ // unmount any images we mounted for this run
for _, image := range artifacts.MountedImages {
- if image != "" {
- // if flow hits here some image was mounted for this run
- i, err := internalUtil.LookupImage(context, b.store, image)
- if err == nil {
- // silently try to unmount and do nothing
- // if image is being used by something else
- _ = i.Unmount(false)
- }
- if errors.Is(err, storageTypes.ErrImageUnknown) {
- // Ignore only if ErrImageUnknown
- // Reason: Image is already unmounted do nothing
- continue
- }
- return err
+ if _, err := b.store.UnmountImage(image, false); err != nil {
+ logrus.Debugf("umounting image %q: %v", image, err)
}
}
+ // remove mount targets that were created for this run
opts := copier.RemoveOptions{
All: true,
}
for _, path := range artifacts.RunMountTargets {
- err := copier.Remove(mountpoint, path, opts)
- if err != nil {
- return err
+ if err := copier.Remove(mountpoint, path, opts); err != nil {
+ return fmt.Errorf("removing mount target %q %q: %w", mountpoint, path, err)
}
}
var prevErr error
for _, path := range artifacts.TmpFiles {
- err := os.Remove(path)
- if !errors.Is(err, os.ErrNotExist) {
+ if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
if prevErr != nil {
logrus.Error(prevErr)
}
- prevErr = err
+ prevErr = fmt.Errorf("removing temporary file: %w", err)
}
}
- // unlock if any locked files from this RUN statement
+ // unlock locks we took, most likely for cache mounts
volumes.UnlockLockArray(artifacts.TargetLocks)
return prevErr
}
diff --git a/vendor/github.com/containers/buildah/run_freebsd.go b/vendor/github.com/containers/buildah/run_freebsd.go
index 341c80c4fc63..cfc2dba0c9bc 100644
--- a/vendor/github.com/containers/buildah/run_freebsd.go
+++ b/vendor/github.com/containers/buildah/run_freebsd.go
@@ -276,7 +276,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
}
defer func() {
- if err := b.cleanupRunMounts(options.SystemContext, mountPoint, runArtifacts); err != nil {
+ if err := b.cleanupRunMounts(mountPoint, runArtifacts); err != nil {
options.Logger.Errorf("unable to cleanup run mounts %v", err)
}
}()
@@ -351,9 +351,12 @@ func setupSpecialMountSpecChanges(spec *spec.Spec, shmSize string) ([]specs.Moun
return spec.Mounts, nil
}
-// If this function succeeds and returns a non-nil *lockfile.LockFile, the caller must unlock it (when??).
-func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir string) (*spec.Mount, *lockfile.LockFile, error) {
- return nil, nil, errors.New("cache mounts not supported on freebsd")
+// If this succeeded, the caller would be expected to, after the command which
+// uses the mount exits, unmount the mounted filesystem and remove its
+// mountpoint (if we provided the path to its mountpoint), and release the lock
+// (if we took one).
+func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir, tmpDir string) (*specs.Mount, string, *lockfile.LockFile, error) {
+ return nil, "", nil, errors.New("cache mounts not supported on freebsd")
}
func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount, idMaps IDMaps) (mounts []specs.Mount, Err error) {
diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go
index 38a77ecf81a1..f32e1149012f 100644
--- a/vendor/github.com/containers/buildah/run_linux.go
+++ b/vendor/github.com/containers/buildah/run_linux.go
@@ -38,6 +38,7 @@ import (
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/lockfile"
+ "github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/stringid"
"github.com/containers/storage/pkg/unshare"
"github.com/docker/go-units"
@@ -492,7 +493,7 @@ rootless=%d
}
defer func() {
- if err := b.cleanupRunMounts(options.SystemContext, mountPoint, runArtifacts); err != nil {
+ if err := b.cleanupRunMounts(mountPoint, runArtifacts); err != nil {
options.Logger.Errorf("unable to cleanup run mounts %v", err)
}
}()
@@ -1112,7 +1113,7 @@ func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string,
RootGID: idMaps.rootGID,
UpperDirOptionFragment: upperDir,
WorkDirOptionFragment: workDir,
- GraphOpts: b.store.GraphOptions(),
+ GraphOpts: slices.Clone(b.store.GraphOptions()),
}
overlayMount, err := overlay.MountWithOptions(contentDir, host, container, &overlayOpts)
@@ -1121,7 +1122,7 @@ func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string,
}
// If chown true, add correct ownership to the overlay temp directories.
- if foundU {
+ if err == nil && foundU {
if err := chown.ChangeHostPathOwnership(contentDir, true, idMaps.processUID, idMaps.processGID); err != nil {
return specs.Mount{}, err
}
@@ -1379,24 +1380,39 @@ func checkIdsGreaterThan5(ids []specs.LinuxIDMapping) bool {
return false
}
-// If this function succeeds and returns a non-nil *lockfile.LockFile, the caller must unlock it (when??).
-func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir string) (*specs.Mount, *lockfile.LockFile, error) {
+// Returns a Mount to add to the runtime spec's list of mounts, an optional
+// path of a mounted filesystem, unmounted, and an optional lock, or an error.
+//
+// The caller is expected to, after the command which uses the mount exits,
+// unmount the mounted filesystem (if we provided the path to its mountpoint)
+// and remove its mountpoint, , and release the lock (if we took one).
+func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir, tmpDir string) (*specs.Mount, string, *lockfile.LockFile, error) {
var optionMounts []specs.Mount
- mount, targetLock, err := volumes.GetCacheMount(tokens, b.store, b.MountLabel, stageMountPoints, workDir)
+ optionMount, intermediateMount, targetLock, err := volumes.GetCacheMount(tokens, stageMountPoints, workDir, tmpDir)
if err != nil {
- return nil, nil, err
+ return nil, "", nil, err
}
succeeded := false
defer func() {
- if !succeeded && targetLock != nil {
- targetLock.Unlock()
+ if !succeeded {
+ if intermediateMount != "" {
+ if err := mount.Unmount(intermediateMount); err != nil {
+ b.Logger.Debugf("unmounting %q: %v", intermediateMount, err)
+ }
+ if err := os.Remove(intermediateMount); err != nil {
+ b.Logger.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err)
+ }
+ }
+ if targetLock != nil {
+ targetLock.Unlock()
+ }
}
}()
- optionMounts = append(optionMounts, mount)
+ optionMounts = append(optionMounts, optionMount)
volumes, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps)
if err != nil {
- return nil, nil, err
+ return nil, "", nil, err
}
succeeded = true
- return &volumes[0], targetLock, nil
+ return &volumes[0], intermediateMount, targetLock, nil
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 2e1419cd1c02..06d19dcc7b3a 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -140,7 +140,7 @@ github.com/containernetworking/cni/pkg/version
# github.com/containernetworking/plugins v1.5.1
## explicit; go 1.20
github.com/containernetworking/plugins/pkg/ns
-# github.com/containers/buildah v1.37.5
+# github.com/containers/buildah v1.37.6
## explicit; go 1.21.0
github.com/containers/buildah
github.com/containers/buildah/bind
@@ -153,6 +153,7 @@ github.com/containers/buildah/internal
github.com/containers/buildah/internal/config
github.com/containers/buildah/internal/mkcw
github.com/containers/buildah/internal/mkcw/types
+github.com/containers/buildah/internal/open
github.com/containers/buildah/internal/parse
github.com/containers/buildah/internal/sbom
github.com/containers/buildah/internal/tmpdir
--
2.46.0