fix: fix oom_score adjustments failing if caps set (#15758)

- Fixes an issue where oom scores would fail to be adjusted in cases
where the `coder` binary has capabilities set on it. This is because
`PR_SET_DUMPABLE` is set to `0` when a process is executed with elevated
capabilities. The fix is to flip `PR_SET_DUMPABLE` to `1` prior to
writing to `oom_score_adj`.
This commit is contained in:
Jon Ayers
2024-12-05 15:30:58 +02:00
committed by GitHub
parent 0109c9fe6f
commit f8d938d299
4 changed files with 103 additions and 16 deletions
+44 -2
View File
@@ -18,6 +18,7 @@ import (
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/testutil"
)
@@ -50,6 +51,32 @@ func TestCLI(t *testing.T) {
requireOOMScore(t, cmd.Process.Pid, expectedOOM)
requireNiceScore(t, cmd.Process.Pid, expectedNice)
})
t.Run("Capabilities", func(t *testing.T) {
testdir := filepath.Dir(TestBin)
capDir := filepath.Join(testdir, "caps")
err := os.Mkdir(capDir, 0o755)
require.NoError(t, err)
bin := buildBinary(capDir)
// Try to set capabilities on the binary. This should work fine in CI but
// it's possible some developers may be working in an environment where they don't have the necessary permissions.
err = setCaps(t, bin, "cap_net_admin")
if os.Getenv("CI") != "" {
require.NoError(t, err)
} else if err != nil {
t.Skipf("unable to set capabilities for test: %v", err)
}
ctx := testutil.Context(t, testutil.WaitMedium)
cmd, path := binCmd(ctx, t, bin, 123, 12)
err = cmd.Start()
require.NoError(t, err)
go cmd.Wait()
waitForSentinel(ctx, t, cmd, path)
// This is what we're really testing, a binary with added capabilities requires setting dumpable.
requireOOMScore(t, cmd.Process.Pid, 123)
requireNiceScore(t, cmd.Process.Pid, 12)
})
}
func requireNiceScore(t *testing.T, pid int, score int) {
@@ -94,7 +121,7 @@ func waitForSentinel(ctx context.Context, t *testing.T, cmd *exec.Cmd, path stri
}
}
func cmd(ctx context.Context, t *testing.T, oom, nice int) (*exec.Cmd, string) {
func binCmd(ctx context.Context, t *testing.T, bin string, oom, nice int) (*exec.Cmd, string) {
var (
args = execArgs(oom, nice)
dir = t.TempDir()
@@ -103,7 +130,7 @@ func cmd(ctx context.Context, t *testing.T, oom, nice int) (*exec.Cmd, string) {
args = append(args, "sh", "-c", fmt.Sprintf("touch %s && sleep 10m", file))
//nolint:gosec
cmd := exec.CommandContext(ctx, TestBin, args...)
cmd := exec.CommandContext(ctx, bin, args...)
// We set this so we can also easily kill the sleep process the shell spawns.
cmd.SysProcAttr = &syscall.SysProcAttr{
@@ -127,6 +154,10 @@ func cmd(ctx context.Context, t *testing.T, oom, nice int) (*exec.Cmd, string) {
return cmd, file
}
func cmd(ctx context.Context, t *testing.T, oom, nice int) (*exec.Cmd, string) {
return binCmd(ctx, t, TestBin, oom, nice)
}
func expectedOOMScore(t *testing.T) int {
t.Helper()
@@ -171,3 +202,14 @@ func execArgs(oom int, nice int) []string {
execArgs = append(execArgs, "--")
return execArgs
}
func setCaps(t *testing.T, bin string, caps ...string) error {
t.Helper()
setcap := fmt.Sprintf("sudo -n setcap %s=ep %s", strings.Join(caps, ", "), bin)
out, err := exec.CommandContext(context.Background(), "sh", "-c", setcap).CombinedOutput()
if err != nil {
return xerrors.Errorf("setcap %q (%s): %w", setcap, out, err)
}
return nil
}