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 := callRKernel6(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 := callRKernel6(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 := callRKernel6(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 := callRKernel6(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 := callRKernel6(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 := callRKernel6(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 }