From dd2bcfc6438e51ca8eae7f72543fcfd7034cdd39 Mon Sep 17 00:00:00 2001 From: Daniel De Graaf Date: Thu, 9 Jul 2026 13:53:32 -0400 Subject: [PATCH] fsmount: Fix selinux mount parameter parsing Because selinux contexts can contain commas, context strings may be quoted in the single-syscall mount API. This quoting is not permitted when using the fsconfig API, so strip the quotes when preparing the system call arguments. Signed-off-by: Daniel De Graaf --- core/mount/mount.go | 6 +++ internal/fsmount/fsmount_linux.go | 8 +++ internal/fsmount/fsmount_linux_test.go | 73 ++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 internal/fsmount/fsmount_linux_test.go diff --git a/core/mount/mount.go b/core/mount/mount.go index 0b7d8e8f87..58c3c57b78 100644 --- a/core/mount/mount.go +++ b/core/mount/mount.go @@ -43,6 +43,12 @@ type Mount struct { Target string // Options contains zero or more fstab-style mount options. Typically, // these are platform specific. + // + // These options are formatted as required for passing to mount(8) or + // the legacy mount(2) API after joining with ",", so some values may + // have option-specific quoting or escaping applied. For example, + // SELinux contexts (which can contain commas) are quoted, and + // overlayfs uses backslash escaping on paths. Options []string } diff --git a/internal/fsmount/fsmount_linux.go b/internal/fsmount/fsmount_linux.go index 3acbf1908c..c0fd9bb2c8 100644 --- a/internal/fsmount/fsmount_linux.go +++ b/internal/fsmount/fsmount_linux.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "slices" + "strconv" "strings" "golang.org/x/sys/unix" @@ -115,6 +116,13 @@ func Fsmount(m mount.Mount, target string) error { // Handle key=value options if key, val, ok := strings.Cut(o, "="); ok { + switch key { + // Remove quoting from selinux parameters. + case "context", "fscontext", "defcontext", "rootcontext": + if nval, err := strconv.Unquote(val); err == nil { + val = nval + } + } if err := unix.FsconfigSetString(int(fsctx.Fd()), key, val); err != nil { return fmt.Errorf("failed to set string option %s=%s: %w", key, val, err) } diff --git a/internal/fsmount/fsmount_linux_test.go b/internal/fsmount/fsmount_linux_test.go new file mode 100644 index 0000000000..f2b64cadb4 --- /dev/null +++ b/internal/fsmount/fsmount_linux_test.go @@ -0,0 +1,73 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package fsmount_test + +import ( + "fmt" + "testing" + + "github.com/containerd/containerd/v2/core/mount" + "github.com/containerd/containerd/v2/internal/fsmount" + "github.com/containerd/containerd/v2/pkg/testutil" + + "github.com/opencontainers/selinux/go-selinux" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Test mounts with SELinux context specified, with and without fsconfig +func TestMountSelinuxContext(t *testing.T) { + testutil.RequiresRoot(t) + + if !selinux.GetEnabled() { + t.Skip("selinux is not enabled") + } + + if !fsmount.SupportsFsmount() { + t.Skip("fsmount is not available") + } + + tmp := t.TempDir() + + // Reuse the label of tmp for the test so we don't depend on a specific policy + label, err := selinux.FileLabel(tmp) + require.NoError(t, err) + + ml := fmt.Sprintf("context=%q", label) + + mnt := mount.Mount{ + Type: "tmpfs", + Options: []string{"mode=0700", ml}, + } + + err = mnt.Mount(tmp) + t.Cleanup(func() { + testutil.Unmount(t, tmp) + }) + require.NoError(t, err) + + newlabel, err := selinux.FileLabel(tmp) + require.NoError(t, err) + assert.Equal(t, label, newlabel) + + err = fsmount.Fsmount(mnt, tmp) + require.NoError(t, err) + + newlabel, err = selinux.FileLabel(tmp) + require.NoError(t, err) + assert.Equal(t, label, newlabel) +}