From 8889f6ec5dfbc92be63ff81f67cce3f1e7f8567a Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 12 May 2026 14:28:23 -0700 Subject: [PATCH] Add SetProcessKind All (open source) users of KVMContainerLabel[s] and InitContainerLabel[s] (containerd, podman, and cri-o) are immediately releasing the acquired MCS. They only need the type field, so they can change the existing label to that type. Everything else, including the just-generated unique MCS label, is not used. Introduce SetProcessKind which does just what all those users need. Signed-off-by: Kir Kolyshkin --- go-selinux/selinux.go | 19 ++++++++++ go-selinux/selinux_linux.go | 43 ++++++++++++++++++++++ go-selinux/selinux_linux_test.go | 63 ++++++++++++++++++++++++++++++++ go-selinux/selinux_stub.go | 4 ++ go-selinux/selinux_stub_test.go | 3 ++ 5 files changed, 132 insertions(+) diff --git a/go-selinux/selinux.go b/go-selinux/selinux.go index 1935bf6..06b4aca 100644 --- a/go-selinux/selinux.go +++ b/go-selinux/selinux.go @@ -48,6 +48,21 @@ var ( 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 @@ -292,6 +307,8 @@ func KVMContainerLabels() (string, string) { // 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() } @@ -306,6 +323,8 @@ func InitContainerLabels() (string, string) { // 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() } diff --git a/go-selinux/selinux_linux.go b/go-selinux/selinux_linux.go index 2117155..f238b19 100644 --- a/go-selinux/selinux_linux.go +++ b/go-selinux/selinux_linux.go @@ -1513,3 +1513,46 @@ func getDefaultContextWithLevel(user, level, scon string) (string, error) { 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 + } + + // Replace cLabel type with one from src. + srcCtx, err := newContext(src) + if err != nil { + return "", fmt.Errorf("selinux.SetProcessKind: invalid %s label %s: %w", primary, src, err) + } + dstCtx, err := newContext(cLabel) + if err != nil { + return "", fmt.Errorf("selinux.SetProcessKind: invalid label %s: %w", cLabel, err) + } + + dstCtx["type"] = srcCtx["type"] + return dstCtx.get(), nil +} diff --git a/go-selinux/selinux_linux_test.go b/go-selinux/selinux_linux_test.go index 9d82d09..2fc1c92 100644 --- a/go-selinux/selinux_linux_test.go +++ b/go-selinux/selinux_linux_test.go @@ -84,6 +84,69 @@ func TestSetFileLabel(t *testing.T) { } } +func TestSetProcessKind(t *testing.T) { + // These cases do not depend on SELinux being enabled. + t.Run("empty label", func(t *testing.T) { + out, err := SetProcessKind("", ProcessKindRegular) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if out != "" { + t.Errorf("got %q, want empty string", out) + } + }) + + t.Run("invalid ProcessKind", func(t *testing.T) { + if _, err := SetProcessKind("system_u:system_r:container_t:s0", ProcessKind(0)); err == nil { + t.Error("expected error for ProcessKind(0), got nil") + } + if _, err := SetProcessKind("system_u:system_r:container_t:s0", ProcessKind(42)); err == nil { + t.Error("expected error for ProcessKind(42), got nil") + } + }) + + if !GetEnabled() { + t.Skip("SELinux not enabled, skipping policy-dependent cases") + } + + t.Run("invalid input label", func(t *testing.T) { + if _, err := SetProcessKind("not-a-valid-label", ProcessKindRegular); err == nil { + t.Error("expected error for malformed label, got nil") + } + }) + + t.Run("ProcessKindKVM", func(t *testing.T) { + // Pick a base label we can mutate. Use the process label from policy as a + // donor for user/role/level, but swap its type with something distinct so we + // can observe SetProcessKind replacing it. + base := label("process") + if base == "" { + t.Skip("no process label in policy, skipping.") + } + baseCtx, err := NewContext(base) + if err != nil { + t.Fatalf("NewContext(%q): %v", base, err) + } + baseCtx["type"] = "foo_bar_baz_t" + if baseCtx["level"] == "" { + baseCtx["level"] = "s0" + } + input := baseCtx.Get() + res, err := SetProcessKind(input, ProcessKindKVM) + t.Logf("SetProcessKind(%q): %q", input, res) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if _, err := CanonicalizeContext(res); err != nil { + t.Error(err) + } + // Check the type has changed. + if strings.Contains(res, baseCtx["type"]) { + t.Errorf("want %q to not contain %q", res, baseCtx["type"]) + } + }) +} + func TestKVMContainerLabel(t *testing.T) { if !GetEnabled() { t.Skip("SELinux not enabled, skipping.") diff --git a/go-selinux/selinux_stub.go b/go-selinux/selinux_stub.go index 78a4e1f..d01bf26 100644 --- a/go-selinux/selinux_stub.go +++ b/go-selinux/selinux_stub.go @@ -157,3 +157,7 @@ func getDefaultContextWithLevel(string, string, string) (string, error) { func label(_ string) string { return "" } + +func setProcessKind(string, ProcessKind) (string, error) { + return "", nil +} diff --git a/go-selinux/selinux_stub_test.go b/go-selinux/selinux_stub_test.go index 192bebf..dcd9d11 100644 --- a/go-selinux/selinux_stub_test.go +++ b/go-selinux/selinux_stub_test.go @@ -120,4 +120,7 @@ func TestSELinuxStubs(t *testing.T) { if _, err = CopyLevel("foo", "bar"); err != nil { t.Error(err) } + if _, err = SetProcessKind("", ProcessKindRegular); err != nil { + t.Error(err) + } }