Skip to content

Commit 638aa7c

Browse files
authored
Merge pull request #176 from kolyshkin/cap-ambient
capability: add separate ambient and bound API
2 parents 4e88a80 + c1ade77 commit 638aa7c

File tree

5 files changed

+278
-29
lines changed

5 files changed

+278
-29
lines changed

Diff for: capability/capability.go

+32
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,35 @@ func NewFile2(path string) (Capabilities, error) {
142142
func LastCap() (Cap, error) {
143143
return lastCap()
144144
}
145+
146+
// GetAmbient determines if a specific ambient capability is raised in the
147+
// calling thread.
148+
func GetAmbient(c Cap) (bool, error) {
149+
return getAmbient(c)
150+
}
151+
152+
// SetAmbient raises or lowers specified ambient capabilities for the calling
153+
// thread. To complete successfully, the prevailing effective capability set
154+
// must have a raised CAP_SETPCAP. Further, to raise a specific ambient
155+
// capability the inheritable and permitted sets of the calling thread must
156+
// already contain the specified capability.
157+
func SetAmbient(raise bool, caps ...Cap) error {
158+
return setAmbient(raise, caps...)
159+
}
160+
161+
// ResetAmbient resets all of the ambient capabilities for the calling thread
162+
// to their lowered value.
163+
func ResetAmbient() error {
164+
return resetAmbient()
165+
}
166+
167+
// GetBound determines if a specific bounding capability is raised in the
168+
// calling thread.
169+
func GetBound(c Cap) (bool, error) {
170+
return getBound(c)
171+
}
172+
173+
// DropBound lowers the specified bounding set capability.
174+
func DropBound(caps ...Cap) error {
175+
return dropBound(caps...)
176+
}

Diff for: capability/capability_linux.go

+65-22
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ func newPid(pid int) (c Capabilities, retErr error) {
117117
return
118118
}
119119

120+
func ignoreEINVAL(err error) error {
121+
if errors.Is(err, syscall.EINVAL) {
122+
err = nil
123+
}
124+
return err
125+
}
126+
120127
type capsV3 struct {
121128
hdr capHeader
122129
data [2]capData
@@ -327,7 +334,7 @@ func (c *capsV3) Load() (err error) {
327334
return
328335
}
329336

330-
func (c *capsV3) Apply(kind CapType) (err error) {
337+
func (c *capsV3) Apply(kind CapType) error {
331338
if c.hdr.pid != 0 {
332339
return errors.New("unable to modify capabilities of another process")
333340
}
@@ -339,21 +346,17 @@ func (c *capsV3) Apply(kind CapType) (err error) {
339346
var data [2]capData
340347
err = capget(&c.hdr, &data[0])
341348
if err != nil {
342-
return
349+
return err
343350
}
344351
if (1<<uint(CAP_SETPCAP))&data[0].effective != 0 {
345352
for i := Cap(0); i <= last; i++ {
346353
if c.Get(BOUNDING, i) {
347354
continue
348355
}
349-
err = prctl(syscall.PR_CAPBSET_DROP, uintptr(i), 0, 0, 0)
356+
// Ignore EINVAL since the capability may not be supported in this system.
357+
err = ignoreEINVAL(dropBound(i))
350358
if err != nil {
351-
// Ignore EINVAL since the capability may not be supported in this system.
352-
if err == syscall.EINVAL { //nolint:errorlint // Errors from syscall are bare.
353-
err = nil
354-
continue
355-
}
356-
return
359+
return err
357360
}
358361
}
359362
}
@@ -362,33 +365,73 @@ func (c *capsV3) Apply(kind CapType) (err error) {
362365
if kind&CAPS == CAPS {
363366
err = capset(&c.hdr, &c.data[0])
364367
if err != nil {
365-
return
368+
return err
366369
}
367370
}
368371

369372
if kind&AMBS == AMBS {
370-
err = prctl(pr_CAP_AMBIENT, pr_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0)
371-
if err != nil && err != syscall.EINVAL { //nolint:errorlint // Errors from syscall are bare.
372-
// Ignore EINVAL as not supported on kernels before 4.3
373-
return
373+
// Ignore EINVAL as not supported on kernels before 4.3
374+
err = ignoreEINVAL(resetAmbient())
375+
if err != nil {
376+
return err
374377
}
375378
for i := Cap(0); i <= last; i++ {
376379
if !c.Get(AMBIENT, i) {
377380
continue
378381
}
379-
err = prctl(pr_CAP_AMBIENT, pr_CAP_AMBIENT_RAISE, uintptr(i), 0, 0)
382+
// Ignore EINVAL as not supported on kernels before 4.3
383+
err = ignoreEINVAL(setAmbient(true, i))
380384
if err != nil {
381-
// Ignore EINVAL as not supported on kernels before 4.3
382-
if err == syscall.EINVAL { //nolint:errorlint // Errors from syscall are bare.
383-
err = nil
384-
continue
385-
}
386-
return
385+
return err
387386
}
388387
}
389388
}
390389

391-
return
390+
return nil
391+
}
392+
393+
func getAmbient(c Cap) (bool, error) {
394+
res, err := prctlRetInt(pr_CAP_AMBIENT, pr_CAP_AMBIENT_IS_SET, uintptr(c))
395+
if err != nil {
396+
return false, err
397+
}
398+
return res > 0, nil
399+
}
400+
401+
func setAmbient(raise bool, caps ...Cap) error {
402+
op := pr_CAP_AMBIENT_RAISE
403+
if !raise {
404+
op = pr_CAP_AMBIENT_LOWER
405+
}
406+
for _, val := range caps {
407+
err := prctl(pr_CAP_AMBIENT, op, uintptr(val))
408+
if err != nil {
409+
return err
410+
}
411+
}
412+
return nil
413+
}
414+
415+
func resetAmbient() error {
416+
return prctl(pr_CAP_AMBIENT, pr_CAP_AMBIENT_CLEAR_ALL, 0)
417+
}
418+
419+
func getBound(c Cap) (bool, error) {
420+
res, err := prctlRetInt(syscall.PR_CAPBSET_READ, uintptr(c), 0)
421+
if err != nil {
422+
return false, err
423+
}
424+
return res > 0, nil
425+
}
426+
427+
func dropBound(caps ...Cap) error {
428+
for _, val := range caps {
429+
err := prctl(syscall.PR_CAPBSET_DROP, uintptr(val), 0)
430+
if err != nil {
431+
return err
432+
}
433+
}
434+
return nil
392435
}
393436

394437
func newFile(path string) (c Capabilities, err error) {

Diff for: capability/capability_noop.go

+20
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,23 @@ func newFile(_ string) (Capabilities, error) {
2424
func lastCap() (Cap, error) {
2525
return -1, errNotSup
2626
}
27+
28+
func getAmbient(_ Cap) (bool, error) {
29+
return false, errNotSup
30+
}
31+
32+
func setAmbient(_ bool, _ ...Cap) error {
33+
return errNotSup
34+
}
35+
36+
func resetAmbient() error {
37+
return errNotSup
38+
}
39+
40+
func getBound(_ Cap) (bool, error) {
41+
return false, errNotSup
42+
}
43+
44+
func dropBound(_ ...Cap) error {
45+
return errNotSup
46+
}

Diff for: capability/capability_test.go

+147-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func requirePCapSet(t testing.TB) {
4242
}
4343

4444
// testInChild runs fn as a separate process, and returns its output.
45-
// This is useful for tests which manipulate capabilties, allowing to
45+
// This is useful for tests which manipulate capabilities, allowing to
4646
// preserve those of the main test process.
4747
//
4848
// The fn is a function which must end with os.Exit. In case exit code
@@ -150,6 +150,7 @@ func TestAmbientCapSet(t *testing.T) {
150150
}
151151

152152
func childAmbientCapSet() {
153+
runtime.LockOSThread()
153154
// We can't use t.Log etc. here, yet filename and line number is nice
154155
// to have. Set up and use the standard logger for this.
155156
log.SetFlags(log.Lshortfile)
@@ -227,3 +228,148 @@ func TestApplyOtherProcess(t *testing.T) {
227228
}
228229
}
229230
}
231+
232+
func TestGetSetResetAmbient(t *testing.T) {
233+
if runtime.GOOS != "linux" {
234+
_, err := GetAmbient(Cap(0))
235+
if err == nil {
236+
t.Error(runtime.GOOS, ": want error, got nil")
237+
}
238+
err = SetAmbient(false, Cap(0))
239+
if err == nil {
240+
t.Error(runtime.GOOS, ": want error, got nil")
241+
}
242+
err = ResetAmbient()
243+
if err == nil {
244+
t.Error(runtime.GOOS, ": want error, got nil")
245+
}
246+
return
247+
}
248+
249+
requirePCapSet(t)
250+
out := testInChild(t, childGetSetResetAmbient)
251+
t.Logf("output from child:\n%s", out)
252+
}
253+
254+
func childGetSetResetAmbient() {
255+
runtime.LockOSThread()
256+
log.SetFlags(log.Lshortfile)
257+
258+
pid, err := NewPid2(0)
259+
if err != nil {
260+
log.Fatal(err)
261+
}
262+
263+
list := []Cap{CAP_KILL, CAP_CHOWN, CAP_SYS_CHROOT}
264+
pid.Set(CAPS, list...)
265+
if err = pid.Apply(CAPS); err != nil {
266+
log.Fatal(err)
267+
}
268+
269+
// Set ambient caps from list.
270+
if err = SetAmbient(true, list...); err != nil {
271+
log.Fatal(err)
272+
}
273+
274+
// Check if they were set as expected.
275+
for _, cap := range list {
276+
want := true
277+
got, err := GetAmbient(cap)
278+
if err != nil {
279+
log.Fatalf("GetAmbient(%s): want nil, got error %v", cap, err)
280+
} else if want != got {
281+
log.Fatalf("Get(AMBIENT, %s): want %v, got %v", cap, want, got)
282+
}
283+
}
284+
285+
// Lower one ambient cap.
286+
const unsetIdx = 1
287+
if err = SetAmbient(false, list[unsetIdx]); err != nil {
288+
log.Fatal(err)
289+
}
290+
// Check they are set as expected.
291+
for i, cap := range list {
292+
want := i != unsetIdx
293+
got, err := GetAmbient(cap)
294+
if err != nil {
295+
log.Fatalf("GetAmbient(%s): want nil, got error %v", cap, err)
296+
} else if want != got {
297+
log.Fatalf("Get(AMBIENT, %s): want %v, got %v", cap, want, got)
298+
}
299+
}
300+
301+
// Lower all ambient caps.
302+
if err = ResetAmbient(); err != nil {
303+
log.Fatal(err)
304+
}
305+
for _, cap := range list {
306+
want := false
307+
got, err := GetAmbient(cap)
308+
if err != nil {
309+
log.Fatalf("GetAmbient(%s): want nil, got error %v", cap, err)
310+
} else if want != got {
311+
log.Fatalf("Get(AMBIENT, %s): want %v, got %v", cap, want, got)
312+
}
313+
}
314+
os.Exit(0)
315+
}
316+
317+
func TestGetBound(t *testing.T) {
318+
if runtime.GOOS != "linux" {
319+
_, err := GetBound(Cap(0))
320+
if err == nil {
321+
t.Error(runtime.GOOS, ": want error, got nil")
322+
}
323+
return
324+
}
325+
326+
last, err := LastCap()
327+
if err != nil {
328+
t.Fatalf("LastCap: %v", err)
329+
}
330+
for i := Cap(0); i < Cap(63); i++ {
331+
wantErr := i > last
332+
set, err := GetBound(i)
333+
t.Logf("GetBound(%q): %v, %v", i, set, err)
334+
if wantErr && err == nil {
335+
t.Errorf("GetBound(%q): want err, got nil", i)
336+
} else if !wantErr && err != nil {
337+
t.Errorf("GetBound(%q): want nil, got error %v", i, err)
338+
}
339+
}
340+
}
341+
342+
func TestDropBound(t *testing.T) {
343+
if runtime.GOOS != "linux" {
344+
err := DropBound(Cap(0))
345+
if err == nil {
346+
t.Error(runtime.GOOS, ": want error, got nil")
347+
}
348+
return
349+
}
350+
351+
requirePCapSet(t)
352+
out := testInChild(t, childDropBound)
353+
t.Logf("output from child:\n%s", out)
354+
}
355+
356+
func childDropBound() {
357+
runtime.LockOSThread()
358+
log.SetFlags(log.Lshortfile)
359+
360+
for i := Cap(2); i < Cap(4); i++ {
361+
err := DropBound(i)
362+
if err != nil {
363+
log.Fatalf("DropBound(%q): want nil, got error %v", i, err)
364+
}
365+
set, err := GetBound(i)
366+
if err != nil {
367+
log.Fatalf("GetBound(%q): want nil, got error %v", i, err)
368+
}
369+
if set {
370+
log.Fatalf("GetBound(%q): want false, got true", i)
371+
}
372+
}
373+
374+
os.Exit(0)
375+
}

0 commit comments

Comments
 (0)