@@ -9,17 +9,16 @@ import (
9
9
"os"
10
10
"os/exec"
11
11
"runtime"
12
+ "slices"
12
13
"strconv"
13
14
"strings"
14
15
"syscall"
15
16
16
17
"golang.org/x/sys/unix"
17
18
"golang.org/x/xerrors"
19
+ "kernel.org/pub/linux/libs/security/libcap/cap"
18
20
)
19
21
20
- // unset is set to an invalid value for nice and oom scores.
21
- const unset = - 2000
22
-
23
22
// CLI runs the agent-exec command. It should only be called by the cli package.
24
23
func CLI () error {
25
24
// We lock the OS thread here to avoid a race condition where the nice priority
@@ -51,6 +50,14 @@ func CLI() error {
51
50
return xerrors .Errorf ("no exec command provided %+v" , os .Args )
52
51
}
53
52
53
+ if * oom == unset {
54
+ // If an explicit oom score isn't set, we use the default.
55
+ * oom , err = defaultOOMScore ()
56
+ if err != nil {
57
+ return xerrors .Errorf ("get default oom score: %w" , err )
58
+ }
59
+ }
60
+
54
61
if * nice == unset {
55
62
// If an explicit nice score isn't set, we use the default.
56
63
* nice , err = defaultNiceScore ()
@@ -59,36 +66,62 @@ func CLI() error {
59
66
}
60
67
}
61
68
62
- if * oom == unset {
63
- // If an explicit oom score isn't set, we use the default.
64
- * oom , err = defaultOOMScore ()
65
- if err != nil {
66
- return xerrors .Errorf ("get default oom score: %w" , err )
67
- }
69
+ // We drop effective caps prior to setting dumpable so that we limit the
70
+ // impact of someone attempting to hijack the process (i.e. with a debugger)
71
+ // to take advantage of the capabilities of the agent process. We encourage
72
+ // users to set cap_net_admin on the agent binary for improved networking
73
+ // performance and doing so results in the process having its SET_DUMPABLE
74
+ // attribute disabled (meaning we cannot adjust the oom score).
75
+ err = dropEffectiveCaps ()
76
+ if err != nil {
77
+ printfStdErr ("failed to drop effective caps: %v" , err )
68
78
}
69
79
70
- err = unix .Setpriority (unix .PRIO_PROCESS , 0 , * nice )
80
+ // Set dumpable to 1 so that we can adjust the oom score. If the process
81
+ // doesn't have capabilities or has an suid/sgid bit set, this is already
82
+ // set.
83
+ err = unix .Prctl (unix .PR_SET_DUMPABLE , 1 , 0 , 0 , 0 )
71
84
if err != nil {
72
- // We alert the user instead of failing the command since it can be difficult to debug
73
- // for a template admin otherwise. It's quite possible (and easy) to set an
74
- // inappriopriate value for niceness.
75
- printfStdErr ("failed to adjust niceness to %d for cmd %+v: %v" , * nice , args , err )
85
+ printfStdErr ("failed to set dumpable: %v" , err )
76
86
}
77
87
78
88
err = writeOOMScoreAdj (* oom )
79
89
if err != nil {
80
90
// We alert the user instead of failing the command since it can be difficult to debug
81
91
// for a template admin otherwise. It's quite possible (and easy) to set an
82
92
// inappriopriate value for oom_score_adj.
83
- printfStdErr ("failed to adjust oom score to %d for cmd %+v: %v" , * oom , args , err )
93
+ printfStdErr ("failed to adjust oom score to %d for cmd %+v: %v" , * oom , execArgs (os .Args ), err )
94
+ }
95
+
96
+ // Set dumpable back to 0 just to be safe. It's not inherited for execve anyways.
97
+ err = unix .Prctl (unix .PR_SET_DUMPABLE , 0 , 0 , 0 , 0 )
98
+ if err != nil {
99
+ printfStdErr ("failed to unset dumpable: %v" , err )
100
+ }
101
+
102
+ err = unix .Setpriority (unix .PRIO_PROCESS , 0 , * nice )
103
+ if err != nil {
104
+ // We alert the user instead of failing the command since it can be difficult to debug
105
+ // for a template admin otherwise. It's quite possible (and easy) to set an
106
+ // inappriopriate value for niceness.
107
+ printfStdErr ("failed to adjust niceness to %d for cmd %+v: %v" , * nice , args , err )
84
108
}
85
109
86
110
path , err := exec .LookPath (args [0 ])
87
111
if err != nil {
88
112
return xerrors .Errorf ("look path: %w" , err )
89
113
}
90
114
91
- return syscall .Exec (path , args , os .Environ ())
115
+ // Remove environment variables specific to the agentexec command. This is
116
+ // especially important for environments that are attempting to develop Coder in Coder.
117
+ env := os .Environ ()
118
+ env = slices .DeleteFunc (env , func (e string ) bool {
119
+ return strings .HasPrefix (e , EnvProcPrioMgmt ) ||
120
+ strings .HasPrefix (e , EnvProcOOMScore ) ||
121
+ strings .HasPrefix (e , EnvProcNiceScore )
122
+ })
123
+
124
+ return syscall .Exec (path , args , env )
92
125
}
93
126
94
127
func defaultNiceScore () (int , error ) {
@@ -154,3 +187,16 @@ func execArgs(args []string) []string {
154
187
func printfStdErr (format string , a ... any ) {
155
188
_ , _ = fmt .Fprintf (os .Stderr , "coder-agent: %s\n " , fmt .Sprintf (format , a ... ))
156
189
}
190
+
191
+ func dropEffectiveCaps () error {
192
+ proc := cap .GetProc ()
193
+ err := proc .ClearFlag (cap .Effective )
194
+ if err != nil {
195
+ return xerrors .Errorf ("clear effective caps: %w" , err )
196
+ }
197
+ err = proc .SetProc ()
198
+ if err != nil {
199
+ return xerrors .Errorf ("set proc: %w" , err )
200
+ }
201
+ return nil
202
+ }
0 commit comments