From 4b20b35f6e5839e1d06b57c94d2d01d37ed7c98b Mon Sep 17 00:00:00 2001 From: Aidan Delaney Date: Wed, 5 Nov 2025 06:44:42 +0000 Subject: [PATCH 1/2] Respect the UMASK env var When a container is launched with a UMASK value or the UMASK value is set in a launch environment then respect that value Signed-off-by: adelaney21 --- launch/launcher.go | 5 +++ launch/umask_unix.go | 27 ++++++++++++++ launch/umask_unix_test.go | 77 +++++++++++++++++++++++++++++++++++++++ launch/umask_windows.go | 5 +++ 4 files changed, 114 insertions(+) create mode 100644 launch/umask_unix.go create mode 100644 launch/umask_unix_test.go create mode 100644 launch/umask_windows.go diff --git a/launch/launcher.go b/launch/launcher.go index e828757b3..b9fcddea4 100644 --- a/launch/launcher.go +++ b/launch/launcher.go @@ -69,6 +69,11 @@ func (l *Launcher) LaunchProcess(self string, proc Process) error { } proc.WorkingDirectory = getProcessWorkingDirectory(proc, l.AppDir) + err := SetUmask(l.Env) + if err != nil { + return errors.Wrap(err, "umask") + } + if proc.Direct { return l.launchDirect(proc) } diff --git a/launch/umask_unix.go b/launch/umask_unix.go new file mode 100644 index 000000000..af211740b --- /dev/null +++ b/launch/umask_unix.go @@ -0,0 +1,27 @@ +//go:build unix + +package launch + +import ( + "strconv" + "syscall" + + "github.com/pkg/errors" +) + +// SetUmask on unix systems from the value in the `UMASK` environment variable +func SetUmask(env Env) error { + return SetUmaskWith(env, syscall.Umask) +} + +// SetUmaskWith the injected function umaskFn +func SetUmaskWith(env Env, umaskFn func(int) int) error { + if umask := env.Get("UMASK"); umask != "" { + u, err := strconv.ParseInt(umask, 8, 0) + if err != nil { + return errors.Wrap(err, "invalid umask value") + } + umaskFn(int(u)) + } + return nil +} diff --git a/launch/umask_unix_test.go b/launch/umask_unix_test.go new file mode 100644 index 000000000..db4d3b637 --- /dev/null +++ b/launch/umask_unix_test.go @@ -0,0 +1,77 @@ +//go:build unix + +package launch_test + +import ( + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/lifecycle/env" + "github.com/buildpacks/lifecycle/launch" + h "github.com/buildpacks/lifecycle/testhelpers" +) + +func TestUmask(t *testing.T) { + spec.Run(t, "Umask", testUmask, spec.Report(report.Terminal{})) +} + +func testUmask(t *testing.T, when spec.G, it spec.S) { + when("UMASK is set", func() { + it("parses octal umask values", func() { + tests := []struct { + name string + umask string + expected int + }{ + {"standard user umask", "0002", 2}, + {"restrictive umask", "0077", 63}, + {"permissive umask", "0000", 0}, + {"three digit umask", "022", 18}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + environ := env.NewLaunchEnv([]string{"UMASK=" + tt.umask}, "", "") + + var called int + spy := func(m int) int { + called = m + return 0 + } + + err := launch.SetUmaskWith(environ, spy) + + h.AssertNil(t, err) + h.AssertEq(t, called, tt.expected) + }) + } + }) + + it("returns error for invalid umask", func() { + environ := env.NewLaunchEnv([]string{"UMASK=invalid"}, "", "") + + err := launch.SetUmaskWith(environ, func(int) int { return 0 }) + + h.AssertNotNil(t, err) + }) + }) + + when("UMASK is unset", func() { + it("does not call umask function", func() { + environ := env.NewLaunchEnv([]string{}, "", "") + + called := false + spy := func(_ int) int { + called = true + return 0 + } + + err := launch.SetUmaskWith(environ, spy) + + h.AssertNil(t, err) + h.AssertEq(t, called, false) + }) + }) +} diff --git a/launch/umask_windows.go b/launch/umask_windows.go new file mode 100644 index 000000000..aeeb915f6 --- /dev/null +++ b/launch/umask_windows.go @@ -0,0 +1,5 @@ +package launch + +func SetUmask(env Env) { + // no operation +} From 93c67dd952fc6d32978266934520442f4654db20 Mon Sep 17 00:00:00 2001 From: adelaney21 Date: Thu, 6 Nov 2025 16:45:59 +0000 Subject: [PATCH 2/2] Log when CNB_LAUNCH_UMASK is incorrect Pass a logger down to the process launcher and log when setting the umaks is incorrect and continue. Signed-off-by: adelaney21 --- cmd/launcher/cli/launcher.go | 1 + launch/launcher.go | 12 +++++++++--- launch/launcher_test.go | 6 ++++++ launch/umask_unix.go | 20 ++++++++++++++------ launch/umask_unix_test.go | 4 ++-- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/cmd/launcher/cli/launcher.go b/cmd/launcher/cli/launcher.go index 82e09e02e..db977a67e 100644 --- a/cmd/launcher/cli/launcher.go +++ b/cmd/launcher/cli/launcher.go @@ -49,6 +49,7 @@ func RunLaunch() error { ExecD: launch.NewExecDRunner(), Shell: launch.DefaultShell, Setenv: os.Setenv, + Logger: cmd.DefaultLogger, } if err := launcher.Launch(os.Args[0], os.Args[1:]); err != nil { diff --git a/launch/launcher.go b/launch/launcher.go index b9fcddea4..e50838b0b 100644 --- a/launch/launcher.go +++ b/launch/launcher.go @@ -9,6 +9,7 @@ import ( "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/env" + "github.com/buildpacks/lifecycle/log" ) var ( @@ -26,9 +27,11 @@ type Launcher struct { ExecD ExecD Shell Shell LayersDir string + Logger *log.DefaultLogger PlatformAPI *api.Version Processes []Process Setenv func(string, string) error + Setumask func(Env) error } type ExecFunc func(argv0 string, argv []string, envv []string) error @@ -69,9 +72,12 @@ func (l *Launcher) LaunchProcess(self string, proc Process) error { } proc.WorkingDirectory = getProcessWorkingDirectory(proc, l.AppDir) - err := SetUmask(l.Env) - if err != nil { - return errors.Wrap(err, "umask") + if l.Setumask == nil { + l.Setumask = SetUmask + } + err := l.Setumask(l.Env) + if err != nil && l.Logger != nil { + l.Logger.Errorf("invalid umask: %s", err) } if proc.Direct { diff --git a/launch/launcher_test.go b/launch/launcher_test.go index 92c3d8476..33e3e8c16 100644 --- a/launch/launcher_test.go +++ b/launch/launcher_test.go @@ -124,6 +124,9 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) { // set command to something on the real path so exec.LookPath succeeds process.Command = launch.NewRawCommand([]string{"sh"}) + launcher.Setumask = func(_ launch.Env) error { + return nil + } mockEnv.EXPECT().Get("PATH").Return("some-path").AnyTimes() launcher.Setenv = func(k string, v string) error { if k == "PATH" { @@ -373,6 +376,9 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) { it.Before(func() { shell = &fakeShell{} launcher.Shell = shell + launcher.Setumask = func(_ launch.Env) error { + return nil + } }) it("sets Caller to self", func() { diff --git a/launch/umask_unix.go b/launch/umask_unix.go index af211740b..284ca9c94 100644 --- a/launch/umask_unix.go +++ b/launch/umask_unix.go @@ -3,12 +3,17 @@ package launch import ( + "fmt" "strconv" "syscall" "github.com/pkg/errors" ) +const ( + umaskEnvVar = "CNB_LAUNCH_UMASK" +) + // SetUmask on unix systems from the value in the `UMASK` environment variable func SetUmask(env Env) error { return SetUmaskWith(env, syscall.Umask) @@ -16,12 +21,15 @@ func SetUmask(env Env) error { // SetUmaskWith the injected function umaskFn func SetUmaskWith(env Env, umaskFn func(int) int) error { - if umask := env.Get("UMASK"); umask != "" { - u, err := strconv.ParseInt(umask, 8, 0) - if err != nil { - return errors.Wrap(err, "invalid umask value") - } - umaskFn(int(u)) + umask := env.Get(umaskEnvVar) + if umask == "" { + return nil + } + + u, err := strconv.ParseInt(umask, 8, 0) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("invalid umask value %s", umask)) } + umaskFn(int(u)) return nil } diff --git a/launch/umask_unix_test.go b/launch/umask_unix_test.go index db4d3b637..02cfacfb6 100644 --- a/launch/umask_unix_test.go +++ b/launch/umask_unix_test.go @@ -33,7 +33,7 @@ func testUmask(t *testing.T, when spec.G, it spec.S) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - environ := env.NewLaunchEnv([]string{"UMASK=" + tt.umask}, "", "") + environ := env.NewLaunchEnv([]string{"CNB_LAUNCH_UMASK=" + tt.umask}, "", "") var called int spy := func(m int) int { @@ -50,7 +50,7 @@ func testUmask(t *testing.T, when spec.G, it spec.S) { }) it("returns error for invalid umask", func() { - environ := env.NewLaunchEnv([]string{"UMASK=invalid"}, "", "") + environ := env.NewLaunchEnv([]string{"CNB_LAUNCH_UMASK=invalid"}, "", "") err := launch.SetUmaskWith(environ, func(int) int { return 0 })