diff --git a/pkg/build/linter.go b/pkg/build/linter.go index 1e46a7c4a..273da6167 100644 --- a/pkg/build/linter.go +++ b/pkg/build/linter.go @@ -36,13 +36,14 @@ type linter struct { } var linterMap = map[string]linter{ - "dev": linter{devLinter, "If this package is creating /dev nodes, it should use udev instead; otherwise, remove any files in /dev"}, - "opt": linter{optLinter, "This package should be a -compat package"}, - "setuidgid": linter{isSetUidOrGidLinter, "Unset the setuid/setgid bit on the relevant files, or remove this linter"}, - "srv": linter{srvLinter, "This package should be a -compat package"}, - "tempdir": linter{tempDirLinter, "Remove any offending files in temporary dirs in the pipeline"}, - "usrlocal": linter{usrLocalLinter, "This package should be a -compat package"}, - "varempty": linter{varEmptyLinter, "Remove any offending files in /var/empty in the pipeline"}, + "dev": linter{devLinter, "If this package is creating /dev nodes, it should use udev instead; otherwise, remove any files in /dev"}, + "opt": linter{optLinter, "This package should be a -compat package"}, + "setuidgid": linter{isSetUidOrGidLinter, "Unset the setuid/setgid bit on the relevant files, or remove this linter"}, + "srv": linter{srvLinter, "This package should be a -compat package"}, + "tempdir": linter{tempDirLinter, "Remove any offending files in temporary dirs in the pipeline"}, + "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"}, } var isDevRegex = regexp.MustCompile("^dev/") @@ -117,6 +118,29 @@ func varEmptyLinter(_ LinterContext, path string, _ fs.DirEntry) error { return nil } +func worldWriteableLinter(_ 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 + } + + mode := info.Mode() + if mode&0002 != 0 { + if mode&0111 != 0 { + return fmt.Errorf("World-writeable executable file found in package (security risk)") + } else { + return fmt.Errorf("World-writeable file found in package") + } + } + + return nil +} + func lintPackageFs(lctx LinterContext, fsys fs.FS, linters []string) error { // If this is a compat package, do nothing. if isCompatPackage.MatchString(lctx.pkgname) { diff --git a/pkg/build/linter_test.go b/pkg/build/linter_test.go index 8116f22f2..3f18b6926 100644 --- a/pkg/build/linter_test.go +++ b/pkg/build/linter_test.go @@ -37,7 +37,7 @@ func Test_usrLocalLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"usrlocal"}, - Disabled: []string{"dev", "opt", "setuidgid", "srv", "tempdir", "varempty"}, + Disabled: []string{"dev", "opt", "setuidgid", "srv", "tempdir", "varempty", "worldwrite"}, }, }, } @@ -66,7 +66,7 @@ func Test_varEmptyLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"varempty"}, - Disabled: []string{"dev", "opt", "setuidgid", "srv", "tempdir", "usrlocal"}, + Disabled: []string{"dev", "opt", "setuidgid", "srv", "tempdir", "usrlocal", "worldwrite"}, }, }, } @@ -96,7 +96,7 @@ func Test_devLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"dev"}, - Disabled: []string{"opt", "setuidgid", "srv", "tempdir", "usrlocal", "varempty"}, + Disabled: []string{"opt", "setuidgid", "srv", "tempdir", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -126,7 +126,7 @@ func Test_optLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"opt"}, - Disabled: []string{"dev", "setuidgid", "srv", "tempdir", "usrlocal", "varempty"}, + Disabled: []string{"dev", "setuidgid", "srv", "tempdir", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -156,7 +156,7 @@ func Test_srvLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"srv"}, - Disabled: []string{"dev", "opt", "setuidgid", "tempdir", "usrlocal", "varempty"}, + Disabled: []string{"dev", "opt", "setuidgid", "tempdir", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -186,7 +186,7 @@ func Test_tempDirLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"tempdir"}, - Disabled: []string{"dev", "opt", "setuidgid", "srv", "usrlocal", "varempty"}, + Disabled: []string{"dev", "opt", "setuidgid", "srv", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -247,7 +247,7 @@ func Test_setUidGidLinter(t *testing.T) { Epoch: 0, Checks: config.Checks{ Enabled: []string{"setuidgid"}, - Disabled: []string{"dev", "opt", "srv", "tempdir", "usrlocal", "varempty"}, + Disabled: []string{"dev", "opt", "srv", "tempdir", "usrlocal", "varempty", "worldwrite"}, }, }, } @@ -271,6 +271,61 @@ func Test_setUidGidLinter(t *testing.T) { assert.Error(t, lintPackageFs(lctx, fsys, linters)) } +func Test_worldWriteLinter(t *testing.T) { + dir, err := os.MkdirTemp("", "melange.XXXXX") + defer os.RemoveAll(dir) + assert.NoError(t, err) + + cfg := config.Configuration{ + Package: config.Package{ + Name: "test", + Version: "4.2.0", + Epoch: 0, + Checks: config.Checks{ + Enabled: []string{"worldwrite"}, + Disabled: []string{"dev", "opt", "srv", "setuidgid", "tempdir", "usrlocal", "varempty"}, + }, + }, + } + + usrLocalDirPath := filepath.Join(dir, "usr", "lib") + err = os.MkdirAll(usrLocalDirPath, 0777) + assert.NoError(t, err) + + // Ensure 777 dirs don't trigger it + linters := cfg.Package.Checks.GetLinters() + assert.Equal(t, linters, []string{"worldwrite"}) + fsys := os.DirFS(dir) + lctx := LinterContext{cfg.Package.Name, &cfg, &cfg.Package.Checks} + assert.NoError(t, lintPackageFs(lctx, fsys, linters)) + + // Create test file + filePath := filepath.Join(usrLocalDirPath, "test.txt") + _, err = os.Create(filepath.Join(filePath)) + assert.NoError(t, err) + + // Set writeable and executable bits for non-world + err = os.Chmod(filePath, 0770) + assert.NoError(t, err) + + // Linter should not trigger + assert.NoError(t, lintPackageFs(lctx, fsys, linters)) + + // Set writeable bit (but not executable bit) + err = os.Chmod(filePath, 0776) + assert.NoError(t, err) + + // Linter should trigger + assert.Error(t, lintPackageFs(lctx, fsys, linters)) + + // Set writeable and executable bit + err = os.Chmod(filePath, 0777) + assert.NoError(t, err) + + // Linter should trigger + assert.Error(t, lintPackageFs(lctx, fsys, linters)) +} + func Test_disableDefaultLinter(t *testing.T) { dir, err := os.MkdirTemp("", "melange.XXXXX") defer os.RemoveAll(dir) diff --git a/pkg/config/config.go b/pkg/config/config.go index 554bfd989..8ae253be5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -345,6 +345,7 @@ var defaultLinters = []string{ "tempdir", "usrlocal", "varempty", + "worldwrite", } type VarTransforms struct {