1// Program compare-cap is a sanity check that Go's cap package is 2// inter-operable with the C libcap. 3package main 4 5import ( 6 "log" 7 "os" 8 "syscall" 9 "unsafe" 10 11 "kernel.org/pub/linux/libs/security/libcap/cap" 12) 13 14// #include <stdlib.h> 15// #include <sys/capability.h> 16// #cgo CFLAGS: -I../libcap/include 17// #cgo LDFLAGS: -L../libcap -lcap 18import "C" 19 20// tryFileCaps attempts to use the cap package to manipulate file 21// capabilities. No reference to libcap in this function. 22func tryFileCaps() { 23 saved := cap.GetProc() 24 25 // Capabilities we will place on a file. 26 want := cap.NewSet() 27 if err := want.SetFlag(cap.Permitted, true, cap.SETFCAP, cap.DAC_OVERRIDE); err != nil { 28 log.Fatalf("failed to explore desired file capability: %v", err) 29 } 30 if err := want.SetFlag(cap.Effective, true, cap.SETFCAP, cap.DAC_OVERRIDE); err != nil { 31 log.Fatalf("failed to raise the effective bits: %v", err) 32 } 33 34 if perm, err := saved.GetFlag(cap.Permitted, cap.SETFCAP); err != nil { 35 log.Fatalf("failed to read capability: %v", err) 36 } else if !perm { 37 log.Printf("skipping file cap tests - insufficient privilege") 38 return 39 } 40 41 if err := saved.ClearFlag(cap.Effective); err != nil { 42 log.Fatalf("failed to drop effective: %v", err) 43 } 44 if err := saved.SetProc(); err != nil { 45 log.Fatalf("failed to limit capabilities: %v", err) 46 } 47 48 // Failing attempt to remove capabilities. 49 var empty *cap.Set 50 if err := empty.SetFile(os.Args[0]); err != syscall.EPERM { 51 log.Fatalf("failed to be blocked from removing filecaps: %v", err) 52 } 53 54 // The privilege we want (in the case we are root, we need the 55 // DAC_OVERRIDE too). 56 working, err := saved.Dup() 57 if err != nil { 58 log.Fatalf("failed to duplicate (%v): %v", saved, err) 59 } 60 if err := working.SetFlag(cap.Effective, true, cap.DAC_OVERRIDE, cap.SETFCAP); err != nil { 61 log.Fatalf("failed to raise effective: %v", err) 62 } 63 64 // Critical (privilege using) section: 65 if err := working.SetProc(); err != nil { 66 log.Fatalf("failed to enable first effective privilege: %v", err) 67 } 68 // Delete capability 69 if err := empty.SetFile(os.Args[0]); err != nil && err != syscall.ENODATA { 70 log.Fatalf("blocked from removing filecaps: %v", err) 71 } 72 if got, err := cap.GetFile(os.Args[0]); err == nil { 73 log.Fatalf("read deleted file caps: %v", got) 74 } 75 // Create file caps (this use employs the effective bit). 76 if err := want.SetFile(os.Args[0]); err != nil { 77 log.Fatalf("failed to set file capability: %v", err) 78 } 79 if err := saved.SetProc(); err != nil { 80 log.Fatalf("failed to lower effective capability: %v", err) 81 } 82 // End of critical section. 83 84 if got, err := cap.GetFile(os.Args[0]); err != nil { 85 log.Fatalf("failed to read caps: %v", err) 86 } else if is, was := got.String(), want.String(); is != was { 87 log.Fatalf("read file caps do not match desired: got=%q want=%q", is, was) 88 } 89 90 // Now, do it all again but this time on an open file. 91 f, err := os.Open(os.Args[0]) 92 if err != nil { 93 log.Fatalf("failed to open %q: %v", os.Args[0], err) 94 } 95 defer f.Close() 96 97 // Failing attempt to remove capabilities. 98 if err := empty.SetFd(f); err != syscall.EPERM { 99 log.Fatalf("failed to be blocked from fremoving filecaps: %v", err) 100 } 101 102 // For the next section, we won't set the effective bit on the file. 103 want.ClearFlag(cap.Effective) 104 105 // Critical (privilege using) section: 106 if err := working.SetProc(); err != nil { 107 log.Fatalf("failed to enable effective privilege: %v", err) 108 } 109 if err := empty.SetFd(f); err != nil && err != syscall.ENODATA { 110 log.Fatalf("blocked from fremoving filecaps: %v", err) 111 } 112 if got, err := cap.GetFd(f); err == nil { 113 log.Fatalf("read fdeleted file caps: %v", got) 114 } 115 // This one does not set the effective bit. 116 if err := want.SetFd(f); err != nil { 117 log.Fatalf("failed to fset file capability: %v", err) 118 } 119 if got, err := cap.GetFd(f); err != nil { 120 log.Fatalf("failed to fread caps: %v", err) 121 } else if is, was := got.String(), want.String(); is != was { 122 log.Fatalf("fread file caps do not match desired: got=%q want=%q", is, was) 123 } 124 if err := empty.SetFd(f); err != nil && err != syscall.ENODATA { 125 log.Fatalf("blocked from cleanup fremoving filecaps: %v", err) 126 } 127 if err := saved.SetProc(); err != nil { 128 log.Fatalf("failed to lower effective capability: %v", err) 129 } 130 // End of critical section. 131} 132 133// tryProcCaps performs a set of convenience functions and compares 134// the results with those seen by libcap. At the end of this function, 135// the running process has no privileges at all. So exiting the 136// program is the only option. 137func tryProcCaps() { 138 c := cap.GetProc() 139 if v, err := c.GetFlag(cap.Permitted, cap.SETPCAP); err != nil { 140 log.Fatalf("failed to read permitted setpcap: %v", err) 141 } else if !v { 142 log.Printf("skipping proc cap tests - insufficient privilege") 143 return 144 } 145 if err := cap.SetUID(99); err != nil { 146 log.Fatalf("failed to set uid=99: %v", err) 147 } 148 if u := syscall.Getuid(); u != 99 { 149 log.Fatal("uid=99 did not take: got=%d", u) 150 } 151 if err := cap.SetGroups(98, 100, 101); err != nil { 152 log.Fatalf("failed to set groups=98 [100, 101]: %v", err) 153 } 154 if g := syscall.Getgid(); g != 98 { 155 log.Fatalf("gid=98 did not take: got=%d", g) 156 } 157 if gs, err := syscall.Getgroups(); err != nil { 158 log.Fatalf("error getting groups: %v", err) 159 } else if len(gs) != 2 || gs[0] != 100 || gs[1] != 101 { 160 log.Fatalf("wrong of groups: got=%v want=[100 l01]", gs) 161 } 162 163 if mode := cap.GetMode(); mode != cap.ModeHybrid { 164 log.Fatalf("initial mode should be 4 (HYBRID), got: %d (%v)", mode, mode) 165 } 166 167 // To distinguish PURE1E and PURE1E_INIT we need an inheritable capability set. 168 working := cap.GetProc() 169 if err := working.SetFlag(cap.Inheritable, true, cap.SETPCAP); err != nil { 170 log.Fatalf("unable to raise inheritable bit: %v", err) 171 } 172 if err := working.SetProc(); err != nil { 173 log.Fatalf("failed to add inheritable bit: %v", err) 174 } 175 176 for i, mode := range []cap.Mode{cap.ModePure1E, cap.ModePure1EInit, cap.ModeNoPriv} { 177 if err := mode.Set(); err != nil { 178 log.Fatalf("[%d] in mode=%v and failed to set mode to %d (%v): %v", i, cap.GetMode(), mode, mode, err) 179 } 180 if got := cap.GetMode(); got != mode { 181 log.Fatalf("[%d] unable to recognise mode %d (%v), got: %d (%v)", i, mode, mode, got, got) 182 } 183 cM := C.cap_get_mode() 184 if mode != cap.Mode(cM) { 185 log.Fatalf("[%d] C and Go disagree on mode: %d vs %d", cM, mode) 186 } 187 } 188 189 // The current process is now without any access to privilege. 190} 191 192func main() { 193 // Use the C libcap to obtain a non-trivial capability in text form (from init). 194 cC := C.cap_get_pid(1) 195 if cC == nil { 196 log.Fatal("basic c caps from init function failure") 197 } 198 defer C.cap_free(unsafe.Pointer(cC)) 199 var tCLen C.ssize_t 200 tC := C.cap_to_text(cC, &tCLen) 201 if tC == nil { 202 log.Fatal("basic c init caps -> text failure") 203 } 204 defer C.cap_free(unsafe.Pointer(tC)) 205 206 importT := C.GoString(tC) 207 if got, want := len(importT), int(tCLen); got != want { 208 log.Fatalf("C string import failed: got=%d [%q] want=%d", got, importT, want) 209 } 210 211 // Validate that it can be decoded in Go. 212 cGo, err := cap.FromText(importT) 213 if err != nil { 214 log.Fatalf("go parsing of c text import failed: %v", err) 215 } 216 217 // Validate that it matches the one directly loaded in Go. 218 c, err := cap.GetPID(1) 219 if err != nil { 220 log.Fatalf("...failed to read init's capabilities:", err) 221 } 222 tGo := c.String() 223 if got, want := tGo, cGo.String(); got != want { 224 log.Fatalf("go text rep does not match c: got=%q, want=%q", got, want) 225 } 226 227 // Export it in text form again from Go. 228 tForC := C.CString(tGo) 229 defer C.free(unsafe.Pointer(tForC)) 230 231 // Validate it can be encoded in C. 232 cC2 := C.cap_from_text(tForC) 233 if cC2 == nil { 234 log.Fatal("go text rep not parsable by c") 235 } 236 defer C.cap_free(unsafe.Pointer(cC2)) 237 238 // Validate that it can be exported in binary form in C 239 const enoughForAnyone = 1000 240 eC := make([]byte, enoughForAnyone) 241 eCLen := C.cap_copy_ext(unsafe.Pointer(&eC[0]), cC2, C.ssize_t(len(eC))) 242 if eCLen < 5 { 243 log.Fatalf("c export yielded bad length: %d", eCLen) 244 } 245 246 // Validate that it can be imported from binary in Go 247 iGo, err := cap.Import(eC[:eCLen]) 248 if err != nil { 249 log.Fatalf("go import of c binary failed: %v", err) 250 } 251 if got, want := iGo.String(), importT; got != want { 252 log.Fatalf("go import of c binary miscompare: got=%q want=%q", got, want) 253 } 254 255 // Validate that it can be exported in binary in Go 256 iE, err := iGo.Export() 257 if err != nil { 258 log.Fatalf("go failed to export binary: %v", err) 259 } 260 261 // Validate that it can be imported in binary in C 262 iC := C.cap_copy_int_check(unsafe.Pointer(&iE[0]), C.ssize_t(len(iE))) 263 if iC == nil { 264 log.Fatal("c failed to import go binary") 265 } 266 defer C.cap_free(unsafe.Pointer(iC)) 267 fC := C.cap_to_text(iC, &tCLen) 268 if fC == nil { 269 log.Fatal("basic c init caps -> text failure") 270 } 271 defer C.cap_free(unsafe.Pointer(fC)) 272 if got, want := C.GoString(fC), importT; got != want { 273 log.Fatalf("c import from go yielded bad caps: got=%q want=%q", got, want) 274 } 275 276 // Validate that everyone agrees what all is: 277 want := "=ep" 278 all, err := cap.FromText("all=ep") 279 if err != nil { 280 log.Fatalf("unable to parse all=ep: %v", err) 281 } 282 if got := all.String(); got != want { 283 log.Fatalf("all decode failed in Go: got=%q, want=%q", got, want) 284 } 285 286 // Validate some random values stringify consistently between 287 // libcap.cap_to_text() and (*cap.Set).String(). 288 mb := cap.MaxBits() 289 sample := cap.NewSet() 290 for c := cap.Value(0); c < 7*mb; c += 3 { 291 n := int(c) 292 raise, f := c%mb, cap.Flag(c/mb)%3 293 sample.SetFlag(f, true, raise) 294 if v, err := cap.FromText(sample.String()); err != nil { 295 log.Fatalf("[%d] cap to text for %q not reversible: %v", n, sample, err) 296 } else if cf, err := v.Compare(sample); err != nil { 297 log.Fatalf("[%d] FromText generated bad capability from %q: %v", n, sample, err) 298 } else if cf != 0 { 299 log.Fatalf("[%d] text import got=%q want=%q", n, v, sample) 300 } 301 e, err := sample.Export() 302 if err != nil { 303 log.Fatalf("[%d] failed to export %q: %v", n, sample, err) 304 } 305 i, err := cap.Import(e) 306 if err != nil { 307 log.Fatalf("[%d] failed to import %q: %v", n, sample, err) 308 } 309 if cf, err := i.Compare(sample); err != nil { 310 log.Fatalf("[%d] failed to compare %q vs original:%q", n, i, sample) 311 } else if cf != 0 { 312 log.Fatalf("[%d] import got=%q want=%q", n, i, sample) 313 } 314 // Confirm that importing this portable binary 315 // representation in libcap and converting to text, 316 // generates the same text as Go generates. This was 317 // broken prior to v0.2.41. 318 cCap := C.cap_copy_int(unsafe.Pointer(&e[0])) 319 if cCap == nil { 320 log.Fatalf("[%d] C import failed for %q export", n, sample) 321 } 322 var tCLen C.ssize_t 323 tC := C.cap_to_text(cCap, &tCLen) 324 if tC == nil { 325 log.Fatalf("[%d] basic c init caps -> text failure", n) 326 } 327 C.cap_free(unsafe.Pointer(cCap)) 328 importT := C.GoString(tC) 329 C.cap_free(unsafe.Pointer(tC)) 330 if got, want := len(importT), int(tCLen); got != want { 331 log.Fatalf("[%d] C text generated wrong length: Go=%d, C=%d", n, got, want) 332 } 333 if got, want := importT, sample.String(); got != want { 334 log.Fatalf("[%d] C and Go text rep disparity: C=%q Go=%q", n, got, want) 335 } 336 } 337 338 iab, err := cap.IABFromText("cap_chown,!cap_setuid,^cap_setgid") 339 if err != nil { 340 log.Fatalf("failed to initialize iab from text: %v", err) 341 } 342 cIAB := C.cap_iab_init() 343 defer C.cap_free(unsafe.Pointer(cIAB)) 344 for c := cap.MaxBits(); c > 0; { 345 c-- 346 if en, err := iab.GetVector(cap.Inh, c); err != nil { 347 log.Fatalf("failed to read iab.i[%v]", c) 348 } else if en { 349 if C.cap_iab_set_vector(cIAB, C.CAP_IAB_INH, C.cap_value_t(int(c)), C.CAP_SET) != 0 { 350 log.Fatalf("failed to set C's AIB.I %v: %v", c) 351 } 352 } 353 if en, err := iab.GetVector(cap.Amb, c); err != nil { 354 log.Fatalf("failed to read iab.a[%v]", c) 355 } else if en { 356 if C.cap_iab_set_vector(cIAB, C.CAP_IAB_AMB, C.cap_value_t(int(c)), C.CAP_SET) != 0 { 357 log.Fatalf("failed to set C's AIB.A %v: %v", c) 358 } 359 } 360 if en, err := iab.GetVector(cap.Bound, c); err != nil { 361 log.Fatalf("failed to read iab.b[%v]", c) 362 } else if en { 363 if C.cap_iab_set_vector(cIAB, C.CAP_IAB_BOUND, C.cap_value_t(int(c)), C.CAP_SET) != 0 { 364 log.Fatalf("failed to set C's AIB.B %v: %v", c) 365 } 366 } 367 } 368 iabC := C.cap_iab_to_text(cIAB) 369 if iabC == nil { 370 log.Fatalf("failed to get text from C for %q", iab) 371 } 372 defer C.cap_free(unsafe.Pointer(iabC)) 373 if got, want := C.GoString(iabC), iab.String(); got != want { 374 log.Fatalf("IAB for Go and C differ: got=%q, want=%q", got, want) 375 } 376 377 // Next, we attempt to manipulate some file capabilities on 378 // the running program. These are optional, based on whether 379 // the current program is capable enough and do not involve 380 // any cgo calls to libcap. 381 tryFileCaps() 382 383 // Nothing left to do but exit after this one. 384 tryProcCaps() 385 log.Printf("compare-cap success!") 386} 387