1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
package cap
import (
"errors"
"os"
"runtime"
"syscall"
"unsafe"
)
// Launcher holds a configuration for launching a child process with
// capability state different from (generally more restricted than)
// the parent.
//
// Note, go1.10 is the earliest version of the Go toolchain that can
// support this abstraction.
type Launcher struct {
path string
args []string
env []string
callbackFn func(pa *syscall.ProcAttr, data interface{}) error
changeUIDs bool
uid int
changeGIDs bool
gid int
groups []int
changeMode bool
mode Mode
iab *IAB
chroot string
}
// NewLauncher returns a new launcher for the specified program path
// and args with the specified environment.
func NewLauncher(path string, args []string, env []string) *Launcher {
return &Launcher{
path: path,
args: args,
env: env,
}
}
// Callback specifies a callback for Launch() to call before changing
// privilege. The only thing that is assumed is that the OS thread in
// use to call this callback function at launch time will be the one
// that ultimately calls fork. Any returned error value of said
// function will terminate the launch process. A nil callback (the
// default) is ignored. The specified callback fn should not call any
// "cap" package functions since this may deadlock or generate
// undefined behavior for the parent process.
func (attr *Launcher) Callback(fn func(*syscall.ProcAttr, interface{}) error) {
attr.callbackFn = fn
}
// SetUID specifies the UID to be used by the launched command.
func (attr *Launcher) SetUID(uid int) {
attr.changeUIDs = true
attr.uid = uid
}
// SetGroups specifies the GID and supplementary groups for the
// launched command.
func (attr *Launcher) SetGroups(gid int, groups []int) {
attr.changeGIDs = true
attr.gid = gid
attr.groups = groups
}
// SetMode specifies the libcap Mode to be used by the launched command.
func (attr *Launcher) SetMode(mode Mode) {
attr.changeMode = true
attr.mode = mode
}
// SetIAB specifies the AIB capability vectors to be inherited by the
// launched command. A nil value means the prevailing vectors of the
// parent will be inherited.
func (attr *Launcher) SetIAB(iab *IAB) {
attr.iab = iab
}
// SetChroot specifies the chroot value to be used by the launched
// command. An empty value means no-change from the prevailing value.
func (attr *Launcher) SetChroot(root string) {
attr.chroot = root
}
// lResult is used to get the result from the doomed launcher thread.
type lResult struct {
pid int
err error
}
// ErrLaunchFailed is returned if a launch was aborted with no more
// specific error.
var ErrLaunchFailed = errors.New("launch failed")
// ErrNoLaunch indicates the go runtime available to this binary does
// not reliably support launching. See cap.LaunchSupported.
var ErrNoLaunch = errors.New("launch not supported")
// ErrAmbiguousChroot indicates that the Launcher is being used in
// addition to a callback supplied Chroot. The former should be used
// exclusively for this.
var ErrAmbiguousChroot = errors.New("use Launcher for chroot")
// ErrAmbiguousIDs indicates that the Launcher is being used in
// addition to a callback supplied Credentials. The former should be
// used exclusively for this.
var ErrAmbiguousIDs = errors.New("use Launcher for uids and gids")
// ErrAmbiguousAmbient indicates that the Launcher is being used in
// addition to a callback supplied ambient set and the former should
// be used exclusively in a Launch call.
var ErrAmbiguousAmbient = errors.New("use Launcher for ambient caps")
// lName is the name we temporarily give to the launcher thread. Note,
// this will likely stick around in the process tree if the Go runtime
// is not cleaning up locked launcher OS threads.
var lName = []byte("cap-launcher\000")
// <uapi/linux/prctl.h>
const prSetName = 15
//go:uintptrescapes
func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<- struct{}) {
if quit != nil {
defer close(quit)
}
pid := syscall.Getpid()
// Wait until we are not scheduled on the parent thread. We
// will exit this thread once the child has launched, and
// don't want other goroutines to use this thread afterwards.
runtime.LockOSThread()
tid := syscall.Gettid()
if tid == pid {
// Force the go runtime to find a new thread to run on.
quit := make(chan struct{})
go launch(result, attr, data, quit)
// Wait for that go routine to complete.
<-quit
runtime.UnlockOSThread()
return
}
// By never releasing the LockOSThread here, we guarantee that
// the runtime will terminate the current OS thread once this
// function returns.
// Name the launcher thread - transient, but helps to debug if
// the callbackFn or something else hangs up.
singlesc.prctlrcall(prSetName, uintptr(unsafe.Pointer(&lName[0])), 0)
// Provide a way to serialize the caller on the thread
// completing.
defer close(result)
pa := &syscall.ProcAttr{
Files: []uintptr{0, 1, 2},
}
var err error
var needChroot bool
if len(attr.env) != 0 {
pa.Env = attr.env
} else {
pa.Env = os.Environ()
}
if attr.callbackFn != nil {
if err = attr.callbackFn(pa, data); err != nil {
goto abort
}
}
if needChroot, err = validatePA(pa, attr.chroot); err != nil {
goto abort
}
if attr.changeUIDs {
if err = singlesc.setUID(attr.uid); err != nil {
goto abort
}
}
if attr.changeGIDs {
if err = singlesc.setGroups(attr.gid, attr.groups); err != nil {
goto abort
}
}
if attr.changeMode {
if err = singlesc.setMode(attr.mode); err != nil {
goto abort
}
}
if attr.iab != nil {
if err = singlesc.iabSetProc(attr.iab); err != nil {
goto abort
}
}
if needChroot {
c := GetProc()
if err = c.SetFlag(Effective, true, SYS_CHROOT); err != nil {
goto abort
}
if err = singlesc.setProc(c); err != nil {
goto abort
}
}
pid, err = syscall.ForkExec(attr.path, attr.args, pa)
abort:
if err != nil {
pid = -1
}
result <- lResult{pid: pid, err: err}
}
// Launch performs a new program launch with security state specified
// in the supplied attr settings.
func (attr *Launcher) Launch(data interface{}) (int, error) {
if attr.path == "" || len(attr.args) == 0 {
return -1, ErrLaunchFailed
}
if !LaunchSupported {
return -1, ErrNoLaunch
}
scwMu.Lock()
defer scwMu.Unlock()
result := make(chan lResult)
go launch(result, attr, data, nil)
for {
select {
case v, ok := <-result:
if !ok {
return -1, ErrLaunchFailed
}
return v.pid, v.err
default:
runtime.Gosched()
}
}
}
|