Lessen the situations where cap.SETPCAP is required for IAB setting.

Discussion and explanation of what is up here is in:

   https://bugzilla.kernel.org/show_bug.cgi?id=219169

This gets the Go cap package to parity with the recent changes to
libcap. This change will be live in cap/v1.2.71.

Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
This commit is contained in:
Andrew G. Morgan 2024-10-19 16:37:56 -07:00
parent 676971a20a
commit 9e4b652f48
5 changed files with 101 additions and 33 deletions

View File

@ -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)

View File

@ -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

1
go/.gitignore vendored
View File

@ -17,6 +17,7 @@ setid
gowns
captree
captrace
iaber
ok
vendor
go.sum

View File

@ -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

37
go/iaber.go Normal file
View File

@ -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)
}