From 4c749550fe34166395a8746a139b6a95b9542ea1 Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy Date: Fri, 26 Jun 2026 16:08:03 -0400 Subject: [PATCH] Roll back `github.com/opencontainers/selinux` version PR #2769 upgraded `github.com/opencontainers/selinux` from `v1.13.1` to `v1.15.0`. However, containerd is still on `v1.13.1`, and the version mismatch is blocking the upgrade of new hcsshim releases (https://github.com/containerd/containerd/pull/13671). Upgrading the `selinux` dependency in containerd is currently blocked (https://github.com/containerd/containerd/pull/13395), so, for now, roll back the version until the latter PR is merged so their hcsshim dependency can be updated. Signed-off-by: Hamza El-Saawy --- go.mod | 2 +- go.sum | 4 +- test/go.mod | 2 +- test/go.sum | 4 +- .../selinux/go-selinux/label/label_linux.go | 99 ++-- .../selinux/go-selinux/label/label_stub.go | 1 + .../selinux/go-selinux/selinux.go | 101 +--- .../selinux/go-selinux/selinux_linux.go | 529 ++++++------------ .../selinux/go-selinux/selinux_stub.go | 38 +- .../selinux/pkg/pwalkdir/pwalkdir.go | 5 +- vendor/modules.txt | 4 +- 11 files changed, 271 insertions(+), 518 deletions(-) diff --git a/go.mod b/go.mod index e0c32ca092..4907e0d3f5 100644 --- a/go.mod +++ b/go.mod @@ -110,7 +110,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/opencontainers/selinux v1.15.0 // indirect + github.com/opencontainers/selinux v1.13.1 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect diff --git a/go.sum b/go.sum index 83d8303596..f1392d64e0 100644 --- a/go.sum +++ b/go.sum @@ -860,8 +860,8 @@ github.com/opencontainers/runc v1.4.2 h1:/AEjjXuVH9lTRl9ZyUFQj7oWBM7Xv00qFV6Vx9q github.com/opencontainers/runc v1.4.2/go.mod h1:ufk5PTTsy5pnGBAvTh50e+eqGk01pYH2YcVxh557Qlk= 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.15.0 h1:4Gs40e/R2FvM8PC1HPaPncLLaDor8Y2WDfk5gjU9o5M= -github.com/opencontainers/selinux v1.15.0/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ= +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/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= diff --git a/test/go.mod b/test/go.mod index efdf6262e2..a18c339a58 100644 --- a/test/go.mod +++ b/test/go.mod @@ -87,7 +87,7 @@ require ( github.com/open-policy-agent/opa v0.70.0 // indirect github.com/opencontainers/cgroups v0.0.6 // indirect github.com/opencontainers/runc v1.4.2 // indirect - github.com/opencontainers/selinux v1.15.0 // indirect + github.com/opencontainers/selinux v1.13.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect diff --git a/test/go.sum b/test/go.sum index 823ad6573a..1b29c6f005 100644 --- a/test/go.sum +++ b/test/go.sum @@ -227,8 +227,8 @@ github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5 github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.9.1-0.20260316125833-8a4db579f5c8 h1:2NAWFjN0PmdIe3XojVL9wf3lJ1//VqAgc7MOSYHQslE= github.com/opencontainers/runtime-tools v0.9.1-0.20260316125833-8a4db579f5c8/go.mod h1:DKDEfzxvRkoQ6n9TGhxQgg2IM1lY4aM0eaQP4e3oElw= -github.com/opencontainers/selinux v1.15.0 h1:4Gs40e/R2FvM8PC1HPaPncLLaDor8Y2WDfk5gjU9o5M= -github.com/opencontainers/selinux v1.15.0/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ= +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/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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 a89c6bda19..95f29e21f4 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 @@ -10,6 +10,7 @@ import ( // Valid Label Options var validOptions = map[string]bool{ + "disable": true, "type": true, "filetype": true, "user": true, @@ -21,74 +22,60 @@ 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. -// -// 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. -// +// the labels. The labels returned will include a random MCS String, that is +// guaranteed 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 } - if len(options) > 0 && options[0] == "disable" { - return "", selinux.PrivContainerMountLabel(), nil - } - processLabel, mountLabel := selinux.ContainerLabels() //nolint:staticcheck // ContainerLabels will be moved to an internal package. - if processLabel == "" || len(options) == 0 { - // 1. processLabel is required; if empty, do nothing. - // 2. If there are no options to process, we're done. - 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 { - // For backward compatibility, process "disable" - // even if it's not the only option. - 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) + processLabel, mountLabel := selinux.ContainerLabels() + if processLabel != "" { + defer func() { + if retErr != nil { + selinux.ReleaseLabel(mountLabel) + } + }() + pcon, err := selinux.NewContext(processLabel) + if err != nil { + return "", "", err } - if k == "filetype" { - mcon["type"] = v - continue + mcsLevel := pcon["level"] + mcon, err := selinux.NewContext(mountLabel) + if err != nil { + return "", "", err } - pcon[k] = v - if k == "level" || k == "user" { - mcon[k] = v + 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 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 + if pcon.Get() != processLabel { + if pcon["level"] != mcsLevel { + selinux.ReleaseLabel(processLabel) } + processLabel = pcon.Get() + selinux.ReserveLabel(processLabel) } - processLabel = p + mountLabel = mcon.Get() } - 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 6cbf8876e3..7a54afc5e6 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,4 +1,5 @@ //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 06b4acacbc..15150d4752 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go @@ -26,6 +26,11 @@ 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 @@ -40,29 +45,12 @@ 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. - // - // Deprecated: use [SetCategoryRange] instead. + // CategoryRange allows the upper bound on the category range to be adjusted CategoryRange = DefaultCategoryRange privContainerMountLabel string ) -// ProcessKind selects which process domain [SetProcessKind] applies to a label. -type ProcessKind int - -const ( - ProcessKindRegular ProcessKind = 1 - ProcessKindInit ProcessKind = 2 - ProcessKindKVM ProcessKind = 3 -) - -// SetProcessKind returns label with its type component replaced by the one -// corresponding to kind. Other label components are kept intact. -func SetProcessKind(label string, kind ProcessKind) (string, error) { - return setProcessKind(label, kind) -} - // Context is a representation of the SELinux label broken into 4 parts type Context map[string]string @@ -76,16 +64,6 @@ 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) { @@ -129,12 +107,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 readConThreadSelf("attr/fscreate") + return fsCreateLabel() } // CurrentLabel returns the SELinux label of the current process thread, or an error. func CurrentLabel() (string, error) { - return readConThreadSelf("attr/current") + return currentLabel() } // PidLabel returns the SELinux label of the given pid, or an error. @@ -145,7 +123,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 readConThreadSelf("attr/exec") + return execLabel() } // CanonicalizeContext takes a context string and writes it to the kernel @@ -202,7 +180,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(int(fd)) //#nosec G115 -- ignore "integer overflow conversion uintptr -> int". + return peerLabel(fd) } // SetKeyLabel takes a process label and tells the kernel to assign the @@ -238,26 +216,9 @@ func ClearLabels() { clearLabels() } -// ReserveLabel reserves the MLS/MCS level component of the specified label. -// -// Deprecated: use [ReserveLabelV2] instead. +// ReserveLabel reserves the MLS/MCS level component of the specified label func ReserveLabel(label string) { - _ = 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) + reserveLabel(label) } // MLSEnabled checks if MLS is enabled. @@ -289,51 +250,25 @@ func ReleaseLabel(label string) { releaseLabel(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. +// ROFileLabel returns the specified SELinux readonly file label 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. -// -// If you only need to change a type of existing label, use [SetProcessKind] instead. -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. -// -// If you only need to change a type of existing label, use [SetProcessKind] instead. -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() } @@ -370,19 +305,11 @@ 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 and finally the global /etc/selinux/{SELINUXTYPE}/contexts/failsafe_context -// file if no match can be found anywhere else. +// file. 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 f238b19400..6d7f8e270b 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,7 +30,6 @@ const ( selinuxDir = "/etc/selinux/" selinuxUsersDir = "contexts/users" defaultContexts = "contexts/default_contexts" - failsafeContext = "contexts/failsafe_context" selinuxConfig = selinuxDir + "config" selinuxfsMount = "/sys/fs/selinux" selinuxTypeTag = "SELINUXTYPE" @@ -39,9 +38,11 @@ const ( ) type selinuxState struct { - mcsList map[string]struct{} - enabledSet bool - enabled bool + mcsList map[string]bool + selinuxfs string + selinuxfsOnce sync.Once + enabledSet bool + enabled bool sync.Mutex } @@ -55,19 +56,10 @@ 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 { - openUserRdr openReaderCloser + userRdr io.Reader verifier func(string) error - openDefaultRdr openReaderCloser - openFailsafeRdr openReaderCloser + defaultRdr io.Reader user, level, scon string } @@ -80,15 +72,26 @@ const ( var ( readOnlyFileLabel string - - state = selinuxState{ - mcsList: make(map[string]struct{}), + state = selinuxState{ + mcsList: make(map[string]bool), } + + // for policyRoot() + policyRootOnce sync.Once + policyRootVal string + + // for label() + loadLabelsOnce sync.Once + labels map[string]string ) -var policyRoot = sync.OnceValue(func() string { - return filepath.Join(selinuxDir, readConfig(selinuxTypeTag)) -}) +func policyRoot() string { + policyRootOnce.Do(func() { + policyRootVal = filepath.Join(selinuxDir, readConfig(selinuxTypeTag)) + }) + + return policyRootVal +} func (s *selinuxState) setEnable(enabled bool) bool { s.Lock() @@ -145,12 +148,7 @@ func verifySELinuxfsMount(mnt string) bool { return true } -// 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 { +func findSELinuxfs() string { // fast path: check the default mount first if verifySELinuxfsMount(selinuxfsMount) { return selinuxfsMount @@ -182,7 +180,7 @@ var getSelinuxMountPoint = sync.OnceValue(func() 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. @@ -205,6 +203,23 @@ 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() @@ -229,9 +244,12 @@ func readConfig(target string) string { // Skip comments continue } - key, val, ok := bytes.Cut(line, []byte{'='}) - if ok && string(key) == target { - return string(bytes.Trim(val, `"`)) + fields := bytes.SplitN(line, []byte{'='}, 2) + if len(fields) != 2 { + continue + } + if bytes.Equal(fields[0], []byte(target)) { + return string(bytes.Trim(fields[1], `"`)) } } return "" @@ -512,6 +530,17 @@ 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) @@ -522,6 +551,12 @@ 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 @@ -546,12 +581,13 @@ func catsToBitset(cats string) (*big.Int, error) { catlist := strings.Split(cats, ",") for _, r := range catlist { - if s, e, ok := strings.Cut(r, "."); ok { - catstart, err := parseLevelItem(s, category) + ranges := strings.SplitN(r, ".", 2) + if len(ranges) > 1 { + catstart, err := parseLevelItem(ranges[0], category) if err != nil { return nil, err } - catend, err := parseLevelItem(e, category) + catend, err := parseLevelItem(ranges[1], category) if err != nil { return nil, err } @@ -559,7 +595,7 @@ func catsToBitset(cats string) (*big.Int, error) { bitset.SetBit(bitset, i, 1) } } else { - cat, err := parseLevelItem(r, category) + cat, err := parseLevelItem(ranges[0], category) if err != nil { return nil, err } @@ -587,14 +623,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 { - s, c, ok := strings.Cut(levelStr, ":") - sens, err := parseLevelItem(s, sensitivity) + lvl := strings.SplitN(levelStr, ":", 2) + sens, err := parseLevelItem(lvl[0], sensitivity) if err != nil { return fmt.Errorf("failed to parse sensitivity: %w", err) } l.sens = sens - if ok { - cats, err := catsToBitset(c) + if len(lvl) > 1 { + cats, err := catsToBitset(lvl[1]) if err != nil { return fmt.Errorf("failed to parse categories: %w", err) } @@ -607,19 +643,25 @@ func (l *level) parseLevel(levelStr string) error { // rangeStrToMLSRange marshals a string representation of a range. func rangeStrToMLSRange(rangeStr string) (*mlsRange, error) { r := &mlsRange{} - 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. + 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(hi); err != nil { - return nil, fmt.Errorf("failed to parse high level %q: %w", hi, err) + if err := r.high.parseLevel(l[1]); err != nil { + return nil, fmt.Errorf("failed to parse high level %q: %w", l[1], err) } - } else { - // rangeStr that is single level, e.g. s6:c0,c3,c5,c30.c1023. + 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 r.high == nil { r.high = r.low } @@ -690,6 +732,22 @@ 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 @@ -712,10 +770,10 @@ func calculateGlbLub(sourceRange, targetRange string) (string, error) { outrange := &mlsRange{low: &level{}, high: &level{}} /* take the greatest of the low */ - outrange.low.sens = max(s.low.sens, t.low.sens) + outrange.low.sens = maxInt(s.low.sens, t.low.sens) /* take the least of the high */ - outrange.high.sens = min(s.high.sens, t.high.sens) + outrange.high.sens = minInt(s.high.sens, t.high.sens) /* find the intersecting categories */ if s.low.cats != nil && t.low.cats != nil { @@ -749,10 +807,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 int) (string, error) { - l, err := unix.GetsockoptString(fd, unix.SOL_SOCKET, unix.SO_PEERSEC) +func peerLabel(fd uintptr) (string, error) { + l, err := unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_PEERSEC) if err != nil { - return "", &os.PathError{Op: "getsockopt", Path: "fd " + strconv.Itoa(fd), Err: err} + return "", &os.PathError{Op: "getsockopt", Path: "fd " + strconv.Itoa(int(fd)), Err: err} } return l, nil } @@ -813,34 +871,18 @@ func newContext(label string) (Context, error) { // clearLabels clears all reserved labels func clearLabels() { state.Lock() - state.mcsList = make(map[string]struct{}) + state.mcsList = make(map[string]bool) state.Unlock() } -// 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 { +// reserveLabel reserves the MLS/MCS level component of the specified label +func reserveLabel(label string) { 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 - } + _ = mcsAdd(con[3]) } } - return nil } func selinuxEnforcePath() string { @@ -896,10 +938,10 @@ func mcsAdd(mcs string) error { } state.Lock() defer state.Unlock() - if _, exist := state.mcsList[mcs]; exist { + if state.mcsList[mcs] { return ErrMCSAlreadyExists } - state.mcsList[mcs] = struct{}{} + state.mcsList[mcs] = true return nil } @@ -909,21 +951,41 @@ func mcsDelete(mcs string) { } state.Lock() defer state.Unlock() - delete(state.mcsList, mcs) + 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) } func uniqMcs(catRange uint32) string { var ( + n uint32 c1, c2 uint32 mcs string ) for { - //#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) - } + _ = binary.Read(rand.Reader, binary.LittleEndian, &n) + c1 = n % catRange + _ = binary.Read(rand.Reader, binary.LittleEndian, &n) + c2 = n % catRange if c1 == c2 { continue } else if c1 > c2 { @@ -961,11 +1023,11 @@ func openContextFile() (*os.File, error) { return os.Open(filepath.Join(policyRoot(), "contexts", "lxc_contexts")) } -var loadLabels = sync.OnceValue(func() map[string]string { - labels := make(map[string]string) +func loadLabels() { + labels = make(map[string]string) in, err := openContextFile() if err != nil { - return labels + return } defer in.Close() @@ -981,21 +1043,25 @@ var loadLabels = sync.OnceValue(func() map[string]string { // Skip comments 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, `"`)) + fields := bytes.SplitN(line, []byte{'='}, 2) + if len(fields) != 2 { + continue } + 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) - return labels -}) + reserveLabel(privContainerMountLabel) +} func label(key string) string { - return loadLabels()[key] + loadLabelsOnce.Do(func() { + loadLabels() + }) + return labels[key] } // kvmContainerLabels returns the default processLabel and mountLabel to be used @@ -1009,15 +1075,6 @@ 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) { @@ -1029,16 +1086,6 @@ 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) { @@ -1061,24 +1108,13 @@ func containerLabels() (processLabel string, fileLabel string) { return addMcs(processLabel, fileLabel) } -func addMcsProc(processLabel string) (string, string, error) { - var mcs string - scon, err := NewContext(processLabel) - if err != nil { - return "", "", err - } +func addMcs(processLabel, fileLabel string) (string, string) { + scon, _ := NewContext(processLabel) if scon["level"] != "" { - mcs = uniqMcs(CategoryRange) + mcs := uniqMcs(CategoryRange) scon["level"] = mcs processLabel = scon.Get() - } - return processLabel, mcs, nil -} - -func addMcs(processLabel, fileLabel string) (string, string) { - processLabel, mcs, _ := addMcsProc(processLabel) - if mcs != "" { - scon, _ := NewContext(fileLabel) + scon, _ = NewContext(fileLabel) scon["level"] = mcs fileLabel = scon.Get() } @@ -1245,111 +1281,6 @@ 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 @@ -1407,33 +1338,6 @@ 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 @@ -1448,45 +1352,18 @@ func getDefaultContextFromReaders(c *defaultSECtx) (string, error) { context["user"] = c.user context["level"] = c.level - userRdr, err := c.openUserRdr() - if err != nil { - 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 - } - - defaultRdr, err := c.openDefaultRdr() - if err != nil { - return "", fmt.Errorf("failed to open default context file: %w", err) - } - defer defaultRdr.Close() - - conn, err = findUserInContext(context, defaultRdr, c.verifier) + conn, err := findUserInContext(context, c.userRdr, c.verifier) if err != nil { - return "", fmt.Errorf("failed to read default user context file: %w", err) + return "", err } if conn != "" { return conn, nil } - failsafeRdr, err := c.openFailsafeRdr() + conn, err = findUserInContext(context, c.defaultRdr, c.verifier) 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) + return "", err } if conn != "" { @@ -1498,61 +1375,27 @@ func getDefaultContextFromReaders(c *defaultSECtx) (string, error) { func getDefaultContextWithLevel(user, level, scon string) (string, error) { userPath := filepath.Join(policyRoot(), selinuxUsersDir, user) - defaultPath := filepath.Join(policyRoot(), defaultContexts) - failsafePath := filepath.Join(policyRoot(), failsafeContext) - - c := defaultSECtx{ - user: user, - level: level, - scon: scon, - openUserRdr: createOpener(userPath), - openDefaultRdr: createOpener(defaultPath), - openFailsafeRdr: createOpener(failsafePath), - verifier: securityCheckContext, - } - - return getDefaultContextFromReaders(&c) -} - -func (k ProcessKind) keys() (primary, fallback string, ok bool) { - switch k { - case ProcessKindRegular: - return "process", "", true - case ProcessKindInit: - return "init_process", "process", true - case ProcessKindKVM: - return "kvm_process", "process", true - } - return "", "", false -} - -func setProcessKind(cLabel string, k ProcessKind) (string, error) { - if cLabel == "" { - return "", nil - } - primary, fallback, ok := k.keys() - if !ok { - return "", fmt.Errorf("selinux.SetProcessKind: invalid ProcessKind %d", k) - } - - src := label(primary) - if src == "" && fallback != "" { - src = label(fallback) - } - if src == "" { - return cLabel, nil + fu, err := os.Open(userPath) + if err != nil { + return "", err } + defer fu.Close() - // Replace cLabel type with one from src. - srcCtx, err := newContext(src) + defaultPath := filepath.Join(policyRoot(), defaultContexts) + fd, err := os.Open(defaultPath) if err != nil { - return "", fmt.Errorf("selinux.SetProcessKind: invalid %s label %s: %w", primary, src, err) + return "", err } - dstCtx, err := newContext(cLabel) - if err != nil { - return "", fmt.Errorf("selinux.SetProcessKind: invalid label %s: %w", cLabel, err) + defer fd.Close() + + c := defaultSECtx{ + user: user, + level: level, + scon: scon, + userRdr: fu, + defaultRdr: fd, + verifier: securityCheckContext, } - dstCtx["type"] = srcCtx["type"] - return dstCtx.get(), nil + 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 d01bf2615e..382244e503 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go @@ -1,4 +1,5 @@ //go:build !linux +// +build !linux package selinux @@ -40,10 +41,22 @@ 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 } @@ -56,7 +69,7 @@ func calculateGlbLub(string, string) (string, error) { return "", nil } -func peerLabel(int) (string, error) { +func peerLabel(uintptr) (string, error) { return "", nil } @@ -79,12 +92,7 @@ func newContext(string) (Context, error) { func clearLabels() { } -func reserveLabel(string) error { - return nil -} - -func checkLabel(string) error { - return nil +func reserveLabel(string) { } func isMLSEnabled() bool { @@ -114,18 +122,10 @@ 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,10 +146,6 @@ 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 } @@ -157,7 +153,3 @@ func getDefaultContextWithLevel(string, string, string) (string, error) { func label(_ string) string { return "" } - -func setProcessKind(string, ProcessKind) (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 d361dcb64c..5d2d09a298 100644 --- a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go +++ b/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go @@ -1,3 +1,6 @@ +//go:build go1.16 +// +build go1.16 + package pwalkdir import ( @@ -89,7 +92,7 @@ func WalkN(root string, walkFn fs.WalkDirFunc, num int) error { }() wg.Add(num) - for range num { + for i := 0; i < num; i++ { 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 8060c68489..9cea083fde 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -462,8 +462,8 @@ github.com/opencontainers/runc/types ## explicit github.com/opencontainers/runtime-spec/specs-go github.com/opencontainers/runtime-spec/specs-go/features -# github.com/opencontainers/selinux v1.15.0 -## explicit; go 1.22 +# github.com/opencontainers/selinux v1.13.1 +## explicit; go 1.19 github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label github.com/opencontainers/selinux/pkg/pwalkdir