• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 err := saved.SetProc(); err != nil {
120		log.Fatalf("failed to lower effective capability: %v", err)
121	}
122	// End of critical section.
123
124	if got, err := cap.GetFd(f); err != nil {
125		log.Fatalf("failed to fread caps: %v", err)
126	} else if is, was := got.String(), want.String(); is != was {
127		log.Fatalf("fread file caps do not match desired: got=%q want=%q", is, was)
128	}
129}
130
131// tryProcCaps performs a set of convenience functions and compares
132// the results with those seen by libcap. At the end of this function,
133// the running process has no privileges at all. So exiting the
134// program is the only option.
135func tryProcCaps() {
136	c := cap.GetProc()
137	if v, err := c.GetFlag(cap.Permitted, cap.SETPCAP); err != nil {
138		log.Fatalf("failed to read permitted setpcap: %v", err)
139	} else if !v {
140		log.Printf("skipping proc cap tests - insufficient privilege")
141		return
142	}
143	if err := cap.SetUID(99); err != nil {
144		log.Fatalf("failed to set uid=99: %v", err)
145	}
146	if u := syscall.Getuid(); u != 99 {
147		log.Fatal("uid=99 did not take: got=%d", u)
148	}
149	if err := cap.SetGroups(98, 100, 101); err != nil {
150		log.Fatalf("failed to set groups=98 [100, 101]: %v", err)
151	}
152	if g := syscall.Getgid(); g != 98 {
153		log.Fatalf("gid=98 did not take: got=%d", g)
154	}
155	if gs, err := syscall.Getgroups(); err != nil {
156		log.Fatalf("error getting groups: %v", err)
157	} else if len(gs) != 2 || gs[0] != 100 || gs[1] != 101 {
158		log.Fatalf("wrong of groups: got=%v want=[100 l01]", gs)
159	}
160
161	if mode := cap.GetMode(); mode != cap.ModeUncertain {
162		log.Fatalf("initial mode should be 0 (UNCERTAIN), got: %d (%v)", mode, mode)
163	}
164
165	// To distinguish PURE1E and PURE1E_INIT we need an inheritable capability set.
166	working := cap.GetProc()
167	if err := working.SetFlag(cap.Inheritable, true, cap.SETPCAP); err != nil {
168		log.Fatalf("unable to raise inheritable bit: %v", err)
169	}
170	if err := working.SetProc(); err != nil {
171		log.Fatalf("failed to add inheritable bit: %v", err)
172	}
173
174	for i, mode := range []cap.Mode{cap.ModePure1E, cap.ModePure1EInit, cap.ModeNoPriv} {
175		if err := mode.Set(); err != nil {
176			log.Fatalf("[%d] in mode=%v and failed to set mode to %d (%v): %v", i, cap.GetMode(), mode, mode, err)
177		}
178		if got := cap.GetMode(); got != mode {
179			log.Fatalf("[%d] unable to recognise mode %d (%v), got: %d (%v)", i, mode, mode, got, got)
180		}
181		cM := C.cap_get_mode()
182		if mode != cap.Mode(cM) {
183			log.Fatalf("[%d] C and Go disagree on mode: %d vs %d", cM, mode)
184		}
185	}
186
187	// The current process is now without any access to privelege.
188}
189
190func main() {
191	// Use the C libcap to obtain a non-trivial capability in text form (from init).
192	cC := C.cap_get_pid(1)
193	if cC == nil {
194		log.Fatal("basic c caps from init function failure")
195	}
196	defer C.cap_free(unsafe.Pointer(cC))
197	var tCLen C.ssize_t
198	tC := C.cap_to_text(cC, &tCLen)
199	if tC == nil {
200		log.Fatal("basic c init caps -> text failure")
201	}
202	defer C.cap_free(unsafe.Pointer(tC))
203
204	importT := C.GoString(tC)
205	if got, want := len(importT), int(tCLen); got != want {
206		log.Fatalf("C string import failed: got=%d [%q] want=%d", got, importT, want)
207	}
208
209	// Validate that it can be decoded in Go.
210	cGo, err := cap.FromText(importT)
211	if err != nil {
212		log.Fatalf("go parsing of c text import failed: %v", err)
213	}
214
215	// Validate that it matches the one directly loaded in Go.
216	c, err := cap.GetPID(1)
217	if err != nil {
218		log.Fatalf("...failed to read init's capabilities:", err)
219	}
220	tGo := c.String()
221	if got, want := tGo, cGo.String(); got != want {
222		log.Fatalf("go text rep does not match c: got=%q, want=%q", got, want)
223	}
224
225	// Export it in text form again from Go.
226	tForC := C.CString(tGo)
227	defer C.free(unsafe.Pointer(tForC))
228
229	// Validate it can be encoded in C.
230	cC2 := C.cap_from_text(tForC)
231	if cC2 == nil {
232		log.Fatal("go text rep not parsable by c")
233	}
234	defer C.cap_free(unsafe.Pointer(cC2))
235
236	// Validate that it can be exported in binary form in C
237	const enoughForAnyone = 1000
238	eC := make([]byte, enoughForAnyone)
239	eCLen := C.cap_copy_ext(unsafe.Pointer(&eC[0]), cC2, C.ssize_t(len(eC)))
240	if eCLen < 5 {
241		log.Fatalf("c export yielded bad length: %d", eCLen)
242	}
243
244	// Validate that it can be imported from binary in Go
245	iGo, err := cap.Import(eC[:eCLen])
246	if err != nil {
247		log.Fatalf("go import of c binary failed: %v", err)
248	}
249	if got, want := iGo.String(), importT; got != want {
250		log.Fatalf("go import of c binary miscompare: got=%q want=%q", got, want)
251	}
252
253	// Validate that it can be exported in binary in Go
254	iE, err := iGo.Export()
255	if err != nil {
256		log.Fatalf("go failed to export binary: %v", err)
257	}
258
259	// Validate that it can be imported in binary in C
260	iC := C.cap_copy_int(unsafe.Pointer(&iE[0]))
261	if iC == nil {
262		log.Fatal("c failed to import go binary")
263	}
264	defer C.cap_free(unsafe.Pointer(iC))
265	fC := C.cap_to_text(cC, &tCLen)
266	if fC == nil {
267		log.Fatal("basic c init caps -> text failure")
268	}
269	defer C.cap_free(unsafe.Pointer(fC))
270	if got, want := C.GoString(fC), importT; got != want {
271		log.Fatalf("c import from go yielded bad caps: got=%q want=%q", got, want)
272	}
273
274	// Validate that everyone agrees what all is:
275	want := "=ep"
276	all, err := cap.FromText("all=ep")
277	if err != nil {
278		log.Fatalf("unable to parse all=ep: %v", err)
279	}
280	if got := all.String(); got != want {
281		log.Fatalf("all decode failed in Go: got=%q, want=%q", got, want)
282	}
283
284	// Validate some random values stringify consistently between
285	// libcap.cap_to_text() and (*cap.Set).String().
286	mb := cap.MaxBits()
287	sample := cap.NewSet()
288	for c := cap.Value(0); c < 7*mb; c += 3 {
289		n := int(c)
290		raise, f := c%mb, cap.Flag(c/mb)%3
291		sample.SetFlag(f, true, raise)
292		if v, err := cap.FromText(sample.String()); err != nil {
293			log.Fatalf("[%d] cap to text for %q not reversible: %v", n, sample, err)
294		} else if cf, err := v.Compare(sample); err != nil {
295			log.Fatalf("[%d] FromText generated bad capability from %q: %v", n, sample, err)
296		} else if cf != 0 {
297			log.Fatalf("[%d] text import got=%q want=%q", n, v, sample)
298		}
299		e, err := sample.Export()
300		if err != nil {
301			log.Fatalf("[%d] failed to export %q: %v", n, sample, err)
302		}
303		i, err := cap.Import(e)
304		if err != nil {
305			log.Fatalf("[%d] failed to import %q: %v", n, sample, err)
306		}
307		if cf, err := i.Compare(sample); err != nil {
308			log.Fatalf("[%d] failed to compare %q vs original:%q", n, i, sample)
309		} else if cf != 0 {
310			log.Fatalf("[%d] import got=%q want=%q", n, i, sample)
311		}
312		// Confirm that importing this portable binary
313		// representation in libcap and converting to text,
314		// generates the same text as Go generates. This was
315		// broken prior to v0.2.41.
316		cCap := C.cap_copy_int(unsafe.Pointer(&e[0]))
317		if cCap == nil {
318			log.Fatalf("[%d] C import failed for %q export", n, sample)
319		}
320		var tCLen C.ssize_t
321		tC := C.cap_to_text(cCap, &tCLen)
322		if tC == nil {
323			log.Fatalf("[%d] basic c init caps -> text failure", n)
324		}
325		C.cap_free(unsafe.Pointer(cCap))
326		importT := C.GoString(tC)
327		C.cap_free(unsafe.Pointer(tC))
328		if got, want := len(importT), int(tCLen); got != want {
329			log.Fatalf("[%d] C text generated wrong length: Go=%d, C=%d", n, got, want)
330		}
331		if got, want := importT, sample.String(); got != want {
332			log.Fatalf("[%d] C and Go text rep disparity: C=%q Go=%q", n, got, want)
333		}
334	}
335
336	iab, err := cap.IABFromText("cap_chown,!cap_setuid,^cap_setgid")
337	if err != nil {
338		log.Fatalf("failed to initialize iab from text: %v", err)
339	}
340	cIAB := C.cap_iab_init()
341	defer C.cap_free(unsafe.Pointer(cIAB))
342	for c := cap.MaxBits(); c > 0; {
343		c--
344		if en, err := iab.GetVector(cap.Inh, c); err != nil {
345			log.Fatalf("failed to read iab.i[%v]", c)
346		} else if en {
347			if C.cap_iab_set_vector(cIAB, C.CAP_IAB_INH, C.cap_value_t(int(c)), C.CAP_SET) != 0 {
348				log.Fatalf("failed to set C's AIB.I %v: %v", c)
349			}
350		}
351		if en, err := iab.GetVector(cap.Amb, c); err != nil {
352			log.Fatalf("failed to read iab.a[%v]", c)
353		} else if en {
354			if C.cap_iab_set_vector(cIAB, C.CAP_IAB_AMB, C.cap_value_t(int(c)), C.CAP_SET) != 0 {
355				log.Fatalf("failed to set C's AIB.A %v: %v", c)
356			}
357		}
358		if en, err := iab.GetVector(cap.Bound, c); err != nil {
359			log.Fatalf("failed to read iab.b[%v]", c)
360		} else if en {
361			if C.cap_iab_set_vector(cIAB, C.CAP_IAB_BOUND, C.cap_value_t(int(c)), C.CAP_SET) != 0 {
362				log.Fatalf("failed to set C's AIB.B %v: %v", c)
363			}
364		}
365	}
366	iabC := C.cap_iab_to_text(cIAB)
367	if iabC == nil {
368		log.Fatalf("failed to get text from C for %q", iab)
369	}
370	defer C.cap_free(unsafe.Pointer(iabC))
371	if got, want := C.GoString(iabC), iab.String(); got != want {
372		log.Fatalf("IAB for Go and C differ: got=%q, want=%q", got, want)
373	}
374
375	// Next, we attempt to manipulate some file capabilities on
376	// the running program.  These are optional, based on whether
377	// the current program is capable enough and do not involve
378	// any cgo calls to libcap.
379	tryFileCaps()
380
381	// Nothing left to do but exit after this one.
382	tryProcCaps()
383	log.Printf("compare-cap success!")
384}
385