File 0007-bsc1221916-update-to-patched-buildkit-version-to-fix.patch of Package docker-stable
From b760758157cd0d00f46f37f86a9cbee7810cb666 Mon Sep 17 00:00:00 2001
From: Aleksa Sarai <cyphar@cyphar.com>
Date: Thu, 2 May 2024 22:50:23 +1000
Subject: [PATCH 07/11] bsc1221916: update to patched buildkit version to fix
symlink resolution
SUSE-Bugs: https://bugzilla.suse.com/show_bug.cgi?id=1221916
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
---
builder/builder-next/worker/worker.go | 2 +-
vendor.mod | 2 +-
vendor.sum | 4 +-
.../buildkit/cache/contenthash/checksum.go | 393 ++++++++++--------
.../moby/buildkit/cache/contenthash/path.go | 161 +++----
vendor/modules.txt | 4 +-
6 files changed, 314 insertions(+), 252 deletions(-)
diff --git a/builder/builder-next/worker/worker.go b/builder/builder-next/worker/worker.go
index 64d7b9131b16..7b40ac63ce7f 100644
--- a/builder/builder-next/worker/worker.go
+++ b/builder/builder-next/worker/worker.go
@@ -50,7 +50,7 @@ import (
)
func init() {
- version.Version = "v0.11.7+cd804dd86389"
+ version.Version = "v0.11.7+6b814972ef19"
}
const labelCreatedAt = "buildkit/createdat"
diff --git a/vendor.mod b/vendor.mod
index 2eb13746cacd..021d62b21d19 100644
--- a/vendor.mod
+++ b/vendor.mod
@@ -99,7 +99,7 @@ require (
)
// github.com/SUSE/buildkit suse-stable-v24.0.9
-replace github.com/moby/buildkit => github.com/SUSE/buildkit v0.0.0-20241218053907-cd804dd86389
+replace github.com/moby/buildkit => github.com/SUSE/buildkit v0.0.0-20241218053911-6b814972ef19
require (
cloud.google.com/go v0.102.1 // indirect
diff --git a/vendor.sum b/vendor.sum
index 716245c80413..4bdbbeb3f073 100644
--- a/vendor.sum
+++ b/vendor.sum
@@ -141,8 +141,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 h1:vX+gnvBc56EbWYrmlhYbFYRaeikAke1GL84N4BEYOFE=
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91/go.mod h1:cDLGBht23g0XQdLjzn6xOGXDkLK182YfINAaZEQLCHQ=
-github.com/SUSE/buildkit v0.0.0-20241218053907-cd804dd86389 h1:EKne0CAOXpf1QuZ3+jj7PTpOtSn+q1Yz5H6pAwrOktY=
-github.com/SUSE/buildkit v0.0.0-20241218053907-cd804dd86389/go.mod h1:bMQDryngJKGvJ/ZuRFhrejurbvYSv3NkGCheQ59X4AM=
+github.com/SUSE/buildkit v0.0.0-20241218053911-6b814972ef19 h1:3gfqJcXxLASvlAfgd+TFPrrhNrM+O26HplOhi3BNT+A=
+github.com/SUSE/buildkit v0.0.0-20241218053911-6b814972ef19/go.mod h1:bMQDryngJKGvJ/ZuRFhrejurbvYSv3NkGCheQ59X4AM=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
diff --git a/vendor/github.com/moby/buildkit/cache/contenthash/checksum.go b/vendor/github.com/moby/buildkit/cache/contenthash/checksum.go
index dcf424a6b4fc..13a74be24c4e 100644
--- a/vendor/github.com/moby/buildkit/cache/contenthash/checksum.go
+++ b/vendor/github.com/moby/buildkit/cache/contenthash/checksum.go
@@ -10,6 +10,7 @@ import (
"path/filepath"
"strings"
"sync"
+ "sync/atomic"
iradix "github.com/hashicorp/go-immutable-radix"
"github.com/hashicorp/golang-lru/simplelru"
@@ -288,7 +289,7 @@ func keyPath(p string) string {
// HandleChange notifies the source about a modification operation
func (cc *cacheContext) HandleChange(kind fsutil.ChangeKind, p string, fi os.FileInfo, err error) (retErr error) {
p = keyPath(p)
- k := convertPathToKey([]byte(p))
+ k := convertPathToKey(p)
deleteDir := func(cr *CacheRecord) {
if cr.Type == CacheRecordTypeDir {
@@ -367,7 +368,7 @@ func (cc *cacheContext) HandleChange(kind fsutil.ChangeKind, p string, fi os.Fil
// note that the source may be called later because data writing is async
if fi.Mode()&os.ModeSymlink == 0 && stat.Linkname != "" {
ln := path.Join("/", filepath.ToSlash(stat.Linkname))
- v, ok := cc.txn.Get(convertPathToKey([]byte(ln)))
+ v, ok := cc.txn.Get(convertPathToKey(ln))
if ok {
cp := *v.(*CacheRecord)
cr = &cp
@@ -405,7 +406,7 @@ func (cc *cacheContext) Checksum(ctx context.Context, mountable cache.Mountable,
defer m.clean()
if !opts.Wildcard && len(opts.IncludePatterns) == 0 && len(opts.ExcludePatterns) == 0 {
- return cc.checksumFollow(ctx, m, p, opts.FollowLinks)
+ return cc.lazyChecksum(ctx, m, p, opts.FollowLinks)
}
includedPaths, err := cc.includedPaths(ctx, m, p, opts)
@@ -416,7 +417,7 @@ func (cc *cacheContext) Checksum(ctx context.Context, mountable cache.Mountable,
if opts.FollowLinks {
for i, w := range includedPaths {
if w.record.Type == CacheRecordTypeSymlink {
- dgst, err := cc.checksumFollow(ctx, m, w.path, opts.FollowLinks)
+ dgst, err := cc.lazyChecksum(ctx, m, w.path, opts.FollowLinks)
if err != nil {
return "", err
}
@@ -443,30 +444,6 @@ func (cc *cacheContext) Checksum(ctx context.Context, mountable cache.Mountable,
return digester.Digest(), nil
}
-func (cc *cacheContext) checksumFollow(ctx context.Context, m *mount, p string, follow bool) (digest.Digest, error) {
- const maxSymlinkLimit = 255
- i := 0
- for {
- if i > maxSymlinkLimit {
- return "", errors.Errorf("too many symlinks: %s", p)
- }
- cr, err := cc.checksumNoFollow(ctx, m, p)
- if err != nil {
- return "", err
- }
- if cr.Type == CacheRecordTypeSymlink && follow {
- link := cr.Linkname
- if !path.IsAbs(cr.Linkname) {
- link = path.Join(path.Dir(p), link)
- }
- i++
- p = link
- } else {
- return cr.Digest, nil
- }
- }
-}
-
func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, opts ChecksumOpts) ([]*includedPath, error) {
cc.mu.Lock()
defer cc.mu.Unlock()
@@ -476,12 +453,12 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
}
root := cc.tree.Root()
- scan, err := cc.needsScan(root, "")
+ scan, err := cc.needsScan(root, "", false)
if err != nil {
return nil, err
}
if scan {
- if err := cc.scanPath(ctx, m, ""); err != nil {
+ if err := cc.scanPath(ctx, m, "", false); err != nil {
return nil, err
}
}
@@ -534,13 +511,13 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
}
} else {
origPrefix = p
- k = convertPathToKey([]byte(origPrefix))
+ k = convertPathToKey(origPrefix)
// We need to resolve symlinks here, in case the base path
// involves a symlink. That will match fsutil behavior of
// calling functions such as stat and walk.
var cr *CacheRecord
- k, cr, err = getFollowLinks(root, k, true)
+ k, cr, err = getFollowLinks(root, k, false)
if err != nil {
return nil, err
}
@@ -552,7 +529,7 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
iter.SeekLowerBound(append(append([]byte{}, k...), 0))
}
- resolvedPrefix = string(convertKeyToPath(k))
+ resolvedPrefix = convertKeyToPath(k)
} else {
k, _, keyOk = iter.Next()
}
@@ -563,7 +540,7 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
)
for keyOk {
- fn := string(convertKeyToPath(k))
+ fn := convertKeyToPath(k)
// Convert the path prefix from what we found in the prefix
// tree to what the argument specified.
@@ -749,36 +726,12 @@ func wildcardPrefix(root *iradix.Node, p string) (string, []byte, bool, error) {
return "", nil, false, nil
}
- linksWalked := 0
- k, cr, err := getFollowLinksWalk(root, convertPathToKey([]byte(d1)), true, &linksWalked)
+ // Only resolve the final symlink component if there are components in the
+ // wildcard segment.
+ k, cr, err := getFollowLinks(root, convertPathToKey(d1), d2 != "")
if err != nil {
return "", k, false, err
}
-
- if d2 != "" && cr != nil && cr.Type == CacheRecordTypeSymlink {
- // getFollowLinks only handles symlinks in path
- // components before the last component, so
- // handle last component in d1 specially.
- resolved := string(convertKeyToPath(k))
- for {
- v, ok := root.Get(k)
-
- if !ok {
- return d1, k, false, nil
- }
- if v.(*CacheRecord).Type != CacheRecordTypeSymlink {
- break
- }
-
- linksWalked++
- if linksWalked > 255 {
- return "", k, false, errors.Errorf("too many links")
- }
-
- resolved := cleanLink(resolved, v.(*CacheRecord).Linkname)
- k = convertPathToKey([]byte(resolved))
- }
- }
return d1, k, cr != nil, nil
}
@@ -814,19 +767,22 @@ func containsWildcards(name string) bool {
return false
}
-func (cc *cacheContext) checksumNoFollow(ctx context.Context, m *mount, p string) (*CacheRecord, error) {
+func (cc *cacheContext) lazyChecksum(ctx context.Context, m *mount, p string, followTrailing bool) (digest.Digest, error) {
p = keyPath(p)
+ k := convertPathToKey(p)
+ // Try to look up the path directly without doing a scan.
cc.mu.RLock()
if cc.txn == nil {
root := cc.tree.Root()
cc.mu.RUnlock()
- v, ok := root.Get(convertPathToKey([]byte(p)))
- if ok {
- cr := v.(*CacheRecord)
- if cr.Digest != "" {
- return cr, nil
- }
+
+ _, cr, err := getFollowLinks(root, k, followTrailing)
+ if err != nil {
+ return "", err
+ }
+ if cr != nil && cr.Digest != "" {
+ return cr.Digest, nil
}
} else {
cc.mu.RUnlock()
@@ -846,7 +802,11 @@ func (cc *cacheContext) checksumNoFollow(ctx context.Context, m *mount, p string
}
}()
- return cc.lazyChecksum(ctx, m, p)
+ cr, err := cc.scanChecksum(ctx, m, p, followTrailing)
+ if err != nil {
+ return "", err
+ }
+ return cr.Digest, nil
}
func (cc *cacheContext) commitActiveTransaction() {
@@ -854,7 +814,7 @@ func (cc *cacheContext) commitActiveTransaction() {
addParentToMap(d, cc.dirtyMap)
}
for d := range cc.dirtyMap {
- k := convertPathToKey([]byte(d))
+ k := convertPathToKey(d)
if _, ok := cc.txn.Get(k); ok {
cc.txn.Insert(k, &CacheRecord{Type: CacheRecordTypeDir})
}
@@ -865,21 +825,21 @@ func (cc *cacheContext) commitActiveTransaction() {
cc.txn = nil
}
-func (cc *cacheContext) lazyChecksum(ctx context.Context, m *mount, p string) (*CacheRecord, error) {
+func (cc *cacheContext) scanChecksum(ctx context.Context, m *mount, p string, followTrailing bool) (*CacheRecord, error) {
root := cc.tree.Root()
- scan, err := cc.needsScan(root, p)
+ scan, err := cc.needsScan(root, p, followTrailing)
if err != nil {
return nil, err
}
if scan {
- if err := cc.scanPath(ctx, m, p); err != nil {
+ if err := cc.scanPath(ctx, m, p, followTrailing); err != nil {
return nil, err
}
}
- k := convertPathToKey([]byte(p))
+ k := convertPathToKey(p)
txn := cc.tree.Txn()
root = txn.Root()
- cr, updated, err := cc.checksum(ctx, root, txn, m, k, true)
+ cr, updated, err := cc.checksum(ctx, root, txn, m, k, followTrailing)
if err != nil {
return nil, err
}
@@ -888,9 +848,9 @@ func (cc *cacheContext) lazyChecksum(ctx context.Context, m *mount, p string) (*
return cr, err
}
-func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, follow bool) (*CacheRecord, bool, error) {
+func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, followTrailing bool) (*CacheRecord, bool, error) {
origk := k
- k, cr, err := getFollowLinks(root, k, follow)
+ k, cr, err := getFollowLinks(root, k, followTrailing)
if err != nil {
return nil, false, err
}
@@ -916,7 +876,9 @@ func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *ir
}
h.Write(bytes.TrimPrefix(subk, k))
- subcr, _, err := cc.checksum(ctx, root, txn, m, subk, true)
+ // We do not follow trailing links when checksumming a directory's
+ // contents.
+ subcr, _, err := cc.checksum(ctx, root, txn, m, subk, false)
if err != nil {
return nil, false, err
}
@@ -933,7 +895,7 @@ func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *ir
dgst = digest.NewDigest(digest.SHA256, h)
default:
- p := string(convertKeyToPath(bytes.TrimSuffix(k, []byte{0})))
+ p := convertKeyToPath(bytes.TrimSuffix(k, []byte{0}))
target, err := m.mount(ctx)
if err != nil {
@@ -965,42 +927,82 @@ func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *ir
return cr2, true, nil
}
-// needsScan returns false if path is in the tree or a parent path is in tree
-// and subpath is missing
-func (cc *cacheContext) needsScan(root *iradix.Node, p string) (bool, error) {
- var linksWalked int
- return cc.needsScanFollow(root, p, &linksWalked)
+// pathSet is a set of path prefixes that can be used to see if a given path is
+// lexically a child of any path in the set. All paths provided to this set
+// MUST be absolute and use / as the separator.
+type pathSet struct {
+ // prefixes contains paths of the form "/a/b/", so that we correctly detect
+ // /a/b as being a parent of /a/b/c but not /a/bc.
+ prefixes []string
}
-func (cc *cacheContext) needsScanFollow(root *iradix.Node, p string, linksWalked *int) (bool, error) {
- if p == "/" {
- p = ""
- }
- v, ok := root.Get(convertPathToKey([]byte(p)))
- if !ok {
- if p == "" {
- return true, nil
+// add a path to the set. This is a no-op if includes(path) == true.
+func (s *pathSet) add(p string) {
+ // Ensure the path is absolute and clean.
+ p = path.Join("/", p)
+ if !s.includes(p) {
+ if p != "/" {
+ p += "/"
}
- return cc.needsScanFollow(root, path.Clean(path.Dir(p)), linksWalked)
+ s.prefixes = append(s.prefixes, p)
+ }
+}
+
+// includes returns true iff there is a path in the pathSet which is a lexical
+// parent of the given path. The provided path MUST be an absolute path and
+// MUST NOT contain any ".." components, as they will be path.Clean'd.
+func (s pathSet) includes(p string) bool {
+ // Ensure the path is absolute and clean.
+ p = path.Join("/", p)
+ if p != "/" {
+ p += "/"
}
- cr := v.(*CacheRecord)
- if cr.Type == CacheRecordTypeSymlink {
- if *linksWalked > 255 {
- return false, errTooManyLinks
+ for _, prefix := range s.prefixes {
+ if strings.HasPrefix(p, prefix) {
+ return true
}
- *linksWalked++
- link := path.Clean(cr.Linkname)
- if !path.IsAbs(cr.Linkname) {
- link = path.Join("/", path.Dir(p), link)
+ }
+ return false
+}
+
+// needsScan returns false if path is in the tree or a parent path is in tree
+// and subpath is missing.
+func (cc *cacheContext) needsScan(root *iradix.Node, path string, followTrailing bool) (bool, error) {
+ var (
+ goodPaths pathSet
+ hasParentInTree bool
+ )
+ k := convertPathToKey(path)
+ _, cr, err := getFollowLinksCallback(root, k, followTrailing, func(subpath string, cr *CacheRecord) error {
+ // If we found a path that exists in the cache, add it to the set of
+ // known-scanned paths. Otherwise, verify whether the not-found subpath
+ // is inside a known-scanned path (we might have hit a "..", taking us
+ // out of the scanned paths, or we might hit a non-existent path inside
+ // a scanned path). getFollowLinksCallback iterates left-to-right, so
+ // we will always hit ancestors first.
+ if cr != nil {
+ hasParentInTree = cr.Type != CacheRecordTypeSymlink
+ goodPaths.add(subpath)
+ } else {
+ hasParentInTree = goodPaths.includes(subpath)
}
- return cc.needsScanFollow(root, link, linksWalked)
+ return nil
+ })
+ if err != nil {
+ return false, err
}
- return false, nil
+ return cr == nil && !hasParentInTree, nil
}
-func (cc *cacheContext) scanPath(ctx context.Context, m *mount, p string) (retErr error) {
+// Only used by TestNeedScanChecksumRegression to make sure scanPath is not
+// called for paths we have already scanned.
+var (
+ scanCounterEnable bool
+ scanCounter atomic.Uint64
+)
+
+func (cc *cacheContext) scanPath(ctx context.Context, m *mount, p string, followTrailing bool) (retErr error) {
p = path.Join("/", p)
- d, _ := path.Split(p)
mp, err := m.mount(ctx)
if err != nil {
@@ -1010,33 +1012,42 @@ func (cc *cacheContext) scanPath(ctx context.Context, m *mount, p string) (retEr
n := cc.tree.Root()
txn := cc.tree.Txn()
- parentPath, err := rootPath(mp, filepath.FromSlash(d), func(p, link string) error {
+ resolvedPath, err := rootPath(mp, filepath.FromSlash(p), followTrailing, func(p, link string) error {
cr := &CacheRecord{
Type: CacheRecordTypeSymlink,
Linkname: filepath.ToSlash(link),
}
- k := []byte(path.Join("/", filepath.ToSlash(p)))
- k = convertPathToKey(k)
- txn.Insert(k, cr)
+ p = path.Join("/", filepath.ToSlash(p))
+ txn.Insert(convertPathToKey(p), cr)
return nil
})
if err != nil {
return err
}
- err = filepath.Walk(parentPath, func(itemPath string, fi os.FileInfo, err error) error {
+ // Scan the parent directory of the path we resolved, unless we're at the
+ // root (in which case we scan the root).
+ scanPath := filepath.Dir(resolvedPath)
+ if !strings.HasPrefix(filepath.ToSlash(scanPath)+"/", filepath.ToSlash(mp)+"/") {
+ scanPath = resolvedPath
+ }
+
+ err = filepath.Walk(scanPath, func(itemPath string, fi os.FileInfo, err error) error {
+ if scanCounterEnable {
+ scanCounter.Add(1)
+ }
if err != nil {
+ // If the root doesn't exist, ignore the error.
+ if itemPath == scanPath && errors.Is(err, os.ErrNotExist) {
+ return nil
+ }
return errors.Wrapf(err, "failed to walk %s", itemPath)
}
rel, err := filepath.Rel(mp, itemPath)
if err != nil {
return err
}
- k := []byte(path.Join("/", filepath.ToSlash(rel)))
- if string(k) == "/" {
- k = []byte{}
- }
- k = convertPathToKey(k)
+ k := convertPathToKey(keyPath(rel))
if _, ok := n.Get(k); !ok {
cr := &CacheRecord{
Type: CacheRecordTypeFile,
@@ -1069,55 +1080,118 @@ func (cc *cacheContext) scanPath(ctx context.Context, m *mount, p string) (retEr
return nil
}
-func getFollowLinks(root *iradix.Node, k []byte, follow bool) ([]byte, *CacheRecord, error) {
- var linksWalked int
- return getFollowLinksWalk(root, k, follow, &linksWalked)
+// followLinksCallback is called after we try to resolve each element. If the
+// path was not found, cr is nil.
+type followLinksCallback func(path string, cr *CacheRecord) error
+
+// getFollowLinks is shorthand for getFollowLinksCallback(..., nil).
+func getFollowLinks(root *iradix.Node, k []byte, followTrailing bool) ([]byte, *CacheRecord, error) {
+ return getFollowLinksCallback(root, k, followTrailing, nil)
}
-func getFollowLinksWalk(root *iradix.Node, k []byte, follow bool, linksWalked *int) ([]byte, *CacheRecord, error) {
+// getFollowLinksCallback looks up the requested key, fully resolving any
+// symlink components encountered. The implementation is heavily based on
+// <https://github.com/cyphar/filepath-securejoin>.
+//
+// followTrailing indicates whether the *final component* of the path should be
+// resolved (effectively O_PATH|O_NOFOLLOW). Note that (in contrast to some
+// Linux APIs), followTrailing is obeyed even if the key has a trailing slash
+// (though paths like "foo/link/." will cause the link to be resolved).
+//
+// cb is a callback that is called for each path component encountered during
+// path resolution (after the path component is looked up in the cache). This
+// means for a path like /a/b/c, the callback will be called for at least
+//
+// {/, /a, /a/b, /a/b/c}
+//
+// Note that if any of the components are symlinks, the paths will depend on
+// the symlink contents and there will be more callbacks. If the requested key
+// has a trailing slash, the callback will also be called for the final
+// trailing-slash lookup (/a/b/c/ in the above example). Note that
+// getFollowLinksCallback will try to look up the original key directly first
+// and the callback is not called for this first lookup.
+func getFollowLinksCallback(root *iradix.Node, k []byte, followTrailing bool, cb followLinksCallback) ([]byte, *CacheRecord, error) {
v, ok := root.Get(k)
- if ok {
+ if ok && (!followTrailing || v.(*CacheRecord).Type != CacheRecordTypeSymlink) {
return k, v.(*CacheRecord), nil
}
- if !follow || len(k) == 0 {
+ if len(k) == 0 {
return k, nil, nil
}
- dir, file := splitKey(k)
+ var (
+ currentPath = "/"
+ remainingPath = convertKeyToPath(k)
+ linksWalked int
+ cr *CacheRecord
+ )
+ // Trailing slashes are significant for the cache, but path.Clean strips
+ // them. We only care about the slash for the final lookup.
+ remainingPath, hadTrailingSlash := strings.CutSuffix(remainingPath, "/")
+ for remainingPath != "" {
+ // Get next component.
+ var part string
+ if i := strings.IndexRune(remainingPath, '/'); i == -1 {
+ part, remainingPath = remainingPath, ""
+ } else {
+ part, remainingPath = remainingPath[:i], remainingPath[i+1:]
+ }
- k, parent, err := getFollowLinksWalk(root, dir, follow, linksWalked)
- if err != nil {
- return nil, nil, err
- }
- if parent != nil {
- if parent.Type == CacheRecordTypeSymlink {
- *linksWalked++
- if *linksWalked > 255 {
- return nil, nil, errors.Errorf("too many links")
+ // Apply the component to the path. Since it is a single component, and
+ // our current path contains no symlinks, we can just apply it
+ // leixically.
+ nextPath := keyPath(path.Join("/", currentPath, part))
+ // In contrast to rootPath, we don't skip lookups for no-op components
+ // or / because we need to call the callback for every path component
+ // we hit (including /) and we need to make sure that the CacheRecord
+ // we return is correct after every iteration.
+
+ cr = nil
+ v, ok := root.Get(convertPathToKey(nextPath))
+ if ok {
+ cr = v.(*CacheRecord)
+ }
+ if cb != nil {
+ if err := cb(nextPath, cr); err != nil {
+ return nil, nil, err
}
+ }
+ if !ok || cr.Type != CacheRecordTypeSymlink {
+ currentPath = nextPath
+ continue
+ }
+ if !followTrailing && remainingPath == "" {
+ currentPath = nextPath
+ break
+ }
- link := cleanLink(string(convertKeyToPath(dir)), parent.Linkname)
- return getFollowLinksWalk(root, append(convertPathToKey([]byte(link)), file...), follow, linksWalked)
+ linksWalked++
+ if linksWalked > maxSymlinkLimit {
+ return nil, nil, errTooManyLinks
}
- }
- k = append(k, file...)
- v, ok = root.Get(k)
- if ok {
- return k, v.(*CacheRecord), nil
- }
- return k, nil, nil
-}
-func cleanLink(dir, linkname string) string {
- dirPath := path.Clean(dir)
- if dirPath == "." || dirPath == "/" {
- dirPath = ""
+ remainingPath = cr.Linkname + "/" + remainingPath
+ if path.IsAbs(cr.Linkname) {
+ currentPath = "/"
+ }
}
- link := path.Clean(linkname)
- if !path.IsAbs(link) {
- return path.Join("/", path.Join(path.Dir(dirPath), link))
+ // We've already looked up the final component. However, if there was a
+ // trailing slash in the original path, we need to do the lookup again with
+ // the slash applied.
+ if hadTrailingSlash {
+ cr = nil
+ currentPath += "/"
+ v, ok := root.Get(convertPathToKey(currentPath))
+ if ok {
+ cr = v.(*CacheRecord)
+ }
+ if cb != nil {
+ if err := cb(currentPath, cr); err != nil {
+ return nil, nil, err
+ }
+ }
}
- return link
+ return convertPathToKey(currentPath), cr, nil
}
func prepareDigest(fp, p string, fi os.FileInfo) (digest.Digest, error) {
@@ -1174,25 +1248,10 @@ func poolsCopy(dst io.Writer, src io.Reader) (written int64, err error) {
return
}
-func convertPathToKey(p []byte) []byte {
+func convertPathToKey(p string) []byte {
return bytes.Replace([]byte(p), []byte("/"), []byte{0}, -1)
}
-func convertKeyToPath(p []byte) []byte {
- return bytes.Replace([]byte(p), []byte{0}, []byte("/"), -1)
-}
-
-func splitKey(k []byte) ([]byte, []byte) {
- foundBytes := false
- i := len(k) - 1
- for {
- if i <= 0 || foundBytes && k[i] == 0 {
- break
- }
- if k[i] != 0 {
- foundBytes = true
- }
- i--
- }
- return append([]byte{}, k[:i]...), k[i:]
+func convertKeyToPath(p []byte) string {
+ return string(bytes.Replace(p, []byte{0}, []byte("/"), -1))
}
diff --git a/vendor/github.com/moby/buildkit/cache/contenthash/path.go b/vendor/github.com/moby/buildkit/cache/contenthash/path.go
index 42b7fd8349c7..ae950f713241 100644
--- a/vendor/github.com/moby/buildkit/cache/contenthash/path.go
+++ b/vendor/github.com/moby/buildkit/cache/contenthash/path.go
@@ -1,108 +1,111 @@
+// This code mostly comes from <https://github.com/cyphar/filepath-securejoin>.
+
+// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
+// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
package contenthash
import (
"os"
"path/filepath"
+ "strings"
"github.com/pkg/errors"
)
-var (
- errTooManyLinks = errors.New("too many links")
-)
+var errTooManyLinks = errors.New("too many links")
+
+const maxSymlinkLimit = 255
type onSymlinkFunc func(string, string) error
-// rootPath joins a path with a root, evaluating and bounding any
-// symlink to the root directory.
-// This is containerd/continuity/fs RootPath implementation with a callback on
-// resolving the symlink.
-func rootPath(root, path string, cb onSymlinkFunc) (string, error) {
- if path == "" {
+// rootPath joins a path with a root, evaluating and bounding any symlink to
+// the root directory. This is a slightly modified version of SecureJoin from
+// github.com/cyphar/filepath-securejoin, with a callback which we call after
+// each symlink resolution.
+func rootPath(root, unsafePath string, followTrailing bool, cb onSymlinkFunc) (string, error) {
+ if unsafePath == "" {
return root, nil
}
- var linksWalked int // to protect against cycles
- for {
- i := linksWalked
- newpath, err := walkLinks(root, path, &linksWalked, cb)
- if err != nil {
- return "", err
- }
- path = newpath
- if i == linksWalked {
- newpath = filepath.Join("/", newpath)
- if path == newpath {
- return filepath.Join(root, newpath), nil
- }
- path = newpath
- }
- }
-}
-func walkLink(root, path string, linksWalked *int, cb onSymlinkFunc) (newpath string, islink bool, err error) {
- if *linksWalked > 255 {
- return "", false, errTooManyLinks
- }
+ unsafePath = filepath.FromSlash(unsafePath)
+ var (
+ currentPath string
+ linksWalked int
+ )
+ for unsafePath != "" {
+ // Windows-specific: remove any drive letters from the path.
+ if v := filepath.VolumeName(unsafePath); v != "" {
+ unsafePath = unsafePath[len(v):]
+ }
- path = filepath.Join("/", path)
- if path == "/" {
- return path, false, nil
- }
- realPath := filepath.Join(root, path)
+ // Remove any unnecessary trailing slashes.
+ unsafePath = strings.TrimSuffix(unsafePath, string(filepath.Separator))
- fi, err := os.Lstat(realPath)
- if err != nil {
- // If path does not yet exist, treat as non-symlink
- if errors.Is(err, os.ErrNotExist) {
- return path, false, nil
+ // Get the next path component.
+ var part string
+ if i := strings.IndexRune(unsafePath, filepath.Separator); i == -1 {
+ part, unsafePath = unsafePath, ""
+ } else {
+ part, unsafePath = unsafePath[:i], unsafePath[i+1:]
}
- return "", false, err
- }
- if fi.Mode()&os.ModeSymlink == 0 {
- return path, false, nil
- }
- newpath, err = os.Readlink(realPath)
- if err != nil {
- return "", false, err
- }
- if cb != nil {
- if err := cb(path, newpath); err != nil {
- return "", false, err
- }
- }
- *linksWalked++
- return newpath, true, nil
-}
-func walkLinks(root, path string, linksWalked *int, cb onSymlinkFunc) (string, error) {
- switch dir, file := filepath.Split(path); {
- case dir == "":
- newpath, _, err := walkLink(root, file, linksWalked, cb)
- return newpath, err
- case file == "":
- if os.IsPathSeparator(dir[len(dir)-1]) {
- if dir == "/" {
- return dir, nil
- }
- return walkLinks(root, dir[:len(dir)-1], linksWalked, cb)
+ // Apply the component lexically to the path we are building. path does
+ // not contain any symlinks, and we are lexically dealing with a single
+ // component, so it's okay to do filepath.Clean here.
+ nextPath := filepath.Join(string(filepath.Separator), currentPath, part)
+ if nextPath == string(filepath.Separator) {
+ // If we end up back at the root, we don't need to re-evaluate /.
+ currentPath = ""
+ continue
}
- newpath, _, err := walkLink(root, dir, linksWalked, cb)
- return newpath, err
- default:
- newdir, err := walkLinks(root, dir, linksWalked, cb)
- if err != nil {
+ fullPath := root + string(filepath.Separator) + nextPath
+
+ // Figure out whether the path is a symlink.
+ fi, err := os.Lstat(fullPath)
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
return "", err
}
- newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked, cb)
+ // Treat non-existent path components the same as non-symlinks (we
+ // can't do any better here).
+ if errors.Is(err, os.ErrNotExist) || fi.Mode()&os.ModeSymlink == 0 {
+ currentPath = nextPath
+ continue
+ }
+ // Don't resolve the final component with !followTrailing.
+ if !followTrailing && unsafePath == "" {
+ currentPath = nextPath
+ break
+ }
+
+ // It's a symlink, so get its contents and expand it by prepending it
+ // to the yet-unparsed path.
+ linksWalked++
+ if linksWalked > maxSymlinkLimit {
+ return "", errTooManyLinks
+ }
+
+ dest, err := os.Readlink(fullPath)
if err != nil {
return "", err
}
- if !islink {
- return newpath, nil
+ if cb != nil {
+ if err := cb(nextPath, dest); err != nil {
+ return "", err
+ }
}
- if filepath.IsAbs(newpath) {
- return newpath, nil
+
+ unsafePath = dest + string(filepath.Separator) + unsafePath
+ // Absolute symlinks reset any work we've already done.
+ if filepath.IsAbs(dest) {
+ currentPath = ""
}
- return filepath.Join(newdir, newpath), nil
}
+
+ // There should be no lexical components left in path here, but just for
+ // safety do a filepath.Clean before the join.
+ finalPath := filepath.Join(string(filepath.Separator), currentPath)
+ return filepath.Join(root, finalPath), nil
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 9adbc22b99fc..27bc31dfd397 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -577,7 +577,7 @@ github.com/mistifyio/go-zfs/v3
# github.com/mitchellh/hashstructure/v2 v2.0.2
## explicit; go 1.14
github.com/mitchellh/hashstructure/v2
-# github.com/moby/buildkit v0.11.7-0.20240124010513-435cb77e369c => github.com/SUSE/buildkit v0.0.0-20241218053907-cd804dd86389
+# github.com/moby/buildkit v0.11.7-0.20240124010513-435cb77e369c => github.com/SUSE/buildkit v0.0.0-20241218053911-6b814972ef19
## explicit; go 1.18
github.com/moby/buildkit/api/services/control
github.com/moby/buildkit/api/types
@@ -1313,4 +1313,4 @@ k8s.io/klog/v2/internal/severity
# resenje.org/singleflight v0.3.0
## explicit; go 1.18
resenje.org/singleflight
-# github.com/moby/buildkit => github.com/SUSE/buildkit v0.0.0-20241218053907-cd804dd86389
+# github.com/moby/buildkit => github.com/SUSE/buildkit v0.0.0-20241218053911-6b814972ef19
--
2.47.1