| // 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 <stdlib.h> |
| // #include <sys/capability.h> |
| // #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 privelege. |
| } |
| |
| 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(unsafe.Pointer(&iE[0])) |
| if iC == nil { |
| log.Fatal("c failed to import go binary") |
| } |
| defer C.cap_free(unsafe.Pointer(iC)) |
| fC := C.cap_to_text(cC, &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!") |
| } |