blob: 189ca61c472e7d7f1603f41f5b21efb5e2ca76bf [file] [log] [blame]
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:
// <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
}