summaryrefslogtreecommitdiff
path: root/cap/file.go
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/file.go
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/file.go')
-rw-r--r--cap/file.go348
1 files changed, 348 insertions, 0 deletions
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
+}