// Program compare-cap is a sanity check that Go's cap package is // inter-operable with the C libcap. package main import ( "log" "os" "syscall" "unsafe" "kernel.org/pub/linux/libs/security/libcap/cap" ) // #include // #include // #cgo CFLAGS: -I../libcap/include // #cgo LDFLAGS: -L../libcap -lcap import "C" // tryFileCaps attempts to use the cap package to manipulate file // capabilities. No reference to libcap in this function. func tryFileCaps() { saved := cap.GetProc() // Capabilities we will place on a file. want := cap.NewSet() if err := want.SetFlag(cap.Permitted, true, cap.SETFCAP, cap.DAC_OVERRIDE); err != nil { log.Fatalf("failed to explore desired file capability: %v", err) } if err := want.SetFlag(cap.Effective, true, cap.SETFCAP, cap.DAC_OVERRIDE); err != nil { log.Fatalf("failed to raise the effective bits: %v", err) } if perm, err := saved.GetFlag(cap.Permitted, cap.SETFCAP); err != nil { log.Fatalf("failed to read capability: %v", err) } else if !perm { log.Printf("skipping file cap tests - insufficient privilege") return } if err := saved.ClearFlag(cap.Effective); err != nil { log.Fatalf("failed to drop effective: %v", err) } if err := saved.SetProc(); err != nil { log.Fatalf("failed to limit capabilities: %v", err) } // Failing attempt to remove capabilities. var empty *cap.Set if err := empty.SetFile(os.Args[0]); err != syscall.EPERM { log.Fatalf("failed to be blocked from removing filecaps: %v", err) } // The privilege we want (in the case we are root, we need the // DAC_OVERRIDE too). working, err := saved.Dup() if err != nil { log.Fatalf("failed to duplicate (%v): %v", saved, err) } if err := working.SetFlag(cap.Effective, true, cap.DAC_OVERRIDE, cap.SETFCAP); err != nil { log.Fatalf("failed to raise effective: %v", err) } // Critical (privilege using) section: if err := working.SetProc(); err != nil { log.Fatalf("failed to enable first effective privilege: %v", err) } // Delete capability if err := empty.SetFile(os.Args[0]); err != nil && err != syscall.ENODATA { log.Fatalf("blocked from removing filecaps: %v", err) } if got, err := cap.GetFile(os.Args[0]); err == nil { log.Fatalf("read deleted file caps: %v", got) } // Create file caps (this use employs the effective bit). if err := want.SetFile(os.Args[0]); err != nil { log.Fatalf("failed to set file capability: %v", err) } if err := saved.SetProc(); err != nil { log.Fatalf("failed to lower effective capability: %v", err) } // End of critical section. if got, err := cap.GetFile(os.Args[0]); err != nil { log.Fatalf("failed to read caps: %v", err) } else if is, was := got.String(), want.String(); is != was { log.Fatalf("read file caps do not match desired: got=%q want=%q", is, was) } // Now, do it all again but this time on an open file. f, err := os.Open(os.Args[0]) if err != nil { log.Fatalf("failed to open %q: %v", os.Args[0], err) } defer f.Close() // Failing attempt to remove capabilities. if err := empty.SetFd(f); err != syscall.EPERM { log.Fatalf("failed to be blocked from fremoving filecaps: %v", err) } // For the next section, we won't set the effective bit on the file. want.ClearFlag(cap.Effective) // Critical (privilege using) section: if err := working.SetProc(); err != nil { log.Fatalf("failed to enable effective privilege: %v", err) } if err := empty.SetFd(f); err != nil && err != syscall.ENODATA { log.Fatalf("blocked from fremoving filecaps: %v", err) } if got, err := cap.GetFd(f); err == nil { log.Fatalf("read fdeleted file caps: %v", got) } // This one does not set the effective bit. if err := want.SetFd(f); err != nil { log.Fatalf("failed to fset file capability: %v", err) } if err := saved.SetProc(); err != nil { log.Fatalf("failed to lower effective capability: %v", err) } // End of critical section. if got, err := cap.GetFd(f); err != nil { log.Fatalf("failed to fread caps: %v", err) } else if is, was := got.String(), want.String(); is != was { log.Fatalf("fread file caps do not match desired: got=%q want=%q", is, was) } } // tryProcCaps performs a set of convenience functions and compares // the results with those seen by libcap. At the end of this function, // the running process has no privileges at all. So exiting the // program is the only option. func tryProcCaps() { c := cap.GetProc() if v, err := c.GetFlag(cap.Permitted, cap.SETPCAP); err != nil { log.Fatalf("failed to read permitted setpcap: %v", err) } else if !v { log.Printf("skipping proc cap tests - insufficient privilege") return } if err := cap.SetUID(99); err != nil { log.Fatalf("failed to set uid=99: %v", err) } if u := syscall.Getuid(); u != 99 { log.Fatal("uid=99 did not take: got=%d", u) } if err := cap.SetGroups(98, 100, 101); err != nil { log.Fatalf("failed to set groups=98 [100, 101]: %v", err) } if g := syscall.Getgid(); g != 98 { log.Fatalf("gid=98 did not take: got=%d", g) } if gs, err := syscall.Getgroups(); err != nil { log.Fatalf("error getting groups: %v", err) } else if len(gs) != 2 || gs[0] != 100 || gs[1] != 101 { log.Fatalf("wrong of groups: got=%v want=[100 l01]", gs) } if mode := cap.GetMode(); mode != cap.ModeUncertain { log.Fatalf("initial mode should be 0 (UNCERTAIN), got: %d (%v)", mode, mode) } // To distinguish PURE1E and PURE1E_INIT we need an inheritable capability set. working := cap.GetProc() if err := working.SetFlag(cap.Inheritable, true, cap.SETPCAP); err != nil { log.Fatalf("unable to raise inheritable bit: %v", err) } if err := working.SetProc(); err != nil { log.Fatalf("failed to add inheritable bit: %v", err) } for i, mode := range []cap.Mode{cap.ModePure1E, cap.ModePure1EInit, cap.ModeNoPriv} { if err := mode.Set(); err != nil { log.Fatalf("[%d] in mode=%v and failed to set mode to %d (%v): %v", i, cap.GetMode(), mode, mode, err) } if got := cap.GetMode(); got != mode { log.Fatalf("[%d] unable to recognise mode %d (%v), got: %d (%v)", i, mode, mode, got, got) } cM := C.cap_get_mode() if mode != cap.Mode(cM) { log.Fatalf("[%d] C and Go disagree on mode: %d vs %d", cM, mode) } } // The current process is now without any access to privilege. } func main() { // Use the C libcap to obtain a non-trivial capability in text form (from init). cC := C.cap_get_pid(1) if cC == nil { log.Fatal("basic c caps from init function failure") } defer C.cap_free(unsafe.Pointer(cC)) var tCLen C.ssize_t tC := C.cap_to_text(cC, &tCLen) if tC == nil { log.Fatal("basic c init caps -> text failure") } defer C.cap_free(unsafe.Pointer(tC)) importT := C.GoString(tC) if got, want := len(importT), int(tCLen); got != want { log.Fatalf("C string import failed: got=%d [%q] want=%d", got, importT, want) } // Validate that it can be decoded in Go. cGo, err := cap.FromText(importT) if err != nil { log.Fatalf("go parsing of c text import failed: %v", err) } // Validate that it matches the one directly loaded in Go. c, err := cap.GetPID(1) if err != nil { log.Fatalf("...failed to read init's capabilities:", err) } tGo := c.String() if got, want := tGo, cGo.String(); got != want { log.Fatalf("go text rep does not match c: got=%q, want=%q", got, want) } // Export it in text form again from Go. tForC := C.CString(tGo) defer C.free(unsafe.Pointer(tForC)) // Validate it can be encoded in C. cC2 := C.cap_from_text(tForC) if cC2 == nil { log.Fatal("go text rep not parsable by c") } defer C.cap_free(unsafe.Pointer(cC2)) // Validate that it can be exported in binary form in C const enoughForAnyone = 1000 eC := make([]byte, enoughForAnyone) eCLen := C.cap_copy_ext(unsafe.Pointer(&eC[0]), cC2, C.ssize_t(len(eC))) if eCLen < 5 { log.Fatalf("c export yielded bad length: %d", eCLen) } // Validate that it can be imported from binary in Go iGo, err := cap.Import(eC[:eCLen]) if err != nil { log.Fatalf("go import of c binary failed: %v", err) } if got, want := iGo.String(), importT; got != want { log.Fatalf("go import of c binary miscompare: got=%q want=%q", got, want) } // Validate that it can be exported in binary in Go iE, err := iGo.Export() if err != nil { log.Fatalf("go failed to export binary: %v", err) } // Validate that it can be imported in binary in C iC := C.cap_copy_int_check(unsafe.Pointer(&iE[0]), C.ssize_t(len(iE))) if iC == nil { log.Fatal("c failed to import go binary") } defer C.cap_free(unsafe.Pointer(iC)) fC := C.cap_to_text(iC, &tCLen) if fC == nil { log.Fatal("basic c init caps -> text failure") } defer C.cap_free(unsafe.Pointer(fC)) if got, want := C.GoString(fC), importT; got != want { log.Fatalf("c import from go yielded bad caps: got=%q want=%q", got, want) } // Validate that everyone agrees what all is: want := "=ep" all, err := cap.FromText("all=ep") if err != nil { log.Fatalf("unable to parse all=ep: %v", err) } if got := all.String(); got != want { log.Fatalf("all decode failed in Go: got=%q, want=%q", got, want) } // Validate some random values stringify consistently between // libcap.cap_to_text() and (*cap.Set).String(). mb := cap.MaxBits() sample := cap.NewSet() for c := cap.Value(0); c < 7*mb; c += 3 { n := int(c) raise, f := c%mb, cap.Flag(c/mb)%3 sample.SetFlag(f, true, raise) if v, err := cap.FromText(sample.String()); err != nil { log.Fatalf("[%d] cap to text for %q not reversible: %v", n, sample, err) } else if cf, err := v.Compare(sample); err != nil { log.Fatalf("[%d] FromText generated bad capability from %q: %v", n, sample, err) } else if cf != 0 { log.Fatalf("[%d] text import got=%q want=%q", n, v, sample) } e, err := sample.Export() if err != nil { log.Fatalf("[%d] failed to export %q: %v", n, sample, err) } i, err := cap.Import(e) if err != nil { log.Fatalf("[%d] failed to import %q: %v", n, sample, err) } if cf, err := i.Compare(sample); err != nil { log.Fatalf("[%d] failed to compare %q vs original:%q", n, i, sample) } else if cf != 0 { log.Fatalf("[%d] import got=%q want=%q", n, i, sample) } // Confirm that importing this portable binary // representation in libcap and converting to text, // generates the same text as Go generates. This was // broken prior to v0.2.41. cCap := C.cap_copy_int(unsafe.Pointer(&e[0])) if cCap == nil { log.Fatalf("[%d] C import failed for %q export", n, sample) } var tCLen C.ssize_t tC := C.cap_to_text(cCap, &tCLen) if tC == nil { log.Fatalf("[%d] basic c init caps -> text failure", n) } C.cap_free(unsafe.Pointer(cCap)) importT := C.GoString(tC) C.cap_free(unsafe.Pointer(tC)) if got, want := len(importT), int(tCLen); got != want { log.Fatalf("[%d] C text generated wrong length: Go=%d, C=%d", n, got, want) } if got, want := importT, sample.String(); got != want { log.Fatalf("[%d] C and Go text rep disparity: C=%q Go=%q", n, got, want) } } iab, err := cap.IABFromText("cap_chown,!cap_setuid,^cap_setgid") if err != nil { log.Fatalf("failed to initialize iab from text: %v", err) } cIAB := C.cap_iab_init() defer C.cap_free(unsafe.Pointer(cIAB)) for c := cap.MaxBits(); c > 0; { c-- if en, err := iab.GetVector(cap.Inh, c); err != nil { log.Fatalf("failed to read iab.i[%v]", c) } else if en { if C.cap_iab_set_vector(cIAB, C.CAP_IAB_INH, C.cap_value_t(int(c)), C.CAP_SET) != 0 { log.Fatalf("failed to set C's AIB.I %v: %v", c) } } if en, err := iab.GetVector(cap.Amb, c); err != nil { log.Fatalf("failed to read iab.a[%v]", c) } else if en { if C.cap_iab_set_vector(cIAB, C.CAP_IAB_AMB, C.cap_value_t(int(c)), C.CAP_SET) != 0 { log.Fatalf("failed to set C's AIB.A %v: %v", c) } } if en, err := iab.GetVector(cap.Bound, c); err != nil { log.Fatalf("failed to read iab.b[%v]", c) } else if en { if C.cap_iab_set_vector(cIAB, C.CAP_IAB_BOUND, C.cap_value_t(int(c)), C.CAP_SET) != 0 { log.Fatalf("failed to set C's AIB.B %v: %v", c) } } } iabC := C.cap_iab_to_text(cIAB) if iabC == nil { log.Fatalf("failed to get text from C for %q", iab) } defer C.cap_free(unsafe.Pointer(iabC)) if got, want := C.GoString(iabC), iab.String(); got != want { log.Fatalf("IAB for Go and C differ: got=%q, want=%q", got, want) } // Next, we attempt to manipulate some file capabilities on // the running program. These are optional, based on whether // the current program is capable enough and do not involve // any cgo calls to libcap. tryFileCaps() // Nothing left to do but exit after this one. tryProcCaps() log.Printf("compare-cap success!") }