@@ -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
+ }