Skip to content

Commit 972c561

Browse files
committed
Fix and improve file permission linters; update tests to mirror actual usage
Signed-off-by: egibs <[email protected]>
1 parent c625d0d commit 972c561

File tree

7 files changed

+102
-86
lines changed

7 files changed

+102
-86
lines changed

docs/md/melange_build.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ melange build [flags]
5353
-i, --interactive when enabled, attaches stdin with a tty to the pod on failure
5454
-k, --keyring-append strings path to extra keys to include in the build environment keyring
5555
--license string license to use for the build config file itself (default "NOASSERTION")
56-
--lint-require strings linters that must pass (default [dev,infodir,tempdir,usrmerge,varempty])
57-
--lint-warn strings linters that will generate warnings (default [cudaruntimelib,lddcheck,maninfo,object,opt,pkgconf,python/docs,python/multiple,python/test,setuidgid,srv,strip,usrlocal,worldwrite])
56+
--lint-require strings linters that must pass (default [dev,infodir,setuidgid,tempdir,usrmerge,varempty,worldwrite])
57+
--lint-warn strings linters that will generate warnings (default [cudaruntimelib,lddcheck,maninfo,object,opt,pkgconf,python/docs,python/multiple,python/test,sbom,srv,strip,usrlocal])
5858
--memory string default memory resources to use for builds
5959
--namespace string namespace to use in package URLs in SBOM (eg wolfi, alpine) (default "unknown")
6060
--out-dir string directory where packages will be output (default "./packages/")

docs/md/melange_lint.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ melange lint [flags]
2929

3030
```
3131
-h, --help help for lint
32-
--lint-require strings linters that must pass (default [dev,infodir,tempdir,usrmerge,varempty])
33-
--lint-warn strings linters that will generate warnings (default [cudaruntimelib,lddcheck,maninfo,object,opt,pkgconf,python/docs,python/multiple,python/test,setuidgid,srv,strip,usrlocal,worldwrite])
32+
--lint-require strings linters that must pass (default [dev,infodir,setuidgid,tempdir,usrmerge,varempty,worldwrite])
33+
--lint-warn strings linters that will generate warnings (default [cudaruntimelib,lddcheck,maninfo,object,opt,pkgconf,python/docs,python/multiple,python/test,sbom,srv,strip,usrlocal])
3434
```
3535

3636
### Options inherited from parent commands

pkg/build/build.go

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"io"
2525
"io/fs"
2626
"log/slog"
27+
"math"
2728
"os"
2829
"path/filepath"
2930
"runtime"
@@ -37,10 +38,10 @@ import (
3738
"chainguard.dev/apko/pkg/apk/apk"
3839
apkofs "chainguard.dev/apko/pkg/apk/fs"
3940
apko_build "chainguard.dev/apko/pkg/build"
40-
"chainguard.dev/apko/pkg/tarfs"
4141
apko_types "chainguard.dev/apko/pkg/build/types"
4242
"chainguard.dev/apko/pkg/options"
4343
"chainguard.dev/apko/pkg/sbom/generator/spdx"
44+
"chainguard.dev/apko/pkg/tarfs"
4445
"github.com/chainguard-dev/clog"
4546
purl "github.com/package-url/packageurl-go"
4647
"github.com/yookoala/realpath"
@@ -103,7 +104,7 @@ type Build struct {
103104
WorkspaceDir string
104105
WorkspaceDirFS apkofs.FullFS
105106
WorkspaceIgnore string
106-
GuestFS apkofs.FullFS
107+
GuestFS apkofs.FullFS
107108
// Ordered directories where to find 'uses' pipelines.
108109
PipelineDirs []string
109110
SourceDir string
@@ -770,7 +771,7 @@ func (b *Build) BuildPackage(ctx context.Context) error {
770771
}
771772
}
772773

773-
// add the main package to the linter queue
774+
// add the subpackage to the linter queue
774775
lintTarget := linterTarget{
775776
pkgName: sp.Name,
776777
disabled: sp.Checks.Disabled,
@@ -819,7 +820,7 @@ func (b *Build) BuildPackage(ctx context.Context) error {
819820
uid := owner["uid"]
820821
gid := owner["gid"]
821822
if err := b.WorkspaceDirFS.Chown(path, uid, gid); err != nil {
822-
log.Warnf("failed to change ownership of %s to %d:%d", path, uid, gid)
823+
log.Warnf("failed to change ownership of %s to %d:%d: %v", path, uid, gid, err)
823824
}
824825
}
825826

@@ -859,7 +860,12 @@ func (b *Build) BuildPackage(ctx context.Context) error {
859860
// perform package linting
860861
for _, lt := range linterQueue {
861862
log.Infof("running package linters for %s", lt.pkgName)
862-
path := filepath.Join(b.WorkspaceDir, melangeOutputDirName, lt.pkgName)
863+
864+
// lint the in-memory filesystem which contains the correct mode bits, xattrs, etc.
865+
fsys, err := apkofs.Sub(b.WorkspaceDirFS, filepath.Join(melangeOutputDirName, lt.pkgName))
866+
if err != nil {
867+
return fmt.Errorf("failed to return filesystem for workspace subtree: %w", err)
868+
}
863869

864870
// Downgrade disabled checks from required to warn
865871
require := slices.DeleteFunc(b.LintRequire, func(s string) bool {
@@ -869,13 +875,13 @@ func (b *Build) BuildPackage(ctx context.Context) error {
869875
return a == b
870876
})
871877

872-
if err := linter.LintBuild(ctx, b.Configuration, lt.pkgName, path, require, warn); err != nil {
878+
if err := linter.LintBuild(ctx, b.Configuration, lt.pkgName, require, warn, fsys); err != nil {
873879
return fmt.Errorf("unable to lint package %s: %w", lt.pkgName, err)
874880
}
875881
}
876882

877883
// Perform all license related linting and analysis
878-
if _, _, err := license.LicenseCheck(ctx, b.Configuration, apkofs.DirFS(ctx, b.WorkspaceDir)); err != nil {
884+
if _, _, err := license.LicenseCheck(ctx, b.Configuration, b.WorkspaceDirFS); err != nil {
879885
return fmt.Errorf("license check: %w", err)
880886
}
881887

@@ -1315,6 +1321,10 @@ func storeMetadata(dir string) (map[string]map[string][]byte, map[string]fs.File
13151321
return err
13161322
}
13171323

1324+
if d.IsDir() || d.Type()&fs.ModeSymlink == fs.ModeSymlink {
1325+
return nil
1326+
}
1327+
13181328
relPath, err := filepath.Rel(dir, path)
13191329
if err != nil {
13201330
return err
@@ -1324,25 +1334,24 @@ func storeMetadata(dir string) (map[string]map[string][]byte, map[string]fs.File
13241334
if err != nil {
13251335
return err
13261336
}
1327-
mode := fi.Mode()
13281337

1329-
// Store ownership info, defaulting to root when unavailable
1338+
// store the file mode bits for every relative path in the provided directory
1339+
// some paths may not be related to or used by the package itself (i.e., they may be candidates for cleanup)
1340+
// but we may want to have them for future linting at the very least
1341+
modes[relPath] = fi.Mode()
1342+
1343+
// Store ownership info, defaulting to root when unavailable or invalid
13301344
owners[relPath] = map[string]int{
13311345
"uid": 0,
13321346
"gid": 0,
13331347
}
13341348
if stat, ok := fi.Sys().(*syscall.Stat_t); ok {
1335-
owners[relPath]["uid"] = int(stat.Uid)
1336-
owners[relPath]["gid"] = int(stat.Gid)
1337-
}
1338-
1339-
if d.IsDir() || d.Type()&fs.ModeSymlink == fs.ModeSymlink {
1340-
return nil
1341-
}
1342-
1343-
// If the path is within the melange-out directory, store the relative path and mode bits
1344-
if strings.Contains(path, melangeOutputDirName) {
1345-
modes[relPath] = mode
1349+
if stat.Uid <= math.MaxInt32 {
1350+
owners[relPath]["uid"] = int(stat.Uid)
1351+
}
1352+
if stat.Gid <= math.MaxInt32 {
1353+
owners[relPath]["gid"] = int(stat.Gid)
1354+
}
13461355
}
13471356

13481357
size, err := unix.Listxattr(path, nil)

pkg/build/test_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func TestBuildWorkspaceConfig(t *testing.T) {
103103
name: "test - with cache dir, exists",
104104
t: func() *Test {
105105
cacheT := baseTest
106-
cacheT.CacheDir = tmpDir
106+
cacheT.CacheDir = tmpDirReal
107107
return &cacheT
108108
}(),
109109
want: func() *container.Config {
@@ -116,15 +116,15 @@ func TestBuildWorkspaceConfig(t *testing.T) {
116116
name: "test - with cache dir, exists, environment",
117117
t: func() *Test {
118118
cacheT := baseTest
119-
cacheT.CacheDir = tmpDir
119+
cacheT.CacheDir = tmpDirReal
120120
return &cacheT
121121
}(),
122122
env: map[string]string{"FOO": "bar", "BAZ": "zzz"},
123123
want: func() *container.Config {
124124
want := wantBase
125125
want.Mounts = append(want.Mounts, container.BindMount{Source: tmpDirReal, Destination: "/var/cache/melange"})
126126
want.Environment = map[string]string{"FOO": "bar", "BAZ": "zzz", "HOME": "/root"}
127-
want.CacheDir = tmpDir
127+
want.CacheDir = tmpDirReal
128128
return &want
129129
}(),
130130
},

pkg/config/config.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,11 +1240,11 @@ func replaceEntrypoint(r *strings.Replacer, in apko_types.ImageEntrypoint) apko_
12401240

12411241
func replaceImageContents(r *strings.Replacer, in apko_types.ImageContents) apko_types.ImageContents {
12421242
return apko_types.ImageContents{
1243-
BuildRepositories: replaceAll(r, in.BuildRepositories),
1244-
Repositories: replaceAll(r, in.Repositories),
1245-
Keyring: replaceAll(r, in.Keyring),
1246-
Packages: replaceAll(r, in.Packages),
1247-
BaseImage: in.BaseImage, // TODO
1243+
BuildRepositories: replaceAll(r, in.BuildRepositories),
1244+
Repositories: replaceAll(r, in.Repositories),
1245+
Keyring: replaceAll(r, in.Keyring),
1246+
Packages: replaceAll(r, in.Packages),
1247+
BaseImage: in.BaseImage, // TODO
12481248
}
12491249
}
12501250

pkg/linter/linter.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"path/filepath"
2828
"regexp"
2929
"slices"
30+
"strconv"
3031
"strings"
3132

3233
apkofs "chainguard.dev/apko/pkg/apk/fs"
@@ -122,12 +123,12 @@ var linterMap = map[string]linter{
122123
"sbom": {
123124
LinterFunc: allPaths(sbomLinter),
124125
Explain: "Remove any files in /var/lib/db/sbom from the package",
125-
defaultBehavior: Ignore, // TODO: needs work to be useful
126+
defaultBehavior: Warn, // TODO: needs work to be useful
126127
},
127128
"setuidgid": {
128129
LinterFunc: isSetUIDOrGIDLinter,
129130
Explain: "Unset the setuid/setgid bit on the relevant files, or remove this linter",
130-
defaultBehavior: Warn,
131+
defaultBehavior: Require,
131132
},
132133
"srv": {
133134
LinterFunc: allPaths(srvLinter),
@@ -151,8 +152,8 @@ var linterMap = map[string]linter{
151152
},
152153
"worldwrite": {
153154
LinterFunc: worldWriteableLinter,
154-
Explain: "Change the permissions of any world-writeable files in the package, disable the linter, or make this a -compat package",
155-
defaultBehavior: Warn,
155+
Explain: "Change the permissions of any permissive files in the package, disable the linter, or make this a -compat package",
156+
defaultBehavior: Require,
156157
},
157158
"strip": {
158159
LinterFunc: strippedLinter,
@@ -267,15 +268,16 @@ func isSetUIDOrGIDLinter(ctx context.Context, _ *config.Configuration, _ string,
267268
if err != nil {
268269
return err
269270
}
270-
if isIgnoredPath(path) {
271-
return nil
272-
}
273271

274272
info, err := d.Info()
275273
if err != nil {
276274
return err
277275
}
278276

277+
if !d.Type().IsRegular() { // Don't worry about non-files
278+
return nil
279+
}
280+
279281
mode := info.Mode()
280282
if mode&fs.ModeSetuid != 0 {
281283
return fmt.Errorf("file is setuid")
@@ -287,8 +289,8 @@ func isSetUIDOrGIDLinter(ctx context.Context, _ *config.Configuration, _ string,
287289
}
288290

289291
func sbomLinter(_ context.Context, _ *config.Configuration, _, path string) error {
290-
if strings.HasPrefix(path, "var/lib/db/sbom/") {
291-
return fmt.Errorf("package writes to %s", path)
292+
if filepath.Dir(path) == "var/lib/db/sbom" && !strings.HasSuffix(path, ".spdx.json") {
293+
return fmt.Errorf("package writes to %s", filepath.Dir(path))
292294
}
293295
return nil
294296
}
@@ -338,9 +340,6 @@ func worldWriteableLinter(ctx context.Context, _ *config.Configuration, pkgname
338340
if err != nil {
339341
return err
340342
}
341-
if isIgnoredPath(path) {
342-
return nil
343-
}
344343

345344
if !d.Type().IsRegular() { // Don't worry about non-files
346345
return nil
@@ -352,11 +351,13 @@ func worldWriteableLinter(ctx context.Context, _ *config.Configuration, pkgname
352351
}
353352

354353
mode := info.Mode()
354+
perm := fmt.Sprintf("0%s", strconv.FormatUint(uint64(mode.Perm()), 8))
355+
355356
if mode&0o002 != 0 {
356357
if mode&0o111 != 0 {
357-
return fmt.Errorf("world-writeable executable file found in package (security risk): %s", path)
358+
return fmt.Errorf("world-writeable executable file found in package (security risk): %s - %s", path, perm)
358359
}
359-
return fmt.Errorf("world-writeable file found in package: %s", path)
360+
return fmt.Errorf("world-writeable file found in package: %s - %s", path, perm)
360361
}
361362
return nil
362363
})
@@ -700,18 +701,18 @@ func checkLinters(linters []string) error {
700701
}
701702

702703
// Lint the given build directory at the given path
703-
func LintBuild(ctx context.Context, cfg *config.Configuration, packageName string, path string, require, warn []string) error {
704+
func LintBuild(ctx context.Context, cfg *config.Configuration, packageName string, require, warn []string, fsys apkofs.FullFS) error {
704705
if err := checkLinters(append(require, warn...)); err != nil {
705706
return err
706707
}
707708

708709
log := clog.FromContext(ctx)
709-
fsys := apkofs.DirFS(ctx, path)
710+
log.Infof("linting apk: %s", packageName)
710711

711712
if err := lintPackageFS(ctx, cfg, packageName, fsys, warn); err != nil {
712713
log.Warn(err.Error())
713714
}
714-
log.Infof("linting apk: %s", packageName)
715+
715716
return lintPackageFS(ctx, cfg, packageName, fsys, require)
716717
}
717718

0 commit comments

Comments
 (0)