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
+53 -14
View File
@@ -15,6 +15,7 @@ import (
"golang.org/x/sys/unix"
"golang.org/x/xerrors"
"kernel.org/pub/linux/libs/security/libcap/cap"
)
// CLI runs the agent-exec command. It should only be called by the cli package.
@@ -48,14 +49,6 @@ func CLI() error {
return xerrors.Errorf("no exec command provided %+v", os.Args)
}
if *nice == unset {
// If an explicit nice score isn't set, we use the default.
*nice, err = defaultNiceScore()
if err != nil {
return xerrors.Errorf("get default nice score: %w", err)
}
}
if *oom == unset {
// If an explicit oom score isn't set, we use the default.
*oom, err = defaultOOMScore()
@@ -64,12 +57,31 @@ func CLI() error {
}
}
err = unix.Setpriority(unix.PRIO_PROCESS, 0, *nice)
if *nice == unset {
// If an explicit nice score isn't set, we use the default.
*nice, err = defaultNiceScore()
if err != nil {
return xerrors.Errorf("get default nice score: %w", err)
}
}
// We drop effective caps prior to setting dumpable so that we limit the
// impact of someone attempting to hijack the process (i.e. with a debugger)
// to take advantage of the capabilities of the agent process. We encourage
// users to set cap_net_admin on the agent binary for improved networking
// performance and doing so results in the process having its SET_DUMPABLE
// attribute disabled (meaning we cannot adjust the oom score).
err = dropEffectiveCaps()
if err != nil {
// We alert the user instead of failing the command since it can be difficult to debug
// for a template admin otherwise. It's quite possible (and easy) to set an
// inappriopriate value for niceness.
printfStdErr("failed to adjust niceness to %d for cmd %+v: %v", *nice, args, err)
printfStdErr("failed to drop effective caps: %v", err)
}
// Set dumpable to 1 so that we can adjust the oom score. If the process
// doesn't have capabilities or has an suid/sgid bit set, this is already
// set.
err = unix.Prctl(unix.PR_SET_DUMPABLE, 1, 0, 0, 0)
if err != nil {
printfStdErr("failed to set dumpable: %v", err)
}
err = writeOOMScoreAdj(*oom)
@@ -77,7 +89,21 @@ func CLI() error {
// We alert the user instead of failing the command since it can be difficult to debug
// for a template admin otherwise. It's quite possible (and easy) to set an
// inappriopriate value for oom_score_adj.
printfStdErr("failed to adjust oom score to %d for cmd %+v: %v", *oom, args, err)
printfStdErr("failed to adjust oom score to %d for cmd %+v: %v", *oom, execArgs(os.Args), err)
}
// Set dumpable back to 0 just to be safe. It's not inherited for execve anyways.
err = unix.Prctl(unix.PR_SET_DUMPABLE, 0, 0, 0, 0)
if err != nil {
printfStdErr("failed to unset dumpable: %v", err)
}
err = unix.Setpriority(unix.PRIO_PROCESS, 0, *nice)
if err != nil {
// We alert the user instead of failing the command since it can be difficult to debug
// for a template admin otherwise. It's quite possible (and easy) to set an
// inappriopriate value for niceness.
printfStdErr("failed to adjust niceness to %d for cmd %+v: %v", *nice, args, err)
}
path, err := exec.LookPath(args[0])
@@ -151,3 +177,16 @@ func execArgs(args []string) []string {
func printfStdErr(format string, a ...any) {
_, _ = fmt.Fprintf(os.Stderr, "coder-agent: %s\n", fmt.Sprintf(format, a...))
}
func dropEffectiveCaps() error {
proc := cap.GetProc()
err := proc.ClearFlag(cap.Effective)
if err != nil {
return xerrors.Errorf("clear effective caps: %w", err)
}
err = proc.SetProc()
if err != nil {
return xerrors.Errorf("set proc: %w", err)
}
return nil
}