Merge pull request #5278 from opencontainers/dependabot/go_modules/github.com/opencontainers/selinux-1.14.1

build(deps): bump github.com/opencontainers/selinux from 1.13.1 to 1.14.1
This commit is contained in:
Kir Kolyshkin
2026-05-13 11:10:22 -07:00
committed by GitHub
9 changed files with 449 additions and 273 deletions
+55 -47
View File
@@ -22,60 +22,68 @@ var ErrIncompatibleLabel = errors.New("bad SELinux option: z and Z can not be us
// InitLabels returns the process label and file labels to be used within
// the container. A list of options can be passed into this function to alter
// the labels. The labels returned will include a random MCS String, that is
// guaranteed to be unique.
// the labels.
//
// Unless the "level" option is provided (to set a custom level), the labels
// returned will include a random MCS string guaranteed to be unique in the
// scope of the process using this package. If the "level" option is provided,
// the custom level set is reserved but not checked to be unique.
//
// If the disabled flag is passed in, the process label will not be set, but the mount label will be set
// to the container_file label with the maximum category. This label is not usable by any confined label.
func InitLabels(options []string) (plabel string, mlabel string, retErr error) {
if !selinux.GetEnabled() {
return "", "", nil
}
processLabel, mountLabel := selinux.ContainerLabels()
if processLabel != "" {
defer func() {
if retErr != nil {
selinux.ReleaseLabel(mountLabel)
}
}()
pcon, err := selinux.NewContext(processLabel)
if err != nil {
return "", "", err
}
mcsLevel := pcon["level"]
mcon, err := selinux.NewContext(mountLabel)
if err != nil {
return "", "", err
}
for _, opt := range options {
if opt == "disable" {
selinux.ReleaseLabel(mountLabel)
return "", selinux.PrivContainerMountLabel(), nil
}
if i := strings.Index(opt, ":"); i == -1 {
return "", "", fmt.Errorf("bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt)
}
con := strings.SplitN(opt, ":", 2)
if !validOptions[con[0]] {
return "", "", fmt.Errorf("bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0])
}
if con[0] == "filetype" {
mcon["type"] = con[1]
continue
}
pcon[con[0]] = con[1]
if con[0] == "level" || con[0] == "user" {
mcon[con[0]] = con[1]
}
}
if pcon.Get() != processLabel {
if pcon["level"] != mcsLevel {
selinux.ReleaseLabel(processLabel)
}
processLabel = pcon.Get()
selinux.ReserveLabel(processLabel)
}
mountLabel = mcon.Get()
processLabel, mountLabel := selinux.ContainerLabels() //nolint:staticcheck // ContainerLabels will be moved to an internal package.
if processLabel == "" {
// processLabel is required; if empty, do nothing.
return processLabel, mountLabel, nil
}
defer func() {
if retErr != nil {
selinux.ReleaseLabel(mountLabel)
}
}()
pcon, err := selinux.NewContext(processLabel)
if err != nil {
return "", "", err
}
mcsLevel := pcon["level"]
mcon, err := selinux.NewContext(mountLabel)
if err != nil {
return "", "", err
}
for _, opt := range options {
if opt == "disable" {
selinux.ReleaseLabel(mountLabel)
return "", selinux.PrivContainerMountLabel(), nil
}
k, v, ok := strings.Cut(opt, ":")
if !ok || !validOptions[k] {
return "", "", fmt.Errorf("bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt)
}
if k == "filetype" {
mcon["type"] = v
continue
}
pcon[k] = v
if k == "level" || k == "user" {
mcon[k] = v
}
}
if p := pcon.Get(); p != processLabel {
if pcon["level"] != mcsLevel {
selinux.ReleaseLabel(processLabel)
// Ignore ErrMCSAlreadyExists as label is user-specified and might be
// already reserved (e.g. when containers in a pod use the same label).
if err := selinux.ReserveLabelV2(p); err != nil && !errors.Is(err, selinux.ErrMCSAlreadyExists) {
return "", "", err
}
}
processLabel = p
}
mountLabel = mcon.Get()
return processLabel, mountLabel, nil
}
@@ -1,5 +1,4 @@
//go:build !linux
// +build !linux
package label
+68 -14
View File
@@ -26,11 +26,6 @@ var (
// ErrInvalidLabel is returned when an invalid label is specified.
ErrInvalidLabel = errors.New("invalid Label")
// InvalidLabel is returned when an invalid label is specified.
//
// Deprecated: use [ErrInvalidLabel].
InvalidLabel = ErrInvalidLabel
// ErrIncomparable is returned two levels are not comparable
ErrIncomparable = errors.New("incomparable levels")
// ErrLevelSyntax is returned when a sensitivity or category do not have correct syntax in a level
@@ -45,7 +40,9 @@ var (
// is not the thread group leader.
ErrNotTGLeader = errors.New("calling thread is not the thread group leader")
// CategoryRange allows the upper bound on the category range to be adjusted
// CategoryRange allows the upper bound on the category range to be adjusted.
//
// Deprecated: use [SetCategoryRange] instead.
CategoryRange = DefaultCategoryRange
privContainerMountLabel string
@@ -64,6 +61,16 @@ func GetEnabled() bool {
return getEnabled()
}
// SetCategoryRange allows to adjust the upper bound of the category range.
// It affects subsequent calls to [KVMContainerLabel] and [InitContainerLabel].
func SetCategoryRange(upper uint32) error {
if upper > DefaultCategoryRange {
return errors.New("can't have more than DefaultCategoryRange categories")
}
CategoryRange = upper
return nil
}
// ClassIndex returns the int index for an object class in the loaded policy,
// or -1 and an error
func ClassIndex(class string) (int, error) {
@@ -107,12 +114,12 @@ func SetFSCreateLabel(label string) error {
// FSCreateLabel returns the default label the kernel which the kernel is using
// for file system objects created by this task. "" indicates default.
func FSCreateLabel() (string, error) {
return fsCreateLabel()
return readConThreadSelf("attr/fscreate")
}
// CurrentLabel returns the SELinux label of the current process thread, or an error.
func CurrentLabel() (string, error) {
return currentLabel()
return readConThreadSelf("attr/current")
}
// PidLabel returns the SELinux label of the given pid, or an error.
@@ -123,7 +130,7 @@ func PidLabel(pid int) (string, error) {
// ExecLabel returns the SELinux label that the kernel will use for any programs
// that are executed by the current process thread, or an error.
func ExecLabel() (string, error) {
return execLabel()
return readConThreadSelf("attr/exec")
}
// CanonicalizeContext takes a context string and writes it to the kernel
@@ -180,7 +187,7 @@ func SocketLabel() (string, error) {
// PeerLabel retrieves the label of the client on the other side of a socket
func PeerLabel(fd uintptr) (string, error) {
return peerLabel(fd)
return peerLabel(int(fd)) //#nosec G115 -- ignore "integer overflow conversion uintptr -> int".
}
// SetKeyLabel takes a process label and tells the kernel to assign the
@@ -216,9 +223,26 @@ func ClearLabels() {
clearLabels()
}
// ReserveLabel reserves the MLS/MCS level component of the specified label
// ReserveLabel reserves the MLS/MCS level component of the specified label.
//
// Deprecated: use [ReserveLabelV2] instead.
func ReserveLabel(label string) {
reserveLabel(label)
_ = reserveLabel(label)
}
// ReserveLabelV2 reserves the MLS/MCS level component of the specified label.
// Returns an error if the label can't be reserved.
//
// Callers that are intentionally reusing an existing level/MCS (e.g. multiple
// container in a pod sharing a label) may safely ignore [ErrMCSAlreadyExists]
// error.
func ReserveLabelV2(label string) error {
return reserveLabel(label)
}
// CheckLabel check the MLS/MCS level component of the specified label
func CheckLabel(label string) error {
return checkLabel(label)
}
// MLSEnabled checks if MLS is enabled.
@@ -250,25 +274,47 @@ func ReleaseLabel(label string) {
releaseLabel(label)
}
// ROFileLabel returns the specified SELinux readonly file label
// ROFileLabel returns the specified SELinux readonly file label.
//
// Deprecated: this (apparently) has no users and will be removed from the
// future version of this package. Open a bug report if you use it.
func ROFileLabel() string {
return roFileLabel()
}
// KVMContainerLabels returns the default processLabel and mountLabel to be used
// for kvm containers by the calling process.
//
// Deprecated: use [KVMContainerLabel] instead.
func KVMContainerLabels() (string, string) {
return kvmContainerLabels()
}
// KVMContainerLabel returns the default process label to be used
// for KVM containers by the calling process.
func KVMContainerLabel() (string, error) {
return kvmContainerLabel()
}
// InitContainerLabels returns the default processLabel and file labels to be
// used for containers running an init system like systemd by the calling process.
//
// Deprecated: use [InitContainerLabel] instead.
func InitContainerLabels() (string, string) {
return initContainerLabels()
}
// InitContainerLabel returns the default process label to be used
// for containers running an init system like systemd by the calling process.
func InitContainerLabel() (string, error) {
return initContainerLabel()
}
// ContainerLabels returns an allocated processLabel and fileLabel to be used for
// container labeling by the calling process.
//
// Deprecated: this (apparently) has no users and will be removed from the
// future version of this package. Open a bug report if you use it.
func ContainerLabels() (processLabel string, fileLabel string) {
return containerLabels()
}
@@ -305,11 +351,19 @@ func DisableSecOpt() []string {
return []string{"disable"}
}
// SEUserByName retrieves the SELinux username and security level for a given
// Linux username. The username and security level is based on the
// /etc/selinux/{SELINUXTYPE}/seusers file.
func SEUserByName(username string) (seUser string, level string, err error) {
return getSeUserByName(username)
}
// GetDefaultContextWithLevel gets a single context for the specified SELinux user
// identity that is reachable from the specified scon context. The context is based
// on the per-user /etc/selinux/{SELINUXTYPE}/contexts/users/<username> if it exists,
// and falls back to the global /etc/selinux/{SELINUXTYPE}/contexts/default_contexts
// file.
// file and finally the global /etc/selinux/{SELINUXTYPE}/contexts/failsafe_context
// file if no match can be found anywhere else.
func GetDefaultContextWithLevel(user, level, scon string) (string, error) {
return getDefaultContextWithLevel(user, level, scon)
}
+301 -187
View File
@@ -3,16 +3,16 @@ package selinux
import (
"bufio"
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"io/fs"
"math/big"
"math/rand/v2"
"os"
"os/user"
"path/filepath"
"slices"
"strconv"
"strings"
"sync"
@@ -30,6 +30,7 @@ const (
selinuxDir = "/etc/selinux/"
selinuxUsersDir = "contexts/users"
defaultContexts = "contexts/default_contexts"
failsafeContext = "contexts/failsafe_context"
selinuxConfig = selinuxDir + "config"
selinuxfsMount = "/sys/fs/selinux"
selinuxTypeTag = "SELINUXTYPE"
@@ -38,11 +39,9 @@ const (
)
type selinuxState struct {
mcsList map[string]bool
selinuxfs string
selinuxfsOnce sync.Once
enabledSet bool
enabled bool
mcsList map[string]struct{}
enabledSet bool
enabled bool
sync.Mutex
}
@@ -56,10 +55,19 @@ type mlsRange struct {
high *level
}
type openReaderCloser func() (io.ReadCloser, error)
func createOpener(path string) openReaderCloser {
return func() (io.ReadCloser, error) {
return os.Open(path)
}
}
type defaultSECtx struct {
userRdr io.Reader
openUserRdr openReaderCloser
verifier func(string) error
defaultRdr io.Reader
openDefaultRdr openReaderCloser
openFailsafeRdr openReaderCloser
user, level, scon string
}
@@ -72,26 +80,15 @@ const (
var (
readOnlyFileLabel string
state = selinuxState{
mcsList: make(map[string]bool),
state = selinuxState{
mcsList: make(map[string]struct{}),
}
// for policyRoot()
policyRootOnce sync.Once
policyRootVal string
// for label()
loadLabelsOnce sync.Once
labels map[string]string
)
func policyRoot() string {
policyRootOnce.Do(func() {
policyRootVal = filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
})
return policyRootVal
}
var policyRoot = sync.OnceValue(func() string {
return filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
})
func (s *selinuxState) setEnable(enabled bool) bool {
s.Lock()
@@ -148,7 +145,12 @@ func verifySELinuxfsMount(mnt string) bool {
return true
}
func findSELinuxfs() string {
// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs
// filesystem or an empty string if no mountpoint is found. Selinuxfs is
// a proc-like pseudo-filesystem that exposes the SELinux policy API to
// processes. The existence of an selinuxfs mount is used to determine
// whether SELinux is currently enabled or not.
var getSelinuxMountPoint = sync.OnceValue(func() string {
// fast path: check the default mount first
if verifySELinuxfsMount(selinuxfsMount) {
return selinuxfsMount
@@ -180,7 +182,7 @@ func findSELinuxfs() string {
return mnt
}
}
}
})
// findSELinuxfsMount returns a next selinuxfs mount point found,
// if there is one, or an empty string in case of EOF or error.
@@ -203,23 +205,6 @@ func findSELinuxfsMount(s *bufio.Scanner) string {
return ""
}
func (s *selinuxState) getSELinuxfs() string {
s.selinuxfsOnce.Do(func() {
s.selinuxfs = findSELinuxfs()
})
return s.selinuxfs
}
// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs
// filesystem or an empty string if no mountpoint is found. Selinuxfs is
// a proc-like pseudo-filesystem that exposes the SELinux policy API to
// processes. The existence of an selinuxfs mount is used to determine
// whether SELinux is currently enabled or not.
func getSelinuxMountPoint() string {
return state.getSELinuxfs()
}
// getEnabled returns whether SELinux is currently enabled.
func getEnabled() bool {
return state.getEnabled()
@@ -244,12 +229,9 @@ func readConfig(target string) string {
// Skip comments
continue
}
fields := bytes.SplitN(line, []byte{'='}, 2)
if len(fields) != 2 {
continue
}
if bytes.Equal(fields[0], []byte(target)) {
return string(bytes.Trim(fields[1], `"`))
key, val, ok := bytes.Cut(line, []byte{'='})
if ok && string(key) == target {
return string(bytes.Trim(val, `"`))
}
}
return ""
@@ -530,17 +512,6 @@ func setFSCreateLabel(label string) error {
return writeConThreadSelf("attr/fscreate", label)
}
// fsCreateLabel returns the default label the kernel which the kernel is using
// for file system objects created by this task. "" indicates default.
func fsCreateLabel() (string, error) {
return readConThreadSelf("attr/fscreate")
}
// currentLabel returns the SELinux label of the current process thread, or an error.
func currentLabel() (string, error) {
return readConThreadSelf("attr/current")
}
// pidLabel returns the SELinux label of the given pid, or an error.
func pidLabel(pid int) (string, error) {
it, err := openProcPid(pid, "attr/current", os.O_RDONLY|unix.O_CLOEXEC)
@@ -551,12 +522,6 @@ func pidLabel(pid int) (string, error) {
return readConFd(it)
}
// ExecLabel returns the SELinux label that the kernel will use for any programs
// that are executed by the current process thread, or an error.
func execLabel() (string, error) {
return readConThreadSelf("exec")
}
// canonicalizeContext takes a context string and writes it to the kernel
// the function then returns the context that the kernel will use. Use this
// function to check if two contexts are equivalent
@@ -581,13 +546,12 @@ func catsToBitset(cats string) (*big.Int, error) {
catlist := strings.Split(cats, ",")
for _, r := range catlist {
ranges := strings.SplitN(r, ".", 2)
if len(ranges) > 1 {
catstart, err := parseLevelItem(ranges[0], category)
if s, e, ok := strings.Cut(r, "."); ok {
catstart, err := parseLevelItem(s, category)
if err != nil {
return nil, err
}
catend, err := parseLevelItem(ranges[1], category)
catend, err := parseLevelItem(e, category)
if err != nil {
return nil, err
}
@@ -595,7 +559,7 @@ func catsToBitset(cats string) (*big.Int, error) {
bitset.SetBit(bitset, i, 1)
}
} else {
cat, err := parseLevelItem(ranges[0], category)
cat, err := parseLevelItem(r, category)
if err != nil {
return nil, err
}
@@ -623,14 +587,14 @@ func parseLevelItem(s string, sep levelItem) (int, error) {
// parseLevel fills a level from a string that contains
// a sensitivity and categories
func (l *level) parseLevel(levelStr string) error {
lvl := strings.SplitN(levelStr, ":", 2)
sens, err := parseLevelItem(lvl[0], sensitivity)
s, c, ok := strings.Cut(levelStr, ":")
sens, err := parseLevelItem(s, sensitivity)
if err != nil {
return fmt.Errorf("failed to parse sensitivity: %w", err)
}
l.sens = sens
if len(lvl) > 1 {
cats, err := catsToBitset(lvl[1])
if ok {
cats, err := catsToBitset(c)
if err != nil {
return fmt.Errorf("failed to parse categories: %w", err)
}
@@ -643,25 +607,19 @@ func (l *level) parseLevel(levelStr string) error {
// rangeStrToMLSRange marshals a string representation of a range.
func rangeStrToMLSRange(rangeStr string) (*mlsRange, error) {
r := &mlsRange{}
l := strings.SplitN(rangeStr, "-", 2)
switch len(l) {
// rangeStr that has a low and a high level, e.g. s4:c0.c1023-s6:c0.c1023
case 2:
r.high = &level{}
if err := r.high.parseLevel(l[1]); err != nil {
return nil, fmt.Errorf("failed to parse high level %q: %w", l[1], err)
}
fallthrough
// rangeStr that is single level, e.g. s6:c0,c3,c5,c30.c1023
case 1:
r.low = &level{}
if err := r.low.parseLevel(l[0]); err != nil {
return nil, fmt.Errorf("failed to parse low level %q: %w", l[0], err)
}
lo, hi, ok := strings.Cut(rangeStr, "-")
r.low = &level{}
if err := r.low.parseLevel(lo); err != nil {
return nil, fmt.Errorf("failed to parse low level %q: %w", lo, err)
}
if r.high == nil {
if ok {
// rangeStr that has a low and a high level, e.g. s4:c0.c1023-s6:c0.c1023.
r.high = &level{}
if err := r.high.parseLevel(hi); err != nil {
return nil, fmt.Errorf("failed to parse high level %q: %w", hi, err)
}
} else {
// rangeStr that is single level, e.g. s6:c0,c3,c5,c30.c1023.
r.high = r.low
}
@@ -732,22 +690,6 @@ func (m mlsRange) String() string {
return low + "-" + high
}
// TODO: remove these in favor of built-in min/max
// once we stop supporting Go < 1.21.
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
// calculateGlbLub computes the glb (greatest lower bound) and lub (least upper bound)
// of a source and target range.
// The glblub is calculated as the greater of the low sensitivities and
@@ -770,10 +712,10 @@ func calculateGlbLub(sourceRange, targetRange string) (string, error) {
outrange := &mlsRange{low: &level{}, high: &level{}}
/* take the greatest of the low */
outrange.low.sens = maxInt(s.low.sens, t.low.sens)
outrange.low.sens = max(s.low.sens, t.low.sens)
/* take the least of the high */
outrange.high.sens = minInt(s.high.sens, t.high.sens)
outrange.high.sens = min(s.high.sens, t.high.sens)
/* find the intersecting categories */
if s.low.cats != nil && t.low.cats != nil {
@@ -807,10 +749,10 @@ func readWriteCon(fpath string, val string) (string, error) {
}
// peerLabel retrieves the label of the client on the other side of a socket
func peerLabel(fd uintptr) (string, error) {
l, err := unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_PEERSEC)
func peerLabel(fd int) (string, error) {
l, err := unix.GetsockoptString(fd, unix.SOL_SOCKET, unix.SO_PEERSEC)
if err != nil {
return "", &os.PathError{Op: "getsockopt", Path: "fd " + strconv.Itoa(int(fd)), Err: err}
return "", &os.PathError{Op: "getsockopt", Path: "fd " + strconv.Itoa(fd), Err: err}
}
return l, nil
}
@@ -871,18 +813,34 @@ func newContext(label string) (Context, error) {
// clearLabels clears all reserved labels
func clearLabels() {
state.Lock()
state.mcsList = make(map[string]bool)
state.mcsList = make(map[string]struct{})
state.Unlock()
}
// reserveLabel reserves the MLS/MCS level component of the specified label
func reserveLabel(label string) {
// reserveLabel reserves the MLS/MCS level component of the specified label.
func reserveLabel(label string) error {
if len(label) != 0 {
con := strings.SplitN(label, ":", 4)
if len(con) > 3 {
_ = mcsAdd(con[3])
return mcsAdd(con[3])
}
}
return nil
}
func checkLabel(label string) error {
if len(label) != 0 {
con := strings.SplitN(label, ":", 4)
if len(con) > 3 {
state.Lock()
defer state.Unlock()
if _, exist := state.mcsList[con[3]]; exist {
return ErrMCSAlreadyExists
}
}
}
return nil
}
func selinuxEnforcePath() string {
@@ -938,10 +896,10 @@ func mcsAdd(mcs string) error {
}
state.Lock()
defer state.Unlock()
if state.mcsList[mcs] {
if _, exist := state.mcsList[mcs]; exist {
return ErrMCSAlreadyExists
}
state.mcsList[mcs] = true
state.mcsList[mcs] = struct{}{}
return nil
}
@@ -951,41 +909,21 @@ func mcsDelete(mcs string) {
}
state.Lock()
defer state.Unlock()
state.mcsList[mcs] = false
}
func intToMcs(id int, catRange uint32) string {
var (
SETSIZE = int(catRange)
TIER = SETSIZE
ORD = id
)
if id < 1 || id > 523776 {
return ""
}
for ORD > TIER {
ORD -= TIER
TIER--
}
TIER = SETSIZE - TIER
ORD += TIER
return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
delete(state.mcsList, mcs)
}
func uniqMcs(catRange uint32) string {
var (
n uint32
c1, c2 uint32
mcs string
)
for {
_ = binary.Read(rand.Reader, binary.LittleEndian, &n)
c1 = n % catRange
_ = binary.Read(rand.Reader, binary.LittleEndian, &n)
c2 = n % catRange
//#nosec G404 -- using slightly more predictable MCS labels won't affect security, so it's fine to use math/rand/v2 here.
{
c1 = rand.Uint32N(catRange)
c2 = rand.Uint32N(catRange)
}
if c1 == c2 {
continue
} else if c1 > c2 {
@@ -1023,11 +961,11 @@ func openContextFile() (*os.File, error) {
return os.Open(filepath.Join(policyRoot(), "contexts", "lxc_contexts"))
}
func loadLabels() {
labels = make(map[string]string)
var loadLabels = sync.OnceValue(func() map[string]string {
labels := make(map[string]string)
in, err := openContextFile()
if err != nil {
return
return labels
}
defer in.Close()
@@ -1043,25 +981,21 @@ func loadLabels() {
// Skip comments
continue
}
fields := bytes.SplitN(line, []byte{'='}, 2)
if len(fields) != 2 {
continue
if key, val, ok := bytes.Cut(line, []byte{'='}); ok {
key, val = bytes.TrimSpace(key), bytes.TrimSpace(val)
labels[string(key)] = string(bytes.Trim(val, `"`))
}
key, val := bytes.TrimSpace(fields[0]), bytes.TrimSpace(fields[1])
labels[string(key)] = string(bytes.Trim(val, `"`))
}
con, _ := NewContext(labels["file"])
con["level"] = fmt.Sprintf("s0:c%d,c%d", maxCategory-2, maxCategory-1)
privContainerMountLabel = con.get()
reserveLabel(privContainerMountLabel)
}
_ = reserveLabel(privContainerMountLabel)
return labels
})
func label(key string) string {
loadLabelsOnce.Do(func() {
loadLabels()
})
return labels[key]
return loadLabels()[key]
}
// kvmContainerLabels returns the default processLabel and mountLabel to be used
@@ -1075,6 +1009,15 @@ func kvmContainerLabels() (string, string) {
return addMcs(processLabel, label("file"))
}
func kvmContainerLabel() (string, error) {
processLabel := label("kvm_process")
if processLabel == "" {
processLabel = label("process")
}
pLabel, _, err := addMcsProc(processLabel)
return pLabel, err
}
// initContainerLabels returns the default processLabel and file labels to be
// used for containers running an init system like systemd by the calling process.
func initContainerLabels() (string, string) {
@@ -1086,6 +1029,16 @@ func initContainerLabels() (string, string) {
return addMcs(processLabel, label("file"))
}
func initContainerLabel() (string, error) {
processLabel := label("init_process")
if processLabel == "" {
processLabel = label("process")
}
pLabel, _, err := addMcsProc(processLabel)
return pLabel, err
}
// containerLabels returns an allocated processLabel and fileLabel to be used for
// container labeling by the calling process.
func containerLabels() (processLabel string, fileLabel string) {
@@ -1108,13 +1061,24 @@ func containerLabels() (processLabel string, fileLabel string) {
return addMcs(processLabel, fileLabel)
}
func addMcs(processLabel, fileLabel string) (string, string) {
scon, _ := NewContext(processLabel)
func addMcsProc(processLabel string) (string, string, error) {
var mcs string
scon, err := NewContext(processLabel)
if err != nil {
return "", "", err
}
if scon["level"] != "" {
mcs := uniqMcs(CategoryRange)
mcs = uniqMcs(CategoryRange)
scon["level"] = mcs
processLabel = scon.Get()
scon, _ = NewContext(fileLabel)
}
return processLabel, mcs, nil
}
func addMcs(processLabel, fileLabel string) (string, string) {
processLabel, mcs, _ := addMcsProc(processLabel)
if mcs != "" {
scon, _ := NewContext(fileLabel)
scon["level"] = mcs
fileLabel = scon.Get()
}
@@ -1281,6 +1245,111 @@ func dupSecOpt(src string) ([]string, error) {
return dup, nil
}
// checkGroup returns true if group's GID is in the list of GIDs gids.
func checkGroup(group string, gids []string, lookupGroup func(string) (*user.Group, error)) bool {
grp, err := lookupGroup(group)
if err != nil {
return false
}
return slices.Contains(gids, grp.Gid)
}
// getSeUserFromReader reads the seusers file: https://www.man7.org/linux/man-pages/man5/seusers.5.html
func getSeUserFromReader(username string, gids []string, r io.Reader, lookupGroup func(string) (*user.Group, error)) (seUser string, level string, err error) {
var defaultSeUser, defaultLevel string
var groupSeUser, groupLevel string
lineNum := -1
scanner := bufio.NewScanner(r)
for scanner.Scan() {
rawLine := scanner.Text()
lineNum++
// remove any trailing comments, then extra whitespace
line, _, _ := strings.Cut(rawLine, "#")
line = strings.TrimSpace(line)
if line == "" {
continue
}
userField, rest, ok := strings.Cut(line, ":")
if !ok {
return "", "", fmt.Errorf("line %d: malformed line", lineNum)
}
if userField == "" {
return "", "", fmt.Errorf("line %d: user_id or group_id is empty", lineNum)
}
seUserField, rest, ok := strings.Cut(rest, ":")
if seUserField == "" {
return "", "", fmt.Errorf("line %d: seuser_id is empty", lineNum)
}
var levelField string
// level is optional
if ok {
levelField = rest
}
// we found a match, return it
if userField == username {
return seUserField, levelField, nil
}
// if the first field starts with '%' it's a group, check if
// the user is a member of that group and set the group
// SELinux user and level if so
if userField[0] == '%' && groupSeUser == "" {
if checkGroup(userField[1:], gids, lookupGroup) {
groupSeUser = seUserField
groupLevel = levelField
}
} else if userField == "__default__" && defaultSeUser == "" {
defaultSeUser = seUserField
defaultLevel = levelField
}
}
if err := scanner.Err(); err != nil {
return "", "", fmt.Errorf("failed to read seusers file: %w", err)
}
if groupSeUser != "" {
return groupSeUser, groupLevel, nil
}
if defaultSeUser != "" {
return defaultSeUser, defaultLevel, nil
}
return "", "", fmt.Errorf("could not find SELinux user for %q login", username)
}
// getSeUserByName returns an SELinux user and MLS level that is
// mapped to a given Linux user.
func getSeUserByName(username string) (string, string, error) {
seUsersConf := filepath.Join(policyRoot(), "seusers")
confFile, err := os.Open(seUsersConf)
if err != nil {
return "", "", fmt.Errorf("failed to open seusers file: %w", err)
}
defer confFile.Close()
usr, err := user.Lookup(username)
if err != nil {
return "", "", err
}
gids, err := usr.GroupIds()
if err != nil {
return "", "", err
}
gids = append([]string{usr.Gid}, gids...)
seUser, level, err := getSeUserFromReader(username, gids, confFile, user.LookupGroup)
if err != nil {
return "", "", fmt.Errorf("failed to parse seusers file: %w", err)
}
return seUser, level, nil
}
// findUserInContext scans the reader for a valid SELinux context
// match that is verified with the verifier. Invalid contexts are
// skipped. It returns a matched context or an empty string if no
@@ -1338,6 +1407,33 @@ func findUserInContext(context Context, r io.Reader, verifier func(string) error
return "", nil
}
// getFailsafeContext returns the context in the failsafe_context file:
// https://www.man7.org/linux/man-pages/man5/failsafe_context.5.html
func getFailsafeContext(context Context, r io.Reader, verifier func(string) error) (string, error) {
conn := make([]byte, 256)
limReader := io.LimitReader(r, int64(len(conn)))
_, err := limReader.Read(conn)
if err != nil {
return "", fmt.Errorf("failed to read failsafe context: %w", err)
}
conn = bytes.TrimSpace(conn)
toConns := strings.SplitN(string(conn), ":", 4)
if len(toConns) != 3 {
return "", nil
}
context["role"] = toConns[0]
context["type"] = toConns[1]
outConn := context.get()
if err := verifier(outConn); err != nil {
return "", err
}
return outConn, nil
}
func getDefaultContextFromReaders(c *defaultSECtx) (string, error) {
if c.verifier == nil {
return "", ErrVerifierNil
@@ -1352,18 +1448,45 @@ func getDefaultContextFromReaders(c *defaultSECtx) (string, error) {
context["user"] = c.user
context["level"] = c.level
conn, err := findUserInContext(context, c.userRdr, c.verifier)
userRdr, err := c.openUserRdr()
if err != nil {
return "", err
return "", fmt.Errorf("failed to open user context file: %w", err)
}
defer userRdr.Close()
conn, err := findUserInContext(context, userRdr, c.verifier)
if err != nil {
return "", fmt.Errorf("failed to read %q's user context file: %w", c.user, err)
}
if conn != "" {
return conn, nil
}
conn, err = findUserInContext(context, c.defaultRdr, c.verifier)
defaultRdr, err := c.openDefaultRdr()
if err != nil {
return "", err
return "", fmt.Errorf("failed to open default context file: %w", err)
}
defer defaultRdr.Close()
conn, err = findUserInContext(context, defaultRdr, c.verifier)
if err != nil {
return "", fmt.Errorf("failed to read default user context file: %w", err)
}
if conn != "" {
return conn, nil
}
failsafeRdr, err := c.openFailsafeRdr()
if err != nil {
return "", fmt.Errorf("failed to open failsafe context file: %w", err)
}
defer failsafeRdr.Close()
conn, err = getFailsafeContext(context, failsafeRdr, c.verifier)
if err != nil {
return "", fmt.Errorf("failed to read failsafe_context: %w", err)
}
if conn != "" {
@@ -1375,26 +1498,17 @@ func getDefaultContextFromReaders(c *defaultSECtx) (string, error) {
func getDefaultContextWithLevel(user, level, scon string) (string, error) {
userPath := filepath.Join(policyRoot(), selinuxUsersDir, user)
fu, err := os.Open(userPath)
if err != nil {
return "", err
}
defer fu.Close()
defaultPath := filepath.Join(policyRoot(), defaultContexts)
fd, err := os.Open(defaultPath)
if err != nil {
return "", err
}
defer fd.Close()
failsafePath := filepath.Join(policyRoot(), failsafeContext)
c := defaultSECtx{
user: user,
level: level,
scon: scon,
userRdr: fu,
defaultRdr: fd,
verifier: securityCheckContext,
user: user,
level: level,
scon: scon,
openUserRdr: createOpener(userPath),
openDefaultRdr: createOpener(defaultPath),
openFailsafeRdr: createOpener(failsafePath),
verifier: securityCheckContext,
}
return getDefaultContextFromReaders(&c)
+19 -15
View File
@@ -1,5 +1,4 @@
//go:build !linux
// +build !linux
package selinux
@@ -41,22 +40,10 @@ func setFSCreateLabel(string) error {
return nil
}
func fsCreateLabel() (string, error) {
return "", nil
}
func currentLabel() (string, error) {
return "", nil
}
func pidLabel(int) (string, error) {
return "", nil
}
func execLabel() (string, error) {
return "", nil
}
func canonicalizeContext(string) (string, error) {
return "", nil
}
@@ -69,7 +56,7 @@ func calculateGlbLub(string, string) (string, error) {
return "", nil
}
func peerLabel(uintptr) (string, error) {
func peerLabel(int) (string, error) {
return "", nil
}
@@ -92,7 +79,12 @@ func newContext(string) (Context, error) {
func clearLabels() {
}
func reserveLabel(string) {
func reserveLabel(string) error {
return nil
}
func checkLabel(string) error {
return nil
}
func isMLSEnabled() bool {
@@ -122,10 +114,18 @@ func kvmContainerLabels() (string, string) {
return "", ""
}
func kvmContainerLabel() (string, error) {
return "", nil
}
func initContainerLabels() (string, string) {
return "", ""
}
func initContainerLabel() (string, error) {
return "", nil
}
func containerLabels() (string, string) {
return "", ""
}
@@ -146,6 +146,10 @@ func dupSecOpt(string) ([]string, error) {
return nil, nil
}
func getSeUserByName(string) (string, string, error) {
return "", "", nil
}
func getDefaultContextWithLevel(string, string, string) (string, error) {
return "", nil
}
+1 -4
View File
@@ -1,6 +1,3 @@
//go:build go1.16
// +build go1.16
package pwalkdir
import (
@@ -92,7 +89,7 @@ func WalkN(root string, walkFn fs.WalkDirFunc, num int) error {
}()
wg.Add(num)
for i := 0; i < num; i++ {
for range num {
go func() {
for file := range files {
if e := walkFn(file.path, file.entry, nil); e != nil {
+2 -2
View File
@@ -85,8 +85,8 @@ github.com/opencontainers/cgroups/systemd
## explicit
github.com/opencontainers/runtime-spec/specs-go
github.com/opencontainers/runtime-spec/specs-go/features
# github.com/opencontainers/selinux v1.13.1
## explicit; go 1.19
# github.com/opencontainers/selinux v1.14.1
## explicit; go 1.22
github.com/opencontainers/selinux/go-selinux
github.com/opencontainers/selinux/go-selinux/label
github.com/opencontainers/selinux/pkg/pwalkdir