diff --git a/pkg/abi/linux/capability.go b/pkg/abi/linux/capability.go index 213815bc7..02c0269fb 100644 --- a/pkg/abi/linux/capability.go +++ b/pkg/abi/linux/capability.go @@ -227,6 +227,31 @@ const ( HighestCapabilityVersion = LINUX_CAPABILITY_VERSION_3 ) +// Constants that are used by file capability extended attributes, defined +// in Linux's include/uapi/linux/capability.h. +const ( + // VFS_CAP_REVISION_1 was the original file capability implementation, + // which supported 32-bit masks for file capabilities. + VFS_CAP_REVISION_1 = 0x01000000 + // VFS_CAP_REVISION_2 allows for file capability masks that are 64 + // bits in size, and was necessary as the number of supported + // capabilities grew beyond 32. + VFS_CAP_REVISION_2 = 0x02000000 + // VFS_CAP_REVISION_3 are provided to support namespaced file capabilities. + // As with version 2 file capabilities, version 3 capability + // masks are 64 bits in size. But in addition, the root user + // ID of namespace is encoded in the security.capability + // extended attribute. + VFS_CAP_REVISION_3 = 0x03000000 + VFS_CAP_REVISION_MASK = 0xFF000000 + // The encoded VFS_CAP_REVISION_1 data's number of bytes. + XATTR_CAPS_SZ_1 = 12 + // The encoded VFS_CAP_REVISION_2 data's number of bytes. + XATTR_CAPS_SZ_2 = 20 + // The encoded VFS_CAP_REVISION_3 data's number of bytes. + XATTR_CAPS_SZ_3 = 24 +) + // CapUserHeader is equivalent to Linux's cap_user_header_t. // // +marshal diff --git a/pkg/sentry/kernel/auth/BUILD b/pkg/sentry/kernel/auth/BUILD index 2d5fc988f..3aeb779a3 100644 --- a/pkg/sentry/kernel/auth/BUILD +++ b/pkg/sentry/kernel/auth/BUILD @@ -1,5 +1,5 @@ load("//pkg/sync/locking:locking.bzl", "declare_mutex", "declare_rwmutex") -load("//tools:defs.bzl", "go_library") +load("//tools:defs.bzl", "go_library", "go_test") load("//tools/go_generics:defs.bzl", "go_template_instance") package( @@ -102,3 +102,10 @@ go_library( "//pkg/sync/locking", ], ) + +go_test( + name = "auth_test", + srcs = ["capability_set_test.go"], + library = ":auth", + deps = ["//pkg/abi/linux"], +) diff --git a/pkg/sentry/kernel/auth/capability_set.go b/pkg/sentry/kernel/auth/capability_set.go index fc8c6745c..1ebfbe39b 100644 --- a/pkg/sentry/kernel/auth/capability_set.go +++ b/pkg/sentry/kernel/auth/capability_set.go @@ -15,6 +15,9 @@ package auth import ( + "encoding/binary" + "fmt" + "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/bits" ) @@ -23,6 +26,15 @@ import ( // value of CapabilitySet is a set containing no capabilities. type CapabilitySet uint64 +// VfsCapData is equivalent to Linux's cpu_vfs_cap_data, defined +// in Linux's include/linux/capability.h. +type VfsCapData struct { + MagicEtc uint32 + RootID uint32 + Permitted CapabilitySet + Inheritable CapabilitySet +} + // AllCapabilities is a CapabilitySet containing all valid capabilities. var AllCapabilities = CapabilitySetOf(linux.CAP_LAST_CAP+1) - 1 @@ -41,6 +53,38 @@ func CapabilitySetOfMany(cps []linux.Capability) CapabilitySet { return CapabilitySet(cs) } +// VfsCapDataOf returns a VfsCapData containing the file capabilities for the given slice of bytes. +// For each field of the cap data, which are in the structure of either vfs_cap_data or vfs_ns_cap_data, +// the bytes are ordered in little endian. +func VfsCapDataOf(data []byte) (VfsCapData, error) { + var capData VfsCapData + size := len(data) + if size < linux.XATTR_CAPS_SZ_1 { + return capData, fmt.Errorf("the size of security.capability is too small, actual size: %v", size) + } + capData.MagicEtc = binary.LittleEndian.Uint32(data[:4]) + capData.Permitted = CapabilitySet(binary.LittleEndian.Uint32(data[4:8])) + capData.Inheritable = CapabilitySet(binary.LittleEndian.Uint32(data[8:12])) + // The version of the file capabilities takes first 4 bytes of the given + // slice. + version := capData.MagicEtc & linux.VFS_CAP_REVISION_MASK + switch { + case version == linux.VFS_CAP_REVISION_3 && size == linux.XATTR_CAPS_SZ_3: + // Like version 2 file capabilities, version 3 capability + // masks are 64 bits in size. In addition, version 3 has + // the root user ID of namespace, which is encoded in the + // security.capability extended attribute. + capData.RootID = binary.LittleEndian.Uint32(data[20:24]) + fallthrough + case version == linux.VFS_CAP_REVISION_2 && size == linux.XATTR_CAPS_SZ_2: + capData.Permitted += CapabilitySet(binary.LittleEndian.Uint32(data[12:16])) << 32 + capData.Inheritable += CapabilitySet(binary.LittleEndian.Uint32(data[16:20])) << 32 + default: + return VfsCapData{}, fmt.Errorf("VFS_CAP_REVISION_%v with cap data size %v is not supported", version, size) + } + return capData, nil +} + // TaskCapabilities represents all the capability sets for a task. Each of these // sets is explained in greater detail in capabilities(7). type TaskCapabilities struct { diff --git a/pkg/sentry/kernel/auth/capability_set_test.go b/pkg/sentry/kernel/auth/capability_set_test.go new file mode 100644 index 000000000..9bfa45858 --- /dev/null +++ b/pkg/sentry/kernel/auth/capability_set_test.go @@ -0,0 +1,85 @@ +// Copyright 2024 The gVisor 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 auth + +import ( + "fmt" + "testing" + + "gvisor.dev/gvisor/pkg/abi/linux" +) + +func TestVfsCapData(t *testing.T) { + for _, tst := range []struct { + name string + data []byte + capData VfsCapData + wantErr error + }{ + { + name: "VfsCapRevision1", + data: []byte{0, 0, 0, 1, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + capData: VfsCapData{}, + wantErr: fmt.Errorf("VFS_CAP_REVISION_%v with cap data size %v is not supported", 0x1000000, 20), + }, + { + name: "VfsCapRevision2", + data: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0}, + capData: VfsCapData{ + MagicEtc: 0x2000001, + Permitted: CapabilitySetOf(linux.CAP_NET_RAW), + Inheritable: CapabilitySetOf(linux.CAP_SYSLOG), + }, + wantErr: nil, + }, + { + name: "VfsCapRevision3", + data: []byte{0, 0, 0, 3, 0, 0, 0, 0, 0, 16, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}, + capData: VfsCapData{ + MagicEtc: 0x3000000, + RootID: 1, + Permitted: CapabilitySetOf(linux.CAP_SYSLOG), + Inheritable: CapabilitySetOf(linux.CAP_NET_ADMIN), + }, + wantErr: nil, + }, + { + name: "VfsCapRevisionNotSupported", + data: []byte{0, 0, 0, 0xf, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0}, + capData: VfsCapData{}, + wantErr: fmt.Errorf("VFS_CAP_REVISION_%v with cap data size %v is not supported", 0xf000000, 20), + }, + { + name: "VfsInvalidInput", + data: []byte{0, 0, 0, 0}, + capData: VfsCapData{}, + wantErr: fmt.Errorf("the size of security.capability is too small, actual size: %v", 4), + }, + } { + t.Run(tst.name, func(t *testing.T) { + capData, err := VfsCapDataOf(tst.data) + if err == nil { + if tst.wantErr != nil { + t.Errorf("VfsCapDataOf(%v) returned unexpected error %v", tst.data, tst.wantErr) + } + if tst.capData != capData { + t.Errorf("VfsCapDataOf(%v) = %v, want %v", tst.data, capData, tst.capData) + } + } else if tst.wantErr == nil || tst.wantErr.Error() != err.Error() { + t.Errorf("VfsCapDataOf(%v) returned error %v, wantErr: %v", tst.data, err, tst.wantErr) + } + }) + } +}