From 5febb347e5add555003db5466b24c43d102bbbc5 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:34:33 +0200 Subject: [PATCH 1/2] policy: close memoized policy filesystems Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- build/policy_loader.go | 20 ++++++++++++++++++-- build/policy_loader_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/build/policy_loader.go b/build/policy_loader.go index 61c68018160a..a97bc6ba8f45 100644 --- a/build/policy_loader.go +++ b/build/policy_loader.go @@ -179,10 +179,10 @@ func (p *policyPathFS) Stat(name string) (fs.FileInfo, error) { } func (p *policyPathFS) Close() error { - if err := p.cwdFS.close(); err != nil { + if err := p.cwdFS.closeAll(); err != nil { return err } - return p.contextFS.close() + return p.contextFS.closeAll() } func (p *policyPathFS) resolve(name string) (fs.StatFS, string, error) { @@ -274,6 +274,22 @@ func (m *memoizedPolicyFS) close() error { return nil } +func (m *memoizedPolicyFS) closeAll() error { + m.mu.Lock() + closeFn := m.closeFn + m.fs = nil + m.closeFn = nil + m.err = nil + m.loaded = false + m.refs = 0 + m.mu.Unlock() + + if closeFn != nil { + return closeFn() + } + return nil +} + func normalizeRemotePolicyPath(raw string) (string, error) { clean := strings.TrimPrefix(path.Join("/", filepath.ToSlash(raw)), "/") if clean == "." || clean == "" { diff --git a/build/policy_loader_test.go b/build/policy_loader_test.go index 584f3150435b..df7ff20b0eae 100644 --- a/build/policy_loader_test.go +++ b/build/policy_loader_test.go @@ -83,3 +83,40 @@ func TestMemoizedPolicyFSReinitializesAfterAllRefsClosed(t *testing.T) { require.NoError(t, m.close()) require.Equal(t, 2, closeCalls) } + +func TestMemoizedPolicyFSCloseAll(t *testing.T) { + var initCalls int + var closeCalls int + + m := &memoizedPolicyFS{ + init: func() (fs.StatFS, func() error, error) { + initCalls++ + root := fstest.MapFS{ + "policy.rego": &fstest.MapFile{Data: []byte("package docker\n")}, + } + return root, func() error { + closeCalls++ + return nil + }, nil + }, + } + + first, err := m.get() + require.NoError(t, err) + require.NotNil(t, first) + second, err := m.get() + require.NoError(t, err) + require.NotNil(t, second) + require.Equal(t, 1, initCalls) + + require.NoError(t, m.closeAll()) + require.Equal(t, 1, closeCalls) + + third, err := m.get() + require.NoError(t, err) + require.NotNil(t, third) + require.Equal(t, 2, initCalls) + + require.NoError(t, m.closeAll()) + require.Equal(t, 2, closeCalls) +} From b304a868d9cb582364875f148a00b7c30da753d2 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:41:03 +0200 Subject: [PATCH 2/2] policy: normalize local policy paths Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- build/policy_loader.go | 7 ++--- build/policy_loader_test.go | 52 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/build/policy_loader.go b/build/policy_loader.go index a97bc6ba8f45..e23c86b8f430 100644 --- a/build/policy_loader.go +++ b/build/policy_loader.go @@ -13,6 +13,7 @@ import ( "time" "github.com/docker/buildx/policy" + "github.com/docker/buildx/util/osutil" "github.com/docker/buildx/util/sourcemeta" "github.com/moby/buildkit/client/llb" gwclient "github.com/moby/buildkit/frontend/gateway/client" @@ -197,7 +198,7 @@ func (p *policyPathFS) resolve(name string) (fs.StatFS, string, error) { if err != nil { return nil, "", err } - return cwd, filepath.Clean(v), nil + return cwd, osutil.SanitizePath(v), nil } contextFS, err := p.contextFS.get() @@ -219,11 +220,11 @@ func normalizeLocalPolicyPath(name, contextDir string) string { if rel, err := filepath.Rel(contextDir, name); err == nil { rel = filepath.Clean(rel) if rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) { - return rel + return osutil.SanitizePath(rel) } } } - return filepath.Clean(name) + return osutil.SanitizePath(name) } type memoizedPolicyFS struct { diff --git a/build/policy_loader_test.go b/build/policy_loader_test.go index df7ff20b0eae..28dcdbe716d3 100644 --- a/build/policy_loader_test.go +++ b/build/policy_loader_test.go @@ -1,13 +1,65 @@ package build import ( + "context" "io/fs" + "os" + "path/filepath" "testing" "testing/fstest" "github.com/stretchr/testify/require" ) +func TestLoadPolicyDataLocalPaths(t *testing.T) { + dir := t.TempDir() + policyData := []byte("package docker\n") + policyRelPath := filepath.Join("policy", "allow.rego") + require.NoError(t, os.MkdirAll(filepath.Join(dir, "policy"), 0700)) + require.NoError(t, os.WriteFile(filepath.Join(dir, policyRelPath), policyData, 0600)) + + t.Run("context-relative", func(t *testing.T) { + provider := newPolicyPathFS(context.Background(), nil, policyOpt{ + ContextDir: dir, + }) + + dt, ok, err := loadPolicyData(provider, policyRelPath) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, policyData, dt) + }) + + t.Run("context-absolute", func(t *testing.T) { + provider := newPolicyPathFS(context.Background(), nil, policyOpt{ + ContextDir: dir, + }) + + dt, ok, err := loadPolicyData(provider, filepath.Join(dir, policyRelPath)) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, policyData, dt) + }) + + t.Run("cwd", func(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + require.NoError(t, os.Chdir(dir)) + t.Cleanup(func() { + require.NoError(t, os.Chdir(cwd)) + }) + + provider := newPolicyPathFS(context.Background(), nil, policyOpt{}) + dt, ok, err := loadPolicyData(provider, "cwd://"+policyRelPath) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, policyData, dt) + }) +} + +func TestNormalizeLocalPolicyPath(t *testing.T) { + require.Equal(t, "policy/allow.rego", normalizeLocalPolicyPath(filepath.Join("policy", "allow.rego"), "")) +} + func TestMemoizedPolicyFSRefCountedClose(t *testing.T) { var initCalls int var closeCalls int