Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
398 changes: 398 additions & 0 deletions internal/cap/License

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions internal/cap/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
This package is modified from the libcap project to allow
cross-platform marshalling to/from the kernel vfs format to write squashfs
xattr values and for parsing the text format used with the setcap command

Package cap is the libcap API for Linux Capabilities written in
Go. The official release announcement site for libcap is:

https://sites.google.com/site/fullycapable/

Like libcap, the cap package is distributed with a "you choose"
License. Specifically: BSD 3-clause, or GPL2. See the License file.

Andrew G. Morgan <morgan@kernel.org>
124 changes: 124 additions & 0 deletions internal/cap/cap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Package cap
// Copyright (c) 2019-21 Andrew G. Morgan <morgan@kernel.org>
//
// The cap and psx packages are licensed with a (you choose) BSD
// 3-clause or GPL2. See LICENSE file for details.
// [the Fully Capable site]: https://sites.google.com/site/fullycapable/
package cap

import (
"errors"
"sync"
)

// Value is the type of a single capability (or permission) bit.
type Value uint

// Flag is the type of one of the three Value dimensions held in a
// Set. It is also used in the (*IAB).Fill() method for changing the
// Bounding and Ambient Vectors.
type Flag uint

// Effective, Permitted, Inheritable are the three Flags of Values
// held in a Set.
const (
Effective Flag = iota
Permitted
Inheritable
)

// String identifies a Flag value by its conventional "e", "p" or "i"
// string abbreviation.
func (f Flag) String() string {
switch f {
case Effective:
return "e"
case Permitted:
return "p"
case Inheritable:
return "i"
default:
return "<Error>"
}
}

// 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. It holds individually addressable capability Value's
// for the three capability Flag's. See GetFlag() and SetFlag() for
// how to adjust them individually, and Clear() and ClearFlag() for
// how to do bulk operations.
//
// For admin tasks associated with managing namespace specific file
// capabilities, Set can also support a namespace-root-UID value which
// defaults to zero. See GetNSOwner() and SetNSOwner().
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 (
// startUp 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 Flag 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
}

// defines from uapi/linux/prctl.h
const (
prCapBSetRead = 23
prCapBSetDrop = 24
)

// 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")

// good confirms that c looks valid.
func (c *Set) good() error {
if c == nil || len(c.flat) == 0 {
return ErrBadSet
}
return nil
}
47 changes: 47 additions & 0 deletions internal/cap/cinit_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//go:build linux

package cap

import (
"sort"
"syscall"
"unsafe"
)

// cInit performs the lazy identification of the capability vintage of
// the running system.
func cInit() {
h := &header{
magic: kv3,
}
_, _, _ = syscall.RawSyscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(h)), uintptr(0), 0)

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
}
}
func GetBound(val Value) (bool, error) {
r, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, prCapBSetRead, uintptr(val), 0)
if err != 0 {
return false, err
}

return int(r) > 0, nil
}
12 changes: 12 additions & 0 deletions internal/cap/cinit_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build !linux

package cap

// cInit performs the lazy identification of the capability vintage of
// the running system.
func cInit() {
if maxValues == 0 {
// Fall back to using the largest value defined at build time.
maxValues = NamedCount
}
}
174 changes: 174 additions & 0 deletions internal/cap/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package cap

import (
"bytes"
"encoding/binary"
"errors"
"io"
"syscall"
)

// 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 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
// [the Fully Capable site] for the way to report or review a bug.
//
// [the Fully Capable site]: https://sites.google.com/site/fullycapable/
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")

// ErrOutOfRange indicates an erroneous value for MinExtFlagSize.
var ErrOutOfRange = errors.New("flag length invalid for export")

// DigestFileCap unpacks a file capability and returns it in a *Set
// form.
func DigestFileCap(d []byte) (*Set, error) {
var (
err error
raw1 vfsCaps1
raw2 vfsCaps2
raw3 vfsCaps3
)
sz := len(d)
if sz < binary.Size(raw1) || sz > binary.Size(raw3) {
return nil, ErrBadSize
}
b := bytes.NewReader(d)
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
}

// 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) {
c.mu.RLock()
defer c.mu.RUnlock()

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, uint32(c.nsRoot))
}
return b.Bytes(), nil
}
Loading