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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ jobs:
continue-on-error: true
run: lima make -C /tmp/selinux GOARCH=386 test

# https://github.com/opencontainers/selinux/issues/222
# https://github.com/opencontainers/selinux/issues/225
- name: "racy test"
continue-on-error: true
run: lima bash -c 'cd /tmp/selinux && go test -timeout 10m -count 100000 ./go-selinux'

- name: "Show AVC denials"
run: lima sudo ausearch -m AVC,USER_AVC || true

Expand Down
16 changes: 12 additions & 4 deletions go-selinux/selinux.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ var (
// ErrVerifierNil is returned when a context verifier function is nil.
ErrVerifierNil = errors.New("verifier function is nil")

// ErrNotTGLeader is returned by [SetKeyLabel] if the calling thread
// 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 = DefaultCategoryRange

Expand Down Expand Up @@ -180,10 +184,14 @@ func PeerLabel(fd uintptr) (string, error) {
}

// SetKeyLabel takes a process label and tells the kernel to assign the
// label to the next kernel keyring that gets created. Calls to SetKeyLabel
// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until
// the kernel keyring is created to guarantee another goroutine does not migrate
// to the current thread before execution is complete.
// label to the next kernel keyring that gets created.
//
// Calls to SetKeyLabel should be wrapped in
// runtime.LockOSThread()/runtime.UnlockOSThread() until the kernel keyring is
// created to guarantee another goroutine does not migrate to the current
// thread before execution is complete.
//
// Only the thread group leader can set key label.
func SetKeyLabel(label string) error {
return setKeyLabel(label)
}
Expand Down
3 changes: 3 additions & 0 deletions go-selinux/selinux_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,9 @@ func setKeyLabel(label string) error {
if label == "" && errors.Is(err, os.ErrPermission) {
return nil
}
if errors.Is(err, unix.EACCES) && unix.Getuid() != unix.Gettid() {
return ErrNotTGLeader
}
return err
}

Expand Down
43 changes: 32 additions & 11 deletions go-selinux/selinux_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"

"golang.org/x/sys/unix"
)

func TestSetFileLabel(t *testing.T) {
Expand Down Expand Up @@ -187,6 +190,12 @@ func TestSocketLabel(t *testing.T) {
t.Skip("SELinux not enabled, skipping.")
}

// Ensure the thread stays the same for duration of the test.
// Otherwise Go runtime can switch this to a different thread,
// which results in EACCES in call to SetSocketLabel.
runtime.LockOSThread()
defer runtime.UnlockOSThread()

label := "system_u:object_r:container_t:s0:c1,c2"
if err := SetSocketLabel(label); err != nil {
t.Fatal(err)
Expand All @@ -205,6 +214,16 @@ func TestKeyLabel(t *testing.T) {
t.Skip("SELinux not enabled, skipping.")
}

// Ensure the thread stays the same for duration of the test.
// Otherwise Go runtime can switch this to a different thread,
// which results in EACCES in call to SetKeyLabel.
runtime.LockOSThread()
defer runtime.UnlockOSThread()

if unix.Getpid() != unix.Gettid() {
t.Skip(ErrNotTGLeader)
}

label := "system_u:object_r:container_t:s0:c1,c2"
if err := SetKeyLabel(label); err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -235,6 +254,12 @@ func TestSELinux(t *testing.T) {
t.Skip("SELinux not enabled, skipping.")
}

// Ensure the thread stays the same for duration of the test.
// Otherwise Go runtime can switch this to a different thread,
// which results in EACCES in call to SetFSCreateLabel.
runtime.LockOSThread()
defer runtime.UnlockOSThread()

var (
err error
plabel, flabel string
Expand All @@ -259,21 +284,17 @@ func TestSELinux(t *testing.T) {
ReleaseLabel(plabel)

pid := os.Getpid()
t.Logf("PID:%d MCS:%s\n", pid, intToMcs(pid, 1023))
t.Logf("PID:%d MCS:%s", pid, intToMcs(pid, 1023))
err = SetFSCreateLabel("unconfined_u:unconfined_r:unconfined_t:s0")
if err == nil {
t.Log(FSCreateLabel())
} else {
t.Log("SetFSCreateLabel failed", err)
t.Fatal(err)
if err != nil {
t.Fatal("SetFSCreateLabel failed:", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit; t.Fatal terminates the test, so we could get rid of the if/else, and instead put the t.Fatal in a if err != nil branch

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just trying to minimize the changes to simplify review. But since you've asked for it... 😁

Indeed it looks better this way.

}
t.Log(FSCreateLabel())
err = SetFSCreateLabel("")
if err == nil {
t.Log(FSCreateLabel())
} else {
t.Log("SetFSCreateLabel failed", err)
t.Fatal(err)
if err != nil {
t.Fatal("SetFSCreateLabel failed:", err)
}
t.Log(FSCreateLabel())
t.Log(PidLabel(1))
}

Expand Down