Skip to content

Commit 30d0cb1

Browse files
build: avoid chmod through path symlinks
1 parent de34d75 commit 30d0cb1

2 files changed

Lines changed: 93 additions & 1 deletion

File tree

pkg/build/paths.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ func mutateDirectory(fsys apkfs.FullFS, o *options.Options, mut types.PathMutati
6464
if err != nil {
6565
return err
6666
}
67+
if d.Type()&fs.ModeSymlink != 0 {
68+
return nil
69+
}
6770
if err := mutatePermissionsDirect(fsys, path, mut.Permissions, mut.UID, mut.GID); err != nil {
6871
return fmt.Errorf("mutating permissions for path %q: %w", path, err)
6972
}
@@ -143,7 +146,7 @@ func mutatePaths(fsys apkfs.FullFS, o *options.Options, ic *types.ImageConfigura
143146
return fmt.Errorf("mutating path %q: %w", mut.Path, err)
144147
}
145148

146-
if mut.Type != "permissions" {
149+
if mut.Type != "permissions" && mut.Type != "symlink" {
147150
if err := mutatePermissions(fsys, o, mut); err != nil {
148151
return fmt.Errorf("%s mutation on %s: %w", mut.Type, mut.Path, err)
149152
}

pkg/build/paths_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2026 Chainguard, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package build
16+
17+
import (
18+
"io/fs"
19+
"testing"
20+
21+
"github.com/stretchr/testify/require"
22+
23+
apkfs "chainguard.dev/apko/pkg/apk/fs"
24+
"chainguard.dev/apko/pkg/build/types"
25+
"chainguard.dev/apko/pkg/options"
26+
)
27+
28+
func TestMutatePathsSymlinkAllowsMissingTarget(t *testing.T) {
29+
fsys := apkfs.NewMemFS()
30+
31+
err := mutatePaths(fsys, &options.Default, &types.ImageConfiguration{
32+
Paths: []types.PathMutation{{
33+
Path: "/bin/xyz",
34+
Type: "symlink",
35+
Source: "/ko-app/abc",
36+
}},
37+
})
38+
require.NoError(t, err)
39+
40+
target, err := fsys.Readlink("/bin/xyz")
41+
require.NoError(t, err)
42+
require.Equal(t, "/ko-app/abc", target)
43+
}
44+
45+
func TestMutatePathsSymlinkDoesNotChangeTargetPermissions(t *testing.T) {
46+
fsys := apkfs.NewMemFS()
47+
require.NoError(t, fsys.MkdirAll("/var/lib/foo", 0o755))
48+
49+
err := mutatePaths(fsys, &options.Default, &types.ImageConfiguration{
50+
Paths: []types.PathMutation{{
51+
Path: "/foo",
52+
Type: "symlink",
53+
Source: "/var/lib/foo",
54+
}},
55+
})
56+
require.NoError(t, err)
57+
58+
target, err := fsys.Readlink("/foo")
59+
require.NoError(t, err)
60+
require.Equal(t, "/var/lib/foo", target)
61+
62+
info, err := fsys.Stat("/var/lib/foo")
63+
require.NoError(t, err)
64+
require.Equal(t, fs.FileMode(0o755), info.Mode().Perm())
65+
}
66+
67+
func TestMutateDirectoryRecursiveSkipsSymlinks(t *testing.T) {
68+
fsys := apkfs.NewMemFS()
69+
require.NoError(t, fsys.MkdirAll("/tree/target", 0o755))
70+
require.NoError(t, fsys.Symlink("/missing", "/tree/link"))
71+
72+
err := mutatePaths(fsys, &options.Default, &types.ImageConfiguration{
73+
Paths: []types.PathMutation{{
74+
Path: "/tree",
75+
Type: "directory",
76+
Permissions: 0o700,
77+
Recursive: true,
78+
}},
79+
})
80+
require.NoError(t, err)
81+
82+
target, err := fsys.Readlink("/tree/link")
83+
require.NoError(t, err)
84+
require.Equal(t, "/missing", target)
85+
86+
info, err := fsys.Stat("/tree/target")
87+
require.NoError(t, err)
88+
require.Equal(t, fs.FileMode(0o700), info.Mode().Perm())
89+
}

0 commit comments

Comments
 (0)