diff --git a/cap/cap.go b/cap/cap.go index df32436..0bff389 100644 --- a/cap/cap.go +++ b/cap/cap.go @@ -7,7 +7,7 @@ // from code. You can read more about how Capabilities are intended to // work here: // -// https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/33528.pdf +// https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/33528.pdf // // This package supports native Go bindings for all the features // described in that paper as well as supporting subsequent changes to @@ -15,23 +15,23 @@ // // Some simple things you can do with this package are: // -// // Read and display the capabilities of the running process -// c := cap.GetProc() -// iab := cap.IABGetProc() -// log.Printf("this process has these caps: %q [%v]", c, iab) +// // Read and display the capabilities of the running process +// c := cap.GetProc() +// iab := cap.IABGetProc() +// log.Printf("this process has these caps: %q [%v]", c, iab) // -// // Drop any privilege a process might have (including for root, -// // but note root 'owns' a lot of system files so a cap-limited -// // root can still do considerable damage to a running system). -// old := cap.GetProc() -// empty := cap.NewSet() -// if err := empty.SetProc(); err != nil { -// log.Fatalf("failed to drop privilege: %q -> %q: %v", old, empty, err) -// } -// now := cap.GetProc() -// if cf, _ := now.Cf(empty); cf != 0 { -// log.Fatalf("failed to fully drop privilege: have=%q, wanted=%q", now, empty) -// } +// // Drop any privilege a process might have (including for root, +// // but note root 'owns' a lot of system files so a cap-limited +// // root can still do considerable damage to a running system). +// old := cap.GetProc() +// empty := cap.NewSet() +// if err := empty.SetProc(); err != nil { +// log.Fatalf("failed to drop privilege: %q -> %q: %v", old, empty, err) +// } +// now := cap.GetProc() +// if cf, _ := now.Cf(empty); cf != 0 { +// log.Fatalf("failed to fully drop privilege: have=%q, wanted=%q", now, empty) +// } // // The "cap" package operates with POSIX semantics for security // state. That is all OS threads are kept in sync at all times. The @@ -195,6 +195,7 @@ type syscaller struct { // caprcall provides a pointer etc wrapper for the system calls // associated with getcap. +// //go:uintptrescapes func (sc *syscaller) caprcall(call uintptr, h *header, d []data) error { x := uintptr(0) @@ -210,6 +211,7 @@ func (sc *syscaller) caprcall(call uintptr, h *header, d []data) error { // capwcall provides a pointer etc wrapper for the system calls // associated with setcap. +// //go:uintptrescapes func (sc *syscaller) capwcall(call uintptr, h *header, d []data) error { x := uintptr(0) @@ -467,9 +469,10 @@ func (sc *syscaller) setAmbient(enable bool, val ...Value) error { // permission is available to perform this task. The settings are // performed in order and the function returns immediately an error is // detected. Use GetAmbient() to unravel where things went -// wrong. Note, the cap package manages an abstraction IAB that -// captures all three inheritable vectors in a single type. Consider -// using that. +// wrong. +// +// Note, the cap package manages an abstraction IAB that captures all +// three inheritable vectors in a single type. Consider using that. func SetAmbient(enable bool, val ...Value) error { state, sc := scwStateSC() defer scwSetState(launchBlocked, state, -1) diff --git a/cap/iab.go b/cap/iab.go index da189be..663a339 100644 --- a/cap/iab.go +++ b/cap/iab.go @@ -109,11 +109,11 @@ func (iab *IAB) Dup() (*IAB, error) { // // Example, replace this: // -// iab := IABInit() +// iab := IABInit() // // with this: // -// iab := NewIAB() +// iab := NewIAB() func IABInit() *IAB { return NewIAB() } @@ -212,25 +212,44 @@ func (iab *IAB) String() string { // The iab is known to be locked by the caller. func (sc *syscaller) iabSetProc(iab *IAB) (err error) { temp := GetProc() - var raising uint32 + raising := false + bounder := false for i := 0; i < words; i++ { newI := iab.i[i] oldIP := temp.flat[i][Inheritable] | temp.flat[i][Permitted] - raising |= (newI & ^oldIP) | iab.a[i] | iab.nb[i] + raising = raising || (newI & ^oldIP != 0) + if iab.nb[i] != 0 { + bounder = true + } temp.flat[i][Inheritable] = newI } + if bounder { + bounder = false + for c := Value(maxValues); c > 0; { + c-- + offset, mask := omask(c) + if iab.nb[offset]&mask == 0 { + continue + } + if b, _ := GetBound(c); b { + bounder = true + raising = true + break + } + } + } working, err2 := temp.Dup() if err2 != nil { err = err2 return } - if raising != 0 { + if raising { if err = working.SetFlag(Effective, true, SETPCAP); err != nil { return } - if err = sc.setProc(working); err != nil { - return - } + } + if err = sc.setProc(working); err != nil { + return } defer func() { if err2 := sc.setProc(temp); err == nil { @@ -246,7 +265,7 @@ func (sc *syscaller) iabSetProc(iab *IAB) (err error) { if iab.a[offset]&mask != 0 { err = sc.setAmbient(true, c) } - if err == nil && iab.nb[offset]&mask != 0 { + if bounder && err == nil && iab.nb[offset]&mask != 0 { err = sc.dropBound(c) } if err != nil { @@ -260,7 +279,9 @@ func (sc *syscaller) iabSetProc(iab *IAB) (err error) { // capability vectors of the current process using the content, // iab. The Bounding vector strongly affects the potential for setting // other bits, so this function carefully performs the combined -// operation in the most flexible manner. +// operation in the most flexible manner. If the desired IAB value +// will change the Bounding value, cap.SETPCAP must be a Permitted +// value. func (iab *IAB) SetProc() error { if err := iab.good(); err != nil { return err diff --git a/go/.gitignore b/go/.gitignore index 96fe4c0..9ddd3bb 100644 --- a/go/.gitignore +++ b/go/.gitignore @@ -17,6 +17,7 @@ setid gowns captree captrace +iaber ok vendor go.sum diff --git a/go/Makefile b/go/Makefile index 98cf0c0..2b3fe67 100644 --- a/go/Makefile +++ b/go/Makefile @@ -125,6 +125,9 @@ endif mismatch: mismatch.go PSXGOPACKAGE CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $< +iaber: iaber.go CAPGOPACKAGE PSXGOPACKAGE + CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $< + ifeq ($(CGO_REQUIRED),0) mismatch-cgo: mismatch.go CAPGOPACKAGE CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $< @@ -150,7 +153,7 @@ endif # Note, the user namespace doesn't require sudo, but I wanted to avoid # requiring that the hosting kernel supports user namespaces for the # regular test case. -sudotest: test ../progs/tcapsh-static b210613 b215283 +sudotest: test ../progs/tcapsh-static b210613 b215283 iaber ../progs/tcapsh-static --has-b=cap_sys_admin || exit 0 && ./gowns --ns -- -c "echo gowns runs with user namespace" ./try-launching ifeq ($(CGO_REQUIRED),0) @@ -166,7 +169,10 @@ ifeq ($(CGO_REQUIRED),0) $(MAKE) b215283-cgo $(SUDO) ./b215283-cgo endif - + @echo should pass: + $(SUDO) ./iaber '!^cap_setgid,^cap_setuid,^cap_setpcap' '!^cap_setuid,^cap_setgid' + @echo should fail: + $(SUDO) ./iaber '!^cap_setgid,^cap_setuid' '!^cap_setuid,^cap_setgid' || exit 0 ; exit 1 # As of libcap-2.55 We stopped installing the cap and psx packages as # part of the install. Most distribution's packagers skip the Go @@ -185,7 +191,7 @@ install: all clean: rm -f *.o *.so *~ mknames ok good-names.go - rm -f web setid gowns captree captrace + rm -f web setid gowns captree captrace iaber rm -f compare-cap try-launching try-launching-cgo rm -f $(topdir)/cap/*~ $(topdir)/psx/*~ rm -f b210613 b215283 b215283-cgo psx-signals psx-signals-cgo diff --git a/go/iaber.go b/go/iaber.go new file mode 100644 index 0000000..579d6b8 --- /dev/null +++ b/go/iaber.go @@ -0,0 +1,37 @@ +// Program iaber attempts to set an iab value and then exec itself +// with its remaining arguments. This is used to validate some +// behavior of IAB setting. +package main + +import ( + "log" + "os" + "syscall" + + "kernel.org/pub/linux/libs/security/libcap/cap" +) + +func main() { + if len(os.Args) <= 1 { + log.Print("success") + return + } + if syscall.Getuid() != 1 { + if err := cap.SetUID(1); err != nil { + log.Fatalf("failed to setuid(1): %v", err) + } + } + caps, iab := cap.GetProc(), cap.IABGetProc() + log.Printf("current: %q [%q]", caps, iab) + iab, err := cap.IABFromText(os.Args[1]) + if err != nil { + log.Fatalf("failed to parse %q: %v", os.Args[1], err) + } + if err := iab.SetProc(); err != nil { + log.Fatalf("unable to set IAB=%q: %v", iab, err) + } + caps, iab = cap.GetProc(), cap.IABGetProc() + log.Printf("pre-exec: %q [%q]", caps, iab) + err = syscall.Exec(os.Args[0], append([]string{os.Args[0]}, os.Args[2:]...), nil) + log.Fatalf("exec %q failed: %v", os.Args[2:], err) +}