summaryrefslogtreecommitdiff
path: root/cap
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2019-05-19 14:57:20 -0700
committerAndrew G. Morgan <morgan@kernel.org>2019-05-19 14:57:20 -0700
commit0615d996379dceedefcd65a114f93fefd81c208f (patch)
tree590edde235e563c88b62b913a449cc43884d1be2 /cap
parentac1ef3125f50594289a2d9a4de3b5a22d2882ea4 (diff)
downloadlibcap2-0615d996379dceedefcd65a114f93fefd81c208f.tar.gz
A Go (golang) implementation of libcap: import "libcap/cap".
The API for this "libcap/cap" package is very similar to libcap. I've included a substantial interoperability test that validate libcap(c) and libcap/cap(go) have import/export text and binary format compatibility. My motivation for implementing a standalone Go package was for a cross-compilation issue I ran into (Go is much more friendly for cross-compilation by default, unless you need to use cgo). Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
Diffstat (limited to 'cap')
-rw-r--r--cap/.gitignore1
-rw-r--r--cap/cap.go379
-rw-r--r--cap/cap_test.go97
-rw-r--r--cap/file.go348
-rw-r--r--cap/text.go182
5 files changed, 1007 insertions, 0 deletions
diff --git a/cap/.gitignore b/cap/.gitignore
new file mode 100644
index 0000000..e7946b9
--- /dev/null
+++ b/cap/.gitignore
@@ -0,0 +1 @@
+names.go
diff --git a/cap/cap.go b/cap/cap.go
new file mode 100644
index 0000000..b5a72fb
--- /dev/null
+++ b/cap/cap.go
@@ -0,0 +1,379 @@
+// Package cap is the Linux capabilities user space API (libcap)
+// bindings in native Go. The key advantage is no requirement for cgo
+// linking etc. which can enable striaghtforward cross-compilation of
+// different architectures just the Go compiler.
+package cap
+
+import (
+ "errors"
+ "sort"
+ "sync"
+ "syscall"
+ "unsafe"
+)
+
+// Value is the type of a single capability (or permission) bit.
+type Value uint
+
+// Flag is the type of one of the three Value vectors held in a Set.
+type Flag uint
+
+// Effective, Permitted, Inheritable are the three vectors of Values
+// held in a Set.
+const (
+ Effective Flag = iota
+ Permitted
+ Inheritable
+)
+
+// data holds a 32-bit slice of the compressed bitmaps of capability
+// sets as understood by the kernel.
+type data [Inheritable + 1]uint32
+
+// Set is an opaque capabilities container for a set of system
+// capbilities.
+type Set struct {
+ // mu protects all other members of a Set.
+ mu sync.RWMutex
+
+ // flat holds Flag Value bitmaps for all capabilities
+ // associated with this Set.
+ flat []data
+
+ // Linux specific
+ nsRoot int
+}
+
+// Various known kernel magic values.
+const (
+ kv1 = 0x19980330 // First iteration of process capabilities (32 bits).
+ kv2 = 0x20071026 // First iteration of process and file capabilities (64 bits) - deprecated.
+ kv3 = 0x20080522 // Most recently supported process and file capabilities (64 bits).
+)
+
+var (
+ // callKernel variables overridable for testing purposes.
+ callKernel = syscall.Syscall
+ callKernel6 = syscall.Syscall6
+
+ // OS environment provides these.
+
+ // starUp protects setting of the following values: magic,
+ // words, maxValues.
+ startUp sync.Once
+
+ // magic holds the preferred magic number for the kernel ABI.
+ magic uint32
+
+ // words holds the number of uint32's associated with each
+ // capability vector for this session.
+ words int
+
+ // maxValues holds the number of bit values that are named by
+ // the running kernel. This is generally expected to match
+ // ValueCount which is autogenerated at packaging time.
+ maxValues uint
+)
+
+type header struct {
+ magic uint32
+ pid int32
+}
+
+// capcall provides a pointer etc wrapper for the system calls
+// associated with getcap and setcap.
+func capcall(call uintptr, h *header, d []data) error {
+ x := uintptr(0)
+ if d != nil {
+ x = uintptr(unsafe.Pointer(&d[0]))
+ }
+ _, _, err := callKernel(call, uintptr(unsafe.Pointer(h)), x, 0)
+ if err != 0 {
+ return err
+ }
+ return nil
+}
+
+// prctlcall provides a wrapper for the prctl systemcalls. There is a
+// limited number of arguments needed and the caller should use 0 for
+// those not needed.
+func prctlcall(prVal, v1, v2 uintptr) (int, error) {
+ r, _, err := callKernel6(syscall.SYS_PRCTL, prVal, v1, v2, 0, 0, 0)
+ if err != 0 {
+ return int(r), err
+ }
+ return int(r), nil
+}
+
+// cInit perfoms the lazy identification of the capability vintage of
+// the running system.
+func cInit() {
+ h := &header{
+ magic: kv3,
+ }
+ capcall(syscall.SYS_CAPGET, h, nil)
+ magic = h.magic
+ switch magic {
+ case kv1:
+ words = 1
+ case kv2, kv3:
+ words = 2
+ default:
+ // Fall back to a known good version.
+ magic = kv3
+ words = 2
+ }
+ // Use the bounding set to evaluate which capabilities exist.
+ maxValues = uint(sort.Search(32*words, func(n int) bool {
+ _, err := GetBound(Value(n))
+ return err != nil
+ }))
+ if maxValues == 0 {
+ // Fall back to using the largest value defined at build time.
+ maxValues = NamedCount
+ }
+}
+
+// NewSet returns an empty capability set.
+func NewSet() *Set {
+ startUp.Do(cInit)
+ return &Set{
+ flat: make([]data, words),
+ }
+}
+
+// ErrBadSet indicates a nil pointer was used for a *Set, or the
+// request of the Set is invalid in some way.
+var ErrBadSet = errors.New("bad capability set")
+
+// Dup returns a copy of the specified capability set.
+func (c *Set) Dup() (*Set, error) {
+ if c == nil {
+ return nil, ErrBadSet
+ }
+ n := NewSet()
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ copy(n.flat, c.flat)
+ n.nsRoot = c.nsRoot
+ return n, nil
+}
+
+// ErrBadValue indicates a bad capability value was specified.
+var ErrBadValue = errors.New("bad capability value")
+
+// bitOf convertes from a Value into the offset and mask for a
+// specific Value bit in the compressed (kernel ABI) representation of
+// a capability vector. If the requested bit is unsupported, an error
+// is returned.
+func bitOf(vec Flag, val Value) (uint, uint32, error) {
+ if vec > Inheritable || val > Value(words*32) {
+ return 0, 0, ErrBadValue
+ }
+ u := uint(val)
+ return u / 32, uint32(1) << (u % 32), nil
+}
+
+// GetFlag determines if the requested bit is enabled in the Flag
+// vector of the capability Set.
+func (c *Set) GetFlag(vec Flag, val Value) (bool, error) {
+ if c == nil {
+ // Checked this first, because otherwise we are sure
+ // cInit has been called.
+ return false, ErrBadSet
+ }
+ offset, mask, err := bitOf(vec, val)
+ if err != nil {
+ return false, err
+ }
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ return c.flat[offset][vec]&mask != 0, nil
+}
+
+// SetFlag sets the requested bits to the indicated enable state. This
+// function does not perform any security checks, so values can be set
+// out-of-order. Only when the Set is used to SetProc() etc., will the
+// bits be checked for validity and permission by the kernel. If the
+// function returns an error, the Set will not be modified.
+func (c *Set) SetFlag(vec Flag, enable bool, val ...Value) error {
+ if c == nil {
+ // Checked this first, because otherwise we are sure
+ // cInit has been called.
+ return ErrBadSet
+ }
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ // Make a backup.
+ replace := make([]uint32, words)
+ for i := range replace {
+ replace[i] = c.flat[i][vec]
+ }
+ var err error
+ for _, v := range val {
+ offset, mask, err2 := bitOf(vec, v)
+ if err2 != nil {
+ err = err2
+ break
+ }
+ if enable {
+ c.flat[offset][vec] |= mask
+ } else {
+ c.flat[offset][vec] &= ^mask
+ }
+ }
+ if err == nil {
+ return nil
+ }
+ // Clean up.
+ for i, bits := range replace {
+ c.flat[i][vec] = bits
+ }
+ return err
+}
+
+// Clear fully clears a capability set.
+func (c *Set) Clear() error {
+ if c == nil {
+ return ErrBadSet
+ }
+ // startUp.Do(cInit) is not called here because c cannot be
+ // initialized except via this package and doing that will
+ // perform that call at least once (sic).
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.flat = make([]data, words)
+ c.nsRoot = 0
+ return nil
+}
+
+// forceFlag sets all capability values of a flag vector to enable.
+func (c *Set) forceFlag(vec Flag, enable bool) error {
+ if c == nil || vec > Inheritable {
+ return ErrBadSet
+ }
+ m := uint32(0)
+ if enable {
+ m = ^m
+ }
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ for i := range c.flat {
+ c.flat[i][vec] = m
+ }
+ return nil
+}
+
+// ClearFlag clears a specific vector of Values associated with the
+// specified Flag.
+func (c *Set) ClearFlag(vec Flag) error {
+ return c.forceFlag(vec, false)
+}
+
+// GetPID returns the capability set associated with the target process
+// id; pid=0 is an alias for current.
+func GetPID(pid int) (*Set, error) {
+ v := NewSet()
+ if err := capcall(syscall.SYS_CAPGET, &header{magic: magic, pid: int32(pid)}, v.flat); err != nil {
+ return nil, err
+ }
+ return v, nil
+}
+
+// GetProc returns the capability Set of the current process. If the
+// kernel is unable to determine the Set associated with the current
+// process, the function panic()s.
+func GetProc() *Set {
+ c, err := GetPID(0)
+ if err != nil {
+ panic(err)
+ }
+ return c
+}
+
+// SetProc attempts to write the capability Set to the current
+// process. The kernel will perform permission checks and an error
+// will be returned if the attempt fails.
+func (c *Set) SetProc() error {
+ if c == nil {
+ return ErrBadSet
+ }
+ return capcall(syscall.SYS_CAPSET, &header{magic: magic}, c.flat)
+}
+
+// defines from uapi/linux/prctl.h
+const (
+ PR_CAPBSET_READ = 23
+ PR_CAPBSET_DROP = 24
+)
+
+// GetBound determines if a specific capability is currently part of
+// the local bounding set. On systems where the bounding set Value is
+// not present, this function returns an error.
+func GetBound(val Value) (bool, error) {
+ v, err := prctlcall(PR_CAPBSET_READ, uintptr(val), 0)
+ if err != nil {
+ return false, err
+ }
+ return v > 0, nil
+}
+
+// DropBound attempts to suppress bounding set Values. The kernel will
+// never allow a bounding set Value bit to be raised once successfully
+// dropped. However, dropping requires the current process is
+// sufficiently capable (usually via cap.SETPCAP being raised in the
+// Effective flag vector). Note, the drops are performed in order and
+// if one bounding value cannot be dropped, the function returns
+// immediately with an error which may leave the system in an
+// ill-defined state.
+func DropBound(val ...Value) error {
+ for _, v := range val {
+ if _, err := prctlcall(PR_CAPBSET_DROP, uintptr(v), 0); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// defines from uapi/linux/prctl.h
+const (
+ PR_CAP_AMBIENT = 47
+
+ PR_CAP_AMBIENT_IS_SET = 1
+ PR_CAP_AMBIENT_RAISE = 2
+ PR_CAP_AMBIENT_LOWER = 3
+ PR_CAP_AMBIENT_CLEAR_ALL = 4
+)
+
+// GetAmbient determines if a specific capability is currently part of
+// the local ambient set. On systems where the ambient set Value is
+// not present, this function returns an error.
+func GetAmbient(val Value) (bool, error) {
+ r, err := prctlcall(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, uintptr(val))
+ return r > 0, err
+}
+
+// SetAmbient attempts to set a specific Value bit to the enable
+// state. This function will return an error if insufficient
+// permission is available to perform this task. The settings are
+// performed in order and the function returns immediately an error is
+// detected.
+func SetAmbient(enable bool, val ...Value) error {
+ dir := uintptr(PR_CAP_AMBIENT_LOWER)
+ if enable {
+ dir = PR_CAP_AMBIENT_RAISE
+ }
+ for _, v := range val {
+ _, err := prctlcall(PR_CAP_AMBIENT, dir, uintptr(v))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// ResetAmbient attempts to fully clear the Ambient set.
+func ResetAmbient() error {
+ _, err := prctlcall(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0)
+ return err
+}
diff --git a/cap/cap_test.go b/cap/cap_test.go
new file mode 100644
index 0000000..a0151cc
--- /dev/null
+++ b/cap/cap_test.go
@@ -0,0 +1,97 @@
+package cap
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestString(t *testing.T) {
+ a := CHOWN
+ if got, want := a.String(), "cap_chown"; got != want {
+ t.Fatalf("pretty basic failure: got=%q, want=%q", got, want)
+ }
+}
+
+func TestText(t *testing.T) {
+ vs := []struct {
+ from, to string
+ err error
+ }{
+ {"", "", ErrBadText},
+ {"=", "=", nil},
+ {"= cap_chown+iep cap_chown-i", "= cap_chown+ep", nil},
+ {"= cap_setfcap,cap_chown+iep cap_chown-i", "= cap_setfcap+epi cap_chown+ep", nil},
+ }
+ for i, v := range vs {
+ c, err := FromText(v.from)
+ if err != v.err {
+ t.Errorf("[%d] parsing %q failed: got=%v, want=%v", i, v.from, err, v.err)
+ continue
+ }
+ if err != nil {
+ continue
+ }
+ to := c.String()
+ if to != v.to {
+ t.Errorf("[%d] failed to stringify cap: %q -> got=%q, want=%q", i, v.from, to, v.to)
+ }
+ if d, err := FromText(to); err != nil {
+ t.Errorf("[%d] failed to reparse %q: %v", i, to, err)
+ } else if got := d.String(); got != to {
+ t.Errorf("[%d] failed to stringify %q getting %q", i, to, got)
+ }
+ }
+}
+
+func same(a, b *Set) error {
+ if (a == nil) != (b == nil) {
+ return fmt.Errorf("nil-ness miscompare: %q vs %v", a, b)
+ }
+ if a == nil {
+ return nil
+ }
+ if a.nsRoot != b.nsRoot {
+ return fmt.Errorf("capabilities differ in nsRoot: a=%d b=%d", a.nsRoot, b.nsRoot)
+ }
+ for i, f := range a.flat {
+ g := b.flat[i]
+ for s := Effective; s <= Inheritable; s++ {
+ if got, want := f[s], g[s]; got != want {
+ return fmt.Errorf("capabilities differ: a[%d].flat[%v]=0x%08x b[%d].flat[%v]=0x%08x", i, s, got, i, s, want)
+ }
+ }
+ }
+ return nil
+}
+
+func TestImportExport(t *testing.T) {
+ // Sanity check empty import/export.
+ c := NewSet()
+ if ex, err := c.Export(); err != nil {
+ t.Fatalf("failed to export empty set: %v", err)
+ } else if len(ex) != 5 {
+ t.Fatalf("wrong length: got=%d want=%d", len(ex), 5)
+ } else if im, err := Import(ex); err != nil {
+ t.Fatalf("failed to import empty set: %v", err)
+ } else if got, want := im.String(), c.String(); got != want {
+ t.Fatalf("import != export: got=%q want=%q", got, want)
+ }
+ // Now keep flipping bits on and off and validate that all
+ // forms of import/export work.
+ for i := uint(0); i < 7000; i += 13 {
+ s := Flag(i % 3)
+ v := Value(i % (maxValues + 3))
+ c.SetFlag(s, i&17 < 8, v)
+ if ex, err := c.Export(); err != nil {
+ t.Fatalf("[%d] failed to export empty set: %v", i, err)
+ } else if im, err := Import(ex); err != nil {
+ t.Fatalf("[%d] failed to import empty set: %v", i, err)
+ } else if got, want := im.String(), c.String(); got != want {
+ t.Fatalf("[%d] import != export: got=%q want=%q [%02x]", i, got, want, ex)
+ } else if parsed, err := FromText(got); err != nil {
+ t.Fatalf("[%d] failed to parse %q: %v", i, got, err)
+ } else if err := same(c, parsed); err != nil {
+ t.Fatalf("[%d] miscompare: %v", i, err)
+ }
+ }
+}
diff --git a/cap/file.go b/cap/file.go
new file mode 100644
index 0000000..57144af
--- /dev/null
+++ b/cap/file.go
@@ -0,0 +1,348 @@
+package cap
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "io"
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+// uapi/linux/xattr.h defined.
+var (
+ xattrNameCaps, _ = syscall.BytePtrFromString("security.capability")
+)
+
+// uapi/linux/capability.h defined.
+const (
+ VFS_CAP_REVISION_MASK = uint32(0xff000000)
+ VFS_CAP_FLAGS_MASK = ^VFS_CAP_REVISION_MASK
+ VFS_CAP_FLAGS_EFFECTIVE = uint32(1)
+
+ VFS_CAP_REVISION_1 = uint32(0x01000000)
+ VFS_CAP_REVISION_2 = uint32(0x02000000)
+ VFS_CAP_REVISION_3 = uint32(0x03000000)
+)
+
+// Data types stored in little-endian order.
+
+type vfs_caps_1 struct {
+ MagicEtc uint32
+ Data [1]struct {
+ Permitted, Inheritable uint32
+ }
+}
+
+type vfs_caps_2 struct {
+ MagicEtc uint32
+ Data [2]struct {
+ Permitted, Inheritable uint32
+ }
+}
+
+type vfs_caps_3 struct {
+ MagicEtc uint32
+ Data [2]struct {
+ Permitted, Inheritable uint32
+ }
+ RootID uint32
+}
+
+// ErrBadSize indicates the the loaded file capability has
+// an invalid number of bytes in it.
+var (
+ ErrBadSize = errors.New("filecap bad size")
+ ErrBadMagic = errors.New("unsupported magic")
+ ErrBadPath = errors.New("file is not a regular executable")
+)
+
+// digestFileCap unpacks a file capability and returns it in a *Set
+// form.
+func digestFileCap(d []byte, sz int, err error) (*Set, error) {
+ if err != nil {
+ return nil, err
+ }
+ var raw1 vfs_caps_1
+ var raw2 vfs_caps_2
+ var raw3 vfs_caps_3
+ if sz < binary.Size(raw1) || sz > binary.Size(raw3) {
+ return nil, ErrBadSize
+ }
+ b := bytes.NewReader(d[:sz])
+ var magicEtc uint32
+ if err = binary.Read(b, binary.LittleEndian, &magicEtc); err != nil {
+ return nil, err
+ }
+
+ c := NewSet()
+ b.Seek(0, io.SeekStart)
+ switch magicEtc & VFS_CAP_REVISION_MASK {
+ case VFS_CAP_REVISION_1:
+ if err = binary.Read(b, binary.LittleEndian, &raw1); err != nil {
+ return nil, err
+ }
+ data := raw1.Data[0]
+ c.flat[0][Permitted] = data.Permitted
+ c.flat[0][Inheritable] = data.Inheritable
+ if raw1.MagicEtc&VFS_CAP_FLAGS_MASK == VFS_CAP_FLAGS_EFFECTIVE {
+ c.flat[0][Effective] = data.Inheritable | data.Permitted
+ }
+ case VFS_CAP_REVISION_2:
+ if err = binary.Read(b, binary.LittleEndian, &raw2); err != nil {
+ return nil, err
+ }
+ for i, data := range raw2.Data {
+ c.flat[i][Permitted] = data.Permitted
+ c.flat[i][Inheritable] = data.Inheritable
+ if raw2.MagicEtc&VFS_CAP_FLAGS_MASK == VFS_CAP_FLAGS_EFFECTIVE {
+ c.flat[i][Effective] = data.Inheritable | data.Permitted
+ }
+ }
+ case VFS_CAP_REVISION_3:
+ if err = binary.Read(b, binary.LittleEndian, &raw3); err != nil {
+ return nil, err
+ }
+ for i, data := range raw3.Data {
+ c.flat[i][Permitted] = data.Permitted
+ c.flat[i][Inheritable] = data.Inheritable
+ if raw3.MagicEtc&VFS_CAP_FLAGS_MASK == VFS_CAP_FLAGS_EFFECTIVE {
+ c.flat[i][Effective] = data.Inheritable | data.Permitted
+ }
+ }
+ c.nsRoot = int(raw3.RootID)
+ default:
+ return nil, ErrBadMagic
+ }
+ return c, nil
+}
+
+// GetFd returns the file capabilities of an open (*os.File).Fd().
+func GetFd(file *os.File) (*Set, error) {
+ var raw3 vfs_caps_3
+ d := make([]byte, binary.Size(raw3))
+ sz, _, oErr := callKernel6(syscall.SYS_FGETXATTR, uintptr(file.Fd()), uintptr(unsafe.Pointer(xattrNameCaps)), uintptr(unsafe.Pointer(&d[0])), uintptr(len(d)), 0, 0)
+ var err error
+ if oErr != 0 {
+ err = oErr
+ }
+ return digestFileCap(d, int(sz), err)
+}
+
+// GetFile returns the file capabilities of a named file.
+func GetFile(path string) (*Set, error) {
+ p, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return nil, err
+ }
+ var raw3 vfs_caps_3
+ d := make([]byte, binary.Size(raw3))
+ sz, _, oErr := callKernel6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(xattrNameCaps)), uintptr(unsafe.Pointer(&d[0])), uintptr(len(d)), 0, 0)
+ if oErr != 0 {
+ err = oErr
+ }
+ return digestFileCap(d, int(sz), err)
+}
+
+// GetNSOwner returns the namespace owner UID of the capability Set.
+func (c *Set) GetNSOwner() (int, error) {
+ if magic < kv3 {
+ return 0, ErrBadMagic
+ }
+ return c.nsRoot, nil
+}
+
+// packFileCap transforms a system capability into a VFS form. Because
+// of the way Linux stores capabilities in the file extended
+// attributes, the process is a little lossy with respect to effective
+// bits.
+func (c *Set) packFileCap() ([]byte, error) {
+ var magic uint32
+ switch words {
+ case 1:
+ if c.nsRoot != 0 {
+ return nil, ErrBadSet // nsRoot not supported for single DWORD caps.
+ }
+ magic = VFS_CAP_REVISION_1
+ case 2:
+ if c.nsRoot == 0 {
+ magic = VFS_CAP_REVISION_2
+ break
+ }
+ magic = VFS_CAP_REVISION_3
+ }
+ if magic == 0 {
+ return nil, ErrBadSize
+ }
+ eff := uint32(0)
+ for _, f := range c.flat {
+ eff |= (f[Permitted] | f[Inheritable]) & f[Effective]
+ }
+ if eff != 0 {
+ magic |= VFS_CAP_FLAGS_EFFECTIVE
+ }
+ b := new(bytes.Buffer)
+ binary.Write(b, binary.LittleEndian, magic)
+ for _, f := range c.flat {
+ binary.Write(b, binary.LittleEndian, f[Permitted])
+ binary.Write(b, binary.LittleEndian, f[Inheritable])
+ }
+ if c.nsRoot != 0 {
+ binary.Write(b, binary.LittleEndian, c.nsRoot)
+ }
+ return b.Bytes(), nil
+}
+
+// SetFd attempts to set the file capabilities of an open
+// (*os.File).Fd(). Note, Linux does not store the full Effective
+// Value vector in the metadata for the file. Only a single Effective
+// bit is stored. This single bit is non-zero if the Permitted vector
+// have any overlapping bits with the Effective or Inheritable vector
+// of c. This may seem suboptimal, but the reasoning behind it is
+// sound. Namely, the purpose of the Effective bit it to support
+// capabability unaware binaries that can work if they magically
+// launch with only the needed bits raised (this bit is sometimes
+// referred to simply as the 'legacy' bit). However, the preferred way
+// a binary will actually manipulate its file-acquired capabilities is
+// carefully and deliberately using this package, or libcap for C
+// family code. This function can also be used to delete a file's
+// capabilities, by calling with c = nil.
+func (c *Set) SetFd(file *os.File) error {
+ if c == nil {
+ if _, _, err := callKernel6(syscall.SYS_FREMOVEXATTR, uintptr(file.Fd()), uintptr(unsafe.Pointer(xattrNameCaps)), 0, 0, 0, 0); err != 0 {
+ return err
+ }
+ return nil
+ }
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ d, err := c.packFileCap()
+ if err != nil {
+ return err
+ }
+ if _, _, err := callKernel6(syscall.SYS_FSETXATTR, uintptr(file.Fd()), uintptr(unsafe.Pointer(xattrNameCaps)), uintptr(unsafe.Pointer(&d[0])), uintptr(len(d)), 0, 0); err != 0 {
+ return err
+ }
+ return nil
+}
+
+// SetFile attempts to set the file capabilities of the specfied
+// filename. See the comment for SetFd() for some non-obvious behavior
+// of Linux for the Effective Value vector on the modified file. This
+// function can also be used to delete a file's capabilities, by
+// calling with c = nil.
+func (c *Set) SetFile(path string) error {
+ fi, err := os.Stat(path)
+ if err != nil {
+ return err
+ }
+ mode := fi.Mode()
+ if mode&os.ModeType != 0 {
+ return ErrBadPath
+ }
+ if mode&os.FileMode(0111) == 0 {
+ return ErrBadPath
+ }
+ p, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return err
+ }
+ if c == nil {
+ if _, _, err := callKernel6(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(xattrNameCaps)), 0, 0, 0, 0); err != 0 {
+ return err
+ }
+ return nil
+ }
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ d, err := c.packFileCap()
+ if err != nil {
+ return err
+ }
+ if _, _, err := callKernel6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(xattrNameCaps)), uintptr(unsafe.Pointer(&d[0])), uintptr(len(d)), 0, 0); err != 0 {
+ return err
+ }
+ return nil
+}
+
+// ExtMagic is the 32-bit (little endian) magic for an external
+// capability set. It can be used to transmit capabilities in binary
+// format in a Linux portable way. The format is:
+// <ExtMagic><byte:length><length-bytes*3-of-cap-data>.
+const ExtMagic = uint32(0x5101c290)
+
+// Import imports a Set from a byte array where it has been stored in
+// a portable (lossless) way.
+func Import(d []byte) (*Set, error) {
+ b := bytes.NewBuffer(d)
+ var m uint32
+ if err := binary.Read(b, binary.LittleEndian, &m); err != nil {
+ return nil, ErrBadSize
+ } else if m != ExtMagic {
+ return nil, ErrBadMagic
+ }
+ var n byte
+ if err := binary.Read(b, binary.LittleEndian, &n); err != nil {
+ return nil, ErrBadSize
+ }
+ c := NewSet()
+ if int(n) > 4*words {
+ return nil, ErrBadSize
+ }
+ f := make([]byte, 3)
+ for i := 0; i < words; i++ {
+ for j := uint(0); n > 0 && j < 4; j++ {
+ n--
+ if x, err := b.Read(f); err != nil || x != 3 {
+ return nil, ErrBadSize
+ }
+ sh := 8 * j
+ c.flat[i][Effective] |= uint32(f[0]) << sh
+ c.flat[i][Permitted] |= uint32(f[1]) << sh
+ c.flat[i][Inheritable] |= uint32(f[2]) << sh
+ }
+ }
+ return c, nil
+}
+
+// Export exports a Set into a lossless byte array format where it is
+// stored in a portable way. Note, any namespace owner in the Set
+// content is not exported by this function.
+func (c *Set) Export() ([]byte, error) {
+ if c == nil {
+ return nil, ErrBadSet
+ }
+ b := new(bytes.Buffer)
+ binary.Write(b, binary.LittleEndian, ExtMagic)
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ var n = byte(0)
+ for i, f := range c.flat {
+ if u := f[Effective] | f[Permitted] | f[Inheritable]; u != 0 {
+ n = 4 * byte(i)
+ for ; u != 0; u >>= 8 {
+ n++
+ }
+ }
+ }
+ b.Write([]byte{n})
+ for _, f := range c.flat {
+ if n == 0 {
+ break
+ }
+ eff, per, inh := f[Effective], f[Permitted], f[Inheritable]
+ for i := 0; n > 0 && i < 4; i++ {
+ n--
+ b.Write([]byte{
+ byte(eff & 0xff),
+ byte(per & 0xff),
+ byte(inh & 0xff),
+ })
+ eff >>= 8
+ per >>= 8
+ inh >>= 8
+ }
+ }
+ return b.Bytes(), nil
+}
diff --git a/cap/text.go b/cap/text.go
new file mode 100644
index 0000000..0af6290
--- /dev/null
+++ b/cap/text.go
@@ -0,0 +1,182 @@
+package cap
+
+import (
+ "bufio"
+ "errors"
+ "strconv"
+ "strings"
+)
+
+// String converts a capability Value into its canonical text
+// representation.
+func (v Value) String() string {
+ name, ok := names[v]
+ if ok {
+ return name
+ }
+ // Un-named capabilities are referred to numerically (in decimal).
+ return strconv.Itoa(int(v))
+}
+
+// FromName converts a named capability Value to its binary
+// representation.
+func FromName(name string) (Value, error) {
+ startUp.Do(cInit)
+ v, ok := bits[name]
+ if ok {
+ if v >= Value(words*32) {
+ return 0, ErrBadValue
+ }
+ return v, nil
+ }
+ i, err := strconv.Atoi(name)
+ if err != nil {
+ return 0, err
+ }
+ if i >= 0 && i < int(words*32) {
+ return Value(i), nil
+ }
+ return 0, ErrBadValue
+}
+
+const (
+ eBin uint = (1 << Effective)
+ pBin = (1 << Permitted)
+ iBin = (1 << Inheritable)
+)
+
+var combos = []string{"", "e", "p", "ep", "i", "ei", "pi", "epi"}
+
+// histo generates a histogram of flag state combinations.
+func (c *Set) histo(m uint, bins []int, patterns []uint, from, limit Value) uint {
+ for v := from; v < limit; v++ {
+ b := uint(v & 31)
+ u, mask, err := bitOf(0, v)
+ if err != nil {
+ break
+ }
+ x := uint((c.flat[u][Effective]&mask)>>b) * eBin
+ x |= uint((c.flat[u][Permitted]&mask)>>b) * pBin
+ x |= uint((c.flat[u][Inheritable]&mask)>>b) * iBin
+ bins[x]++
+ patterns[uint(v)] = x
+ if bins[m] <= bins[x] {
+ m = x
+ }
+ }
+ return m
+}
+
+// String converts a full capability Set into it canonical readable
+// string representation (which may contain spaces).
+func (c *Set) String() string {
+ if c == nil {
+ return "<invalid>"
+ }
+ bins := make([]int, 8)
+ patterns := make([]uint, 32*words)
+
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ // Note, in order to have a *Set pointer, startUp.Do(cInit)
+ // must have been called which sets maxValues.
+ m := c.histo(0, bins, patterns, Value(maxValues), 32*Value(words))
+ // Background state is the most popular of the unnamed bits,
+ // this has the effect of tending to not list numerical values
+ // for unnamed capabilities in the generated text.
+ vs := []string{"=" + combos[m]}
+
+ // Extend the histogram with the remaining Value occurrences.
+ c.histo(m, bins, patterns, 0, Value(maxValues))
+ for i := uint(8); i > 0; {
+ i--
+ if i == m || bins[i] == 0 {
+ continue
+ }
+ var list []string
+ for j, p := range patterns {
+ if p != i {
+ continue
+ }
+ list = append(list, Value(j).String())
+ }
+ if cf := i & ^m; cf != 0 {
+ vs = append(vs, strings.Join(list, ",")+"+"+combos[cf])
+ }
+ if cf := m & ^i; cf != 0 {
+ vs = append(vs, strings.Join(list, ",")+"-"+combos[cf])
+ }
+ }
+ return strings.Join(vs, " ")
+}
+
+// ErrBadText is returned if the text for a capability set cannot be parsed.
+var ErrBadText = errors.New("bad text")
+
+// FromText converts the canonical text representation for a Set into
+// a freshly allocated Set.
+func FromText(text string) (*Set, error) {
+ c := NewSet()
+ scanner := bufio.NewScanner(strings.NewReader(text))
+ scanner.Split(bufio.ScanWords)
+ chunks := 0
+ for scanner.Scan() {
+ chunks++
+ // Parsing for xxx[-+=][eip]+
+ t := scanner.Text()
+ i := strings.IndexAny(t, "=+-")
+ if i < 0 {
+ return nil, ErrBadText
+ }
+ var vs []Value
+ sep := t[i]
+ if vals := t[:i]; vals != "all" && vals != "" {
+ for _, name := range strings.Split(vals, ",") {
+ v, err := FromName(name)
+ if err != nil {
+ return nil, ErrBadText
+ }
+ vs = append(vs, v)
+ }
+ } else if sep != '=' && vals == "" {
+ return nil, ErrBadText // Only "=" supports ""=="all".
+ }
+ sets := t[i+1:]
+ var fE, fP, fI bool
+ for j := 0; j < len(sets); j++ {
+ switch sets[j] {
+ case 'e':
+ fE = true
+ case 'p':
+ fP = true
+ case 'i':
+ fI = true
+ default:
+ return nil, ErrBadText
+ }
+ }
+ if sep == '=' {
+ keep := len(vs) == 0 // '=' means default to off except setting individual values.
+ c.forceFlag(Effective, fE && keep)
+ c.forceFlag(Permitted, fP && keep)
+ c.forceFlag(Inheritable, fI && keep)
+ if keep {
+ continue
+ }
+ }
+ if fE {
+ c.SetFlag(Effective, sep != '-', vs...)
+ }
+ if fP {
+ c.SetFlag(Permitted, sep != '-', vs...)
+ }
+ if fI {
+ c.SetFlag(Inheritable, sep == '+', vs...)
+ }
+ }
+ if chunks == 0 {
+ return nil, ErrBadText
+ }
+ return c, nil
+}