From 0615d996379dceedefcd65a114f93fefd81c208f Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 19 May 2019 14:57:20 -0700 Subject: 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 --- cap/.gitignore | 1 + cap/cap.go | 379 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cap/cap_test.go | 97 +++++++++++++++ cap/file.go | 348 +++++++++++++++++++++++++++++++++++++++++++++++++++ cap/text.go | 182 +++++++++++++++++++++++++++ 5 files changed, 1007 insertions(+) create mode 100644 cap/.gitignore create mode 100644 cap/cap.go create mode 100644 cap/cap_test.go create mode 100644 cap/file.go create mode 100644 cap/text.go (limited to 'cap') 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: +// . +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 "" + } + 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 +} -- cgit v1.2.1