From cd6de7012630202bf29e607bd4ead6bd95583f75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 04:52:50 +0000 Subject: [PATCH] build(deps): bump github.com/opencontainers/selinux Bumps [github.com/opencontainers/selinux](https://github.com/opencontainers/selinux) from 1.13.1 to 1.14.0. - [Release notes](https://github.com/opencontainers/selinux/releases) - [Commits](https://github.com/opencontainers/selinux/compare/v1.13.1...v1.14.0) --- updated-dependencies: - dependency-name: github.com/opencontainers/selinux dependency-version: 1.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- .../selinux/go-selinux/label/label_linux.go | 83 +-- .../selinux/go-selinux/label/label_stub.go | 1 - .../selinux/go-selinux/selinux.go | 78 ++- .../selinux/go-selinux/selinux_linux.go | 486 +++++++++++------- .../selinux/go-selinux/selinux_stub.go | 34 +- .../selinux/pkg/pwalkdir/pwalkdir.go | 5 +- vendor/modules.txt | 4 +- 9 files changed, 431 insertions(+), 266 deletions(-) diff --git a/go.mod b/go.mod index f5d3898e188..dd60ccee944 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/mrunalp/fileutils v0.5.1 github.com/opencontainers/cgroups v0.0.6 github.com/opencontainers/runtime-spec v1.3.0 - github.com/opencontainers/selinux v1.13.1 + github.com/opencontainers/selinux v1.14.0 github.com/seccomp/libseccomp-golang v0.11.1 github.com/sirupsen/logrus v1.9.4 github.com/urfave/cli v1.22.17 diff --git a/go.sum b/go.sum index 1223a1b3f0c..05a64df6176 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,8 @@ github.com/opencontainers/cgroups v0.0.6 h1:tfZFWTIIGaUUFImTyuTg+Mr5x8XRiSdZESgE github.com/opencontainers/cgroups v0.0.6/go.mod h1:oWVzJsKK0gG9SCRBfTpnn16WcGEqDI8PAcpMGbqWxcs= github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= -github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= +github.com/opencontainers/selinux v1.14.0 h1:k1w6YWg3w/TvfZUAc3ksdaRwGNulRbE88TxqAZxUSOE= +github.com/opencontainers/selinux v1.14.0/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go index 95f29e21f4e..9c0ee6755b8 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go @@ -30,52 +30,53 @@ 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 + 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) } - mcsLevel := pcon["level"] - mcon, err := selinux.NewContext(mountLabel) - if err != nil { - return "", "", err + }() + 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 } - 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] - } + pcon[k] = v + if k == "level" || k == "user" { + mcon[k] = v } - if pcon.Get() != processLabel { - if pcon["level"] != mcsLevel { - selinux.ReleaseLabel(processLabel) - } - processLabel = pcon.Get() - selinux.ReserveLabel(processLabel) + } + if p := pcon.Get(); p != processLabel { + if pcon["level"] != mcsLevel { + selinux.ReleaseLabel(processLabel) + } + if err := selinux.ReserveLabelV2(p); err != nil { + return "", "", err } - mountLabel = mcon.Get() + processLabel = p } + mountLabel = mcon.Get() return processLabel, mountLabel, nil } diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go index 7a54afc5e6d..6cbf8876e39 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_stub.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux package label diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go index 15150d47528..9144bfcd55e 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go @@ -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,22 @@ 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. +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 +270,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 +347,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/ 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) } diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index 6d7f8e270bd..2117155701f 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -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), - } - - // for policyRoot() - policyRootOnce sync.Once - policyRootVal string - // for label() - loadLabelsOnce sync.Once - labels map[string]string + state = selinuxState{ + mcsList: make(map[string]struct{}), + } ) -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: + 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 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(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) + if err := r.high.parseLevel(hi); err != nil { + return nil, fmt.Errorf("failed to parse high level %q: %w", hi, err) } - } - - if r.high == nil { + } 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 { + return mcsAdd(con[3]) + } + } + + return nil +} + +func checkLabel(label string) error { if len(label) != 0 { con := strings.SplitN(label, ":", 4) if len(con) > 3 { - _ = mcsAdd(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) diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go index 382244e5036..78a4e1fe352 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go @@ -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 } diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go b/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go index 5d2d09a2985..d361dcb64ce 100644 --- a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go +++ b/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go @@ -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 { diff --git a/vendor/modules.txt b/vendor/modules.txt index 977edd5db60..987c94f589c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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.0 +## explicit; go 1.22 github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label github.com/opencontainers/selinux/pkg/pwalkdir