Add a function to decode file capabilities extended attributes.

The attributes are usually retrieved via API like Fgetxattr,
Getxattr in the form of a slice of bytes.

PiperOrigin-RevId: 605466585
This commit is contained in:
Jing Chen
2024-02-08 16:57:36 -08:00
committed by gVisor bot
parent cdededb792
commit a4c27c0722
4 changed files with 162 additions and 1 deletions
+25
View File
@@ -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
+8 -1
View File
@@ -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"],
)
+44
View File
@@ -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 {
@@ -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)
}
})
}
}