You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
11 KiB
387 lines
11 KiB
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 (
|
|
vfsCapRevisionMask = uint32(0xff000000)
|
|
vfsCapFlagsMask = ^vfsCapRevisionMask
|
|
vfsCapFlagsEffective = uint32(1)
|
|
|
|
vfsCapRevision1 = uint32(0x01000000)
|
|
vfsCapRevision2 = uint32(0x02000000)
|
|
vfsCapRevision3 = uint32(0x03000000)
|
|
)
|
|
|
|
// Data types stored in little-endian order.
|
|
|
|
type vfsCaps1 struct {
|
|
MagicEtc uint32
|
|
Data [1]struct {
|
|
Permitted, Inheritable uint32
|
|
}
|
|
}
|
|
|
|
type vfsCaps2 struct {
|
|
MagicEtc uint32
|
|
Data [2]struct {
|
|
Permitted, Inheritable uint32
|
|
}
|
|
}
|
|
|
|
type vfsCaps3 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 indicates that the kernel preferred magic number for
|
|
// capability Set values is not supported by this package. This
|
|
// generally implies you are using an exceptionally old
|
|
// "../libcap/cap" package. An upgrade is needed, or failing that see
|
|
// https://sites.google.com/site/fullycapable/ for how to file a bug.
|
|
var ErrBadMagic = errors.New("unsupported magic")
|
|
|
|
// ErrBadPath indicates a failed attempt to set a file capability on
|
|
// an irregular (non-executable) file.
|
|
var 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 vfsCaps1
|
|
var raw2 vfsCaps2
|
|
var raw3 vfsCaps3
|
|
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 & vfsCapRevisionMask {
|
|
case vfsCapRevision1:
|
|
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&vfsCapFlagsMask == vfsCapFlagsEffective {
|
|
c.flat[0][Effective] = data.Inheritable | data.Permitted
|
|
}
|
|
case vfsCapRevision2:
|
|
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&vfsCapFlagsMask == vfsCapFlagsEffective {
|
|
c.flat[i][Effective] = data.Inheritable | data.Permitted
|
|
}
|
|
}
|
|
case vfsCapRevision3:
|
|
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&vfsCapFlagsMask == vfsCapFlagsEffective {
|
|
c.flat[i][Effective] = data.Inheritable | data.Permitted
|
|
}
|
|
}
|
|
c.nsRoot = int(raw3.RootID)
|
|
default:
|
|
return nil, ErrBadMagic
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
//go:uintptrescapes
|
|
|
|
// GetFd returns the file capabilities of an open (*os.File).Fd().
|
|
func GetFd(file *os.File) (*Set, error) {
|
|
var raw3 vfsCaps3
|
|
d := make([]byte, binary.Size(raw3))
|
|
sz, _, oErr := multisc.r6(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)
|
|
}
|
|
|
|
//go:uintptrescapes
|
|
|
|
// 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 vfsCaps3
|
|
d := make([]byte, binary.Size(raw3))
|
|
sz, _, oErr := multisc.r6(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
|
|
}
|
|
|
|
// SetNSOwner adds an explicit namespace owner UID to the capability
|
|
// Set. This is only honored when generating file capabilities, and is
|
|
// generally for use by a setup process when installing binaries that
|
|
// use file capabilities to become capable inside a namespace to be
|
|
// administered by that UID. If capability aware code within that
|
|
// namespace writes file capabilities without explicitly setting such
|
|
// a UID, the kernel will fix-up the capabilities to be specific to
|
|
// that owner. In this way, the kernel prevents filesystem
|
|
// capabilities from leaking out of that restricted namespace.
|
|
func (c *Set) SetNSOwner(uid int) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.nsRoot = uid
|
|
}
|
|
|
|
// 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 = vfsCapRevision1
|
|
case 2:
|
|
if c.nsRoot == 0 {
|
|
magic = vfsCapRevision2
|
|
break
|
|
}
|
|
magic = vfsCapRevision3
|
|
}
|
|
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 |= vfsCapFlagsEffective
|
|
}
|
|
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
|
|
}
|
|
|
|
//go:uintptrescapes
|
|
|
|
// SetFd attempts to set the file capabilities of an open
|
|
// (*os.File).Fd(). This function can also be used to delete a file's
|
|
// capabilities, by calling with c = nil.
|
|
//
|
|
// Note, Linux does not store the full Effective Value Flag in the
|
|
// metadata for the file. Only a single Effective bit is stored in
|
|
// this metadata. This single bit is non-zero if the Effective vector
|
|
// has any overlapping bits with the Permitted or Inheritable vector
|
|
// of c. This may appear suboptimal, but the reasoning behind it is
|
|
// sound. Namely, the purpose of the Effective bit it to support
|
|
// capabability unaware binaries that will only work if they magically
|
|
// launch with the needed bits already raised (this bit is sometimes
|
|
// referred to simply as the 'legacy' bit). Without *full* support for
|
|
// capability manipulation, as it is provided in this "../libcap/cap"
|
|
// package, this was the only way for Go programs to make use of
|
|
// file capabilities.
|
|
//
|
|
// The preferred way a binary will actually manipulate its
|
|
// file-acquired capabilities is to carefully and deliberately use
|
|
// this package (or libcap, assisted by libpsx, for threaded C/C++
|
|
// family code).
|
|
func (c *Set) SetFd(file *os.File) error {
|
|
if c == nil {
|
|
if _, _, err := multisc.r6(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 := multisc.r6(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
|
|
}
|
|
|
|
//go:uintptrescapes
|
|
|
|
// SetFile attempts to set the file capabilities of the specfied
|
|
// filename. This function can also be used to delete a file's
|
|
// capabilities, by calling with c = nil.
|
|
//
|
|
// Note, see the comment for SetFd() for some non-obvious behavior of
|
|
// Linux for the Effective Value vector on the modified file.
|
|
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 := multisc.r6(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 := multisc.r6(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
|
|
}
|