diff --git a/pkg/build/build.go b/pkg/build/build.go index 422d3c2ca..53b45e5a6 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -1130,7 +1130,7 @@ func (b *Build) BuildPackage(ctx context.Context) error { path := filepath.Join(b.WorkspaceDir, "melange-out", lt.pkgName) fsys := os.DirFS(path) - lctx := linter.NewLinterContext(lt.pkgName, &b.Configuration, <.checks) + lctx := linter.NewLinterContext(lt.pkgName, fsys, &b.Configuration, <.checks) linters := lt.checks.GetLinters() err = lctx.LintPackageFs(fsys, linters) diff --git a/pkg/config/config.go b/pkg/config/config.go index d6c729c50..ae0eb7840 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -343,6 +343,7 @@ var defaultLinters = []string{ "opt", "srv", "setuidgid", + "strip", "tempdir", "usrlocal", "varempty", diff --git a/pkg/linter/linter.go b/pkg/linter/linter.go index d9204a589..6ea117e7b 100644 --- a/pkg/linter/linter.go +++ b/pkg/linter/linter.go @@ -15,8 +15,12 @@ package linter import ( + "debug/elf" "fmt" + "io" "io/fs" + "os" + "path/filepath" "regexp" "chainguard.dev/melange/pkg/config" @@ -24,12 +28,13 @@ import ( type LinterContext struct { pkgname string + fsys fs.FS cfg *config.Configuration chk *config.Checks } -func NewLinterContext(name string, cfg *config.Configuration, chk *config.Checks) LinterContext { - return LinterContext{name, cfg, chk} +func NewLinterContext(name string, fsys fs.FS, cfg *config.Configuration, chk *config.Checks) LinterContext { + return LinterContext{name, fsys, cfg, chk} } type linterFunc func(lctx LinterContext, path string, d fs.DirEntry) error @@ -55,6 +60,7 @@ var linterMap = map[string]linter{ "usrlocal": linter{usrLocalLinter, "This package should be a -compat package"}, "varempty": linter{varEmptyLinter, "Remove any offending files in /var/empty in the pipeline"}, "worldwrite": linter{worldWriteableLinter, "Change the permissions of any world-writeable files in the package, disable the linter, or make this a -compat package"}, + "strip": linter{strippedLinter, "Properly strip all binaries in the pipeline"}, } var postLinterMap = map[string]postLinter{ @@ -67,7 +73,8 @@ var isSrvRegex = regexp.MustCompile("^srv/") var isTempDirRegex = regexp.MustCompile("^(var/)?(tmp|run)/") var isUsrLocalRegex = regexp.MustCompile("^usr/local/") var isVarEmptyRegex = regexp.MustCompile("^var/empty/") -var isCompatPackage = regexp.MustCompile("-compat$") +var isCompatPackageRegex = regexp.MustCompile("-compat$") +var isObjectFileRegex = regexp.MustCompile(`\.(a|so|dylib)(\..*)?`) func devLinter(_ LinterContext, path string, _ fs.DirEntry) error { if isDevRegex.MatchString(path) { @@ -156,6 +163,64 @@ func worldWriteableLinter(_ LinterContext, path string, d fs.DirEntry) error { return nil } +func strippedLinter(lctx LinterContext, path string, d fs.DirEntry) error { + if !d.Type().IsRegular() { + // Don't worry about non-files + return nil + } + + info, err := d.Info() + if err != nil { + return err + } + + ext := filepath.Ext(path) + mode := info.Mode() + if mode&0111 == 0 && !isObjectFileRegex.MatchString(ext) { + // Not an executable or library + return nil + } + + reader, err := lctx.fsys.Open(path) + if err != nil { + return fmt.Errorf("Could not open file for reading: %v", err) + } + defer reader.Close() + + // XXX(Elizafox) - fs.Open doesn't support the ReaderAt interface so we copy it to a temp file. + // This sucks but what can you do? + tempfile, err := os.CreateTemp("", "melange.XXXXX") + if err != nil { + return fmt.Errorf("Could not create temporary file: %v", err) + } + defer os.Remove(tempfile.Name()) + + _, err = io.Copy(tempfile, reader) + if err != nil { + return fmt.Errorf("Could not write to temporary file: %v", err) + } + + _, err = tempfile.Seek(0, 0) + if err != nil { + return fmt.Errorf("Could not rewind temporary file: %v", err) + } + + file, err := elf.NewFile(tempfile) + if err != nil { + // We don't particularly care if this fails, it means it's probably not an ELF file + fmt.Printf("WARNING: Could not open file %q as executable: %v\n", path, err) + return nil + } + defer file.Close() + + // No debug sections allowed + if file.Section(".debug") != nil || file.Section(".zdebug") != nil { + return fmt.Errorf("File is not stripped") + } + + return nil +} + func emptyPostLinter(_ LinterContext, fsys fs.FS) error { foundfile := false walkCb := func(path string, _ fs.DirEntry, err error) error { @@ -187,7 +252,7 @@ func emptyPostLinter(_ LinterContext, fsys fs.FS) error { func (lctx LinterContext) LintPackageFs(fsys fs.FS, linters []string) error { // If this is a compat package, do nothing. - if isCompatPackage.MatchString(lctx.pkgname) { + if isCompatPackageRegex.MatchString(lctx.pkgname) { return nil } @@ -212,22 +277,21 @@ func (lctx LinterContext) LintPackageFs(fsys fs.FS, linters []string) error { err = linter.LinterFunc(lctx, path, d) if err != nil { - return fmt.Errorf("Linter %s failed at path \"%s\": %w; suggest: %s", linterName, path, err, linter.Explain) + return fmt.Errorf("Linter %s failed at path %q: %w; suggest: %s", linterName, path, err, linter.Explain) } } return nil } - err := fs.WalkDir(fsys, ".", walkCb) - if err != nil { + if err := fs.WalkDir(fsys, ".", walkCb); err != nil { return err } // Run post-walking linters for _, linterName := range postLinters { linter := postLinterMap[linterName] - err = linter.LinterFunc(lctx, fsys) + err := linter.LinterFunc(lctx, fsys) if err != nil { return fmt.Errorf("Linter %s failed; suggest: %s", linterName, linter.Explain) } diff --git a/pkg/linter/linter_test.go b/pkg/linter/linter_test.go index d951c0d8b..6ada50293 100644 --- a/pkg/linter/linter_test.go +++ b/pkg/linter/linter_test.go @@ -37,7 +37,7 @@ func Test_emptyLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"empty"}, - Disabled: []string{"dev", "opt", "setuidgid", "srv", "tempdir", "usrlocal", "varempty", "worldwrite"}, + Disabled: []string{"dev", "opt", "setuidgid", "srv", "strip", "tempdir", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -45,7 +45,7 @@ func Test_emptyLinter(t *testing.T) { linters := cfg.Package.Checks.GetLinters() assert.Equal(t, linters, []string{"empty"}) fsys := os.DirFS(dir) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.Error(t, lctx.LintPackageFs(fsys, linters)) } @@ -61,7 +61,7 @@ func Test_usrLocalLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"usrlocal"}, - Disabled: []string{"dev", "empty", "opt", "setuidgid", "srv", "tempdir", "varempty", "worldwrite"}, + Disabled: []string{"dev", "empty", "opt", "setuidgid", "strip", "srv", "tempdir", "varempty", "worldwrite"}, }, }, } @@ -74,7 +74,7 @@ func Test_usrLocalLinter(t *testing.T) { linters := cfg.Package.Checks.GetLinters() assert.Equal(t, linters, []string{"usrlocal"}) fsys := os.DirFS(dir) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.Error(t, lctx.LintPackageFs(fsys, linters)) } @@ -90,7 +90,7 @@ func Test_varEmptyLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"varempty"}, - Disabled: []string{"dev", "empty", "opt", "setuidgid", "srv", "tempdir", "usrlocal", "worldwrite"}, + Disabled: []string{"dev", "empty", "opt", "setuidgid", "strip", "srv", "tempdir", "usrlocal", "worldwrite"}, }, }, } @@ -104,7 +104,7 @@ func Test_varEmptyLinter(t *testing.T) { linters := cfg.Package.Checks.GetLinters() assert.Equal(t, linters, []string{"varempty"}) fsys := os.DirFS(dir) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.Error(t, lctx.LintPackageFs(fsys, linters)) } @@ -120,7 +120,7 @@ func Test_devLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"dev"}, - Disabled: []string{"empty", "opt", "setuidgid", "srv", "tempdir", "usrlocal", "varempty", "worldwrite"}, + Disabled: []string{"empty", "opt", "setuidgid", "strip", "srv", "tempdir", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -134,7 +134,7 @@ func Test_devLinter(t *testing.T) { linters := cfg.Package.Checks.GetLinters() assert.Equal(t, linters, []string{"dev"}) fsys := os.DirFS(dir) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.Error(t, lctx.LintPackageFs(fsys, linters)) } @@ -150,7 +150,7 @@ func Test_optLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"opt"}, - Disabled: []string{"dev", "empty", "setuidgid", "srv", "tempdir", "usrlocal", "varempty", "worldwrite"}, + Disabled: []string{"dev", "empty", "setuidgid", "strip", "srv", "tempdir", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -164,7 +164,7 @@ func Test_optLinter(t *testing.T) { linters := cfg.Package.Checks.GetLinters() assert.Equal(t, linters, []string{"opt"}) fsys := os.DirFS(dir) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.Error(t, lctx.LintPackageFs(fsys, linters)) } @@ -180,7 +180,7 @@ func Test_srvLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"srv"}, - Disabled: []string{"dev", "empty", "opt", "setuidgid", "tempdir", "usrlocal", "varempty", "worldwrite"}, + Disabled: []string{"dev", "empty", "opt", "setuidgid", "strip", "tempdir", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -194,7 +194,7 @@ func Test_srvLinter(t *testing.T) { linters := cfg.Package.Checks.GetLinters() assert.Equal(t, linters, []string{"srv"}) fsys := os.DirFS(dir) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.Error(t, lctx.LintPackageFs(fsys, linters)) } @@ -210,7 +210,7 @@ func Test_tempDirLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"tempdir"}, - Disabled: []string{"dev", "empty", "opt", "setuidgid", "srv", "usrlocal", "varempty", "worldwrite"}, + Disabled: []string{"dev", "empty", "opt", "setuidgid", "strip", "srv", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -236,7 +236,7 @@ func Test_tempDirLinter(t *testing.T) { assert.NoError(t, err) _, err = os.Create(filename) assert.NoError(t, err) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.Error(t, lctx.LintPackageFs(fsys, linters)) os.Remove(filename) @@ -271,7 +271,7 @@ func Test_setUidGidLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"setuidgid"}, - Disabled: []string{"dev", "empty", "opt", "srv", "tempdir", "usrlocal", "varempty", "worldwrite"}, + Disabled: []string{"dev", "empty", "opt", "srv", "strip", "tempdir", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -291,7 +291,7 @@ func Test_setUidGidLinter(t *testing.T) { linters := cfg.Package.Checks.GetLinters() assert.Equal(t, linters, []string{"setuidgid"}) fsys := os.DirFS(dir) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.Error(t, lctx.LintPackageFs(fsys, linters)) } @@ -307,7 +307,7 @@ func Test_worldWriteLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"worldwrite"}, - Disabled: []string{"dev", "empty", "opt", "srv", "setuidgid", "tempdir", "usrlocal", "varempty"}, + Disabled: []string{"dev", "empty", "opt", "setuidgid", "strip", "srv", "tempdir", "usrlocal", "varempty"}, }, }, } @@ -320,7 +320,7 @@ func Test_worldWriteLinter(t *testing.T) { linters := cfg.Package.Checks.GetLinters() assert.Equal(t, linters, []string{"worldwrite"}) fsys := os.DirFS(dir) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.NoError(t, lctx.LintPackageFs(fsys, linters)) // Create test file @@ -377,6 +377,6 @@ func Test_disableDefaultLinter(t *testing.T) { linters := cfg.Package.Checks.GetLinters() fsys := os.DirFS(dir) - lctx := NewLinterContext(cfg.Package.Name, &cfg, &cfg.Package.Checks) + lctx := NewLinterContext(cfg.Package.Name, fsys, &cfg, &cfg.Package.Checks) assert.NoError(t, lctx.LintPackageFs(fsys, linters)) }