Allow user mount for verity fs
Allow user mounting a verity fs on an existing mount by specifying mount flags root_hash and lower_path. PiperOrigin-RevId: 366843846
This commit is contained in:
parent
58afd120d3
commit
e21a71bff1
|
@ -35,6 +35,7 @@ package verity
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
|
@ -105,6 +106,13 @@ var (
|
|||
verityMu sync.RWMutex
|
||||
)
|
||||
|
||||
// Mount option names for verityfs.
|
||||
const (
|
||||
moptLowerPath = "lower_path"
|
||||
moptRootHash = "root_hash"
|
||||
moptRootName = "root_name"
|
||||
)
|
||||
|
||||
// HashAlgorithm is a type specifying the algorithm used to hash the file
|
||||
// content.
|
||||
type HashAlgorithm int
|
||||
|
@ -171,6 +179,9 @@ type filesystem struct {
|
|||
// system.
|
||||
alg HashAlgorithm
|
||||
|
||||
// opts is the string mount options passed to opts.Data.
|
||||
opts string
|
||||
|
||||
// renameMu synchronizes renaming with non-renaming operations in order
|
||||
// to ensure consistent lock ordering between dentry.dirMu in different
|
||||
// dentries.
|
||||
|
@ -193,9 +204,6 @@ type filesystem struct {
|
|||
//
|
||||
// +stateify savable
|
||||
type InternalFilesystemOptions struct {
|
||||
// RootMerkleFileName is the name of the verity root Merkle tree file.
|
||||
RootMerkleFileName string
|
||||
|
||||
// LowerName is the name of the filesystem wrapped by verity fs.
|
||||
LowerName string
|
||||
|
||||
|
@ -203,9 +211,6 @@ type InternalFilesystemOptions struct {
|
|||
// system.
|
||||
Alg HashAlgorithm
|
||||
|
||||
// RootHash is the root hash of the overall verity file system.
|
||||
RootHash []byte
|
||||
|
||||
// AllowRuntimeEnable specifies whether the verity file system allows
|
||||
// enabling verification for files (i.e. building Merkle trees) during
|
||||
// runtime.
|
||||
|
@ -239,28 +244,99 @@ func alertIntegrityViolation(msg string) error {
|
|||
|
||||
// GetFilesystem implements vfs.FilesystemType.GetFilesystem.
|
||||
func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
|
||||
mopts := vfs.GenericParseMountOptions(opts.Data)
|
||||
var rootHash []byte
|
||||
if encodedRootHash, ok := mopts[moptRootHash]; ok {
|
||||
delete(mopts, moptRootHash)
|
||||
hash, err := hex.DecodeString(encodedRootHash)
|
||||
if err != nil {
|
||||
ctx.Warningf("verity.FilesystemType.GetFilesystem: Failed to decode root hash: %v", err)
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
rootHash = hash
|
||||
}
|
||||
var lowerPathname string
|
||||
if path, ok := mopts[moptLowerPath]; ok {
|
||||
delete(mopts, moptLowerPath)
|
||||
lowerPathname = path
|
||||
}
|
||||
rootName := "root"
|
||||
if root, ok := mopts[moptRootName]; ok {
|
||||
delete(mopts, moptRootName)
|
||||
rootName = root
|
||||
}
|
||||
|
||||
// Check for unparsed options.
|
||||
if len(mopts) != 0 {
|
||||
ctx.Warningf("verity.FilesystemType.GetFilesystem: unknown options: %v", mopts)
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
// Handle internal options.
|
||||
iopts, ok := opts.InternalData.(InternalFilesystemOptions)
|
||||
if !ok {
|
||||
if len(lowerPathname) == 0 && !ok {
|
||||
ctx.Warningf("verity.FilesystemType.GetFilesystem: missing verity configs")
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
if len(lowerPathname) != 0 {
|
||||
if ok {
|
||||
ctx.Warningf("verity.FilesystemType.GetFilesystem: unexpected verity configs with specified lower path")
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
iopts = InternalFilesystemOptions{
|
||||
AllowRuntimeEnable: len(rootHash) == 0,
|
||||
Action: ErrorOnViolation,
|
||||
}
|
||||
}
|
||||
action = iopts.Action
|
||||
|
||||
// Mount the lower file system. The lower file system is wrapped inside
|
||||
// verity, and should not be exposed or connected.
|
||||
mopts := &vfs.MountOptions{
|
||||
GetFilesystemOptions: iopts.LowerGetFSOptions,
|
||||
InternalMount: true,
|
||||
}
|
||||
mnt, err := vfsObj.MountDisconnected(ctx, creds, "", iopts.LowerName, mopts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
var lowerMount *vfs.Mount
|
||||
var mountedLowerVD vfs.VirtualDentry
|
||||
// Use an existing mount if lowerPath is provided.
|
||||
if len(lowerPathname) != 0 {
|
||||
vfsroot := vfs.RootFromContext(ctx)
|
||||
if vfsroot.Ok() {
|
||||
defer vfsroot.DecRef(ctx)
|
||||
}
|
||||
lowerPath := fspath.Parse(lowerPathname)
|
||||
if !lowerPath.Absolute {
|
||||
ctx.Infof("verity.FilesystemType.GetFilesystem: lower_path %q must be absolute", lowerPathname)
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
var err error
|
||||
mountedLowerVD, err = vfsObj.GetDentryAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: vfsroot,
|
||||
Start: vfsroot,
|
||||
Path: lowerPath,
|
||||
FollowFinalSymlink: true,
|
||||
}, &vfs.GetDentryOptions{
|
||||
CheckSearchable: true,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Infof("verity.FilesystemType.GetFilesystem: failed to resolve lower_path %q: %v", lowerPathname, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
lowerMount = mountedLowerVD.Mount()
|
||||
defer mountedLowerVD.DecRef(ctx)
|
||||
} else {
|
||||
// Mount the lower file system. The lower file system is wrapped inside
|
||||
// verity, and should not be exposed or connected.
|
||||
mountOpts := &vfs.MountOptions{
|
||||
GetFilesystemOptions: iopts.LowerGetFSOptions,
|
||||
InternalMount: true,
|
||||
}
|
||||
mnt, err := vfsObj.MountDisconnected(ctx, creds, "", iopts.LowerName, mountOpts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
lowerMount = mnt
|
||||
}
|
||||
|
||||
fs := &filesystem{
|
||||
creds: creds.Fork(),
|
||||
alg: iopts.Alg,
|
||||
lowerMount: mnt,
|
||||
lowerMount: lowerMount,
|
||||
opts: opts.Data,
|
||||
allowRuntimeEnable: iopts.AllowRuntimeEnable,
|
||||
}
|
||||
fs.vfsfs.Init(vfsObj, &fstype, fs)
|
||||
|
@ -268,11 +344,11 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
|||
// Construct the root dentry.
|
||||
d := fs.newDentry()
|
||||
d.refs = 1
|
||||
lowerVD := vfs.MakeVirtualDentry(mnt, mnt.Root())
|
||||
lowerVD := vfs.MakeVirtualDentry(lowerMount, lowerMount.Root())
|
||||
lowerVD.IncRef()
|
||||
d.lowerVD = lowerVD
|
||||
|
||||
rootMerkleName := merkleRootPrefix + iopts.RootMerkleFileName
|
||||
rootMerkleName := merkleRootPrefix + rootName
|
||||
|
||||
lowerMerkleVD, err := vfsObj.GetDentryAt(ctx, fs.creds, &vfs.PathOperation{
|
||||
Root: lowerVD,
|
||||
|
@ -352,7 +428,7 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
|||
d.mode = uint32(stat.Mode)
|
||||
d.uid = stat.UID
|
||||
d.gid = stat.GID
|
||||
d.hash = make([]byte, len(iopts.RootHash))
|
||||
d.hash = make([]byte, len(rootHash))
|
||||
d.childrenNames = make(map[string]struct{})
|
||||
|
||||
if !d.isDir() {
|
||||
|
@ -427,7 +503,7 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
|||
}
|
||||
|
||||
d.hashMu.Lock()
|
||||
copy(d.hash, iopts.RootHash)
|
||||
copy(d.hash, rootHash)
|
||||
d.hashMu.Unlock()
|
||||
d.vfsd.Init(d)
|
||||
|
||||
|
@ -443,7 +519,7 @@ func (fs *filesystem) Release(ctx context.Context) {
|
|||
|
||||
// MountOptions implements vfs.FilesystemImpl.MountOptions.
|
||||
func (fs *filesystem) MountOptions() string {
|
||||
return ""
|
||||
return fs.opts
|
||||
}
|
||||
|
||||
// dentry implements vfs.DentryImpl.
|
||||
|
|
|
@ -89,10 +89,11 @@ func newVerityRoot(t *testing.T, hashAlg HashAlgorithm) (*vfs.VirtualFilesystem,
|
|||
AllowUserMount: true,
|
||||
})
|
||||
|
||||
data := "root_name=" + rootMerkleFilename
|
||||
mntns, err := vfsObj.NewMountNamespace(ctx, auth.CredentialsFromContext(ctx), "", "verity", &vfs.MountOptions{
|
||||
GetFilesystemOptions: vfs.GetFilesystemOptions{
|
||||
Data: data,
|
||||
InternalData: InternalFilesystemOptions{
|
||||
RootMerkleFileName: rootMerkleFilename,
|
||||
LowerName: "tmpfs",
|
||||
Alg: hashAlg,
|
||||
AllowRuntimeEnable: true,
|
||||
|
|
|
@ -92,7 +92,7 @@ func registerFilesystems(k *kernel.Kernel) error {
|
|||
})
|
||||
vfsObj.MustRegisterFilesystemType(verity.Name, &verity.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserList: true,
|
||||
AllowUserMount: false,
|
||||
AllowUserMount: true,
|
||||
})
|
||||
|
||||
// Setup files in devtmpfs.
|
||||
|
@ -483,7 +483,7 @@ func (c *containerMounter) getMountNameAndOptionsVFS2(conf *config.Config, m *mo
|
|||
var data []string
|
||||
var iopts interface{}
|
||||
|
||||
verityOpts, verityRequested, remainingMOpts, err := parseVerityMountOptions(m.Options)
|
||||
verityData, verityOpts, verityRequested, remainingMOpts, err := parseVerityMountOptions(m.Options)
|
||||
if err != nil {
|
||||
return "", nil, false, err
|
||||
}
|
||||
|
@ -555,13 +555,13 @@ func (c *containerMounter) getMountNameAndOptionsVFS2(conf *config.Config, m *mo
|
|||
}
|
||||
|
||||
if verityRequested {
|
||||
verityOpts.RootMerkleFileName = path.Base(m.Mount.Destination)
|
||||
verityData = verityData + "root_name=" + path.Base(m.Mount.Destination)
|
||||
verityOpts.LowerName = fsName
|
||||
verityOpts.LowerGetFSOptions = opts.GetFilesystemOptions
|
||||
fsName = verity.Name
|
||||
opts = &vfs.MountOptions{
|
||||
GetFilesystemOptions: vfs.GetFilesystemOptions{
|
||||
Data: strings.Join(data, ","),
|
||||
Data: verityData,
|
||||
InternalData: verityOpts,
|
||||
},
|
||||
InternalMount: true,
|
||||
|
@ -582,9 +582,10 @@ func parseKeyValue(s string) (string, string, bool) {
|
|||
// parseAndFilterOptions scans the provided mount options for verity-related
|
||||
// mount options. It returns the parsed set of verity mount options, as well as
|
||||
// the filtered set of mount options unrelated to verity.
|
||||
func parseVerityMountOptions(mopts []string) (verity.InternalFilesystemOptions, bool, []string, error) {
|
||||
func parseVerityMountOptions(mopts []string) (string, verity.InternalFilesystemOptions, bool, []string, error) {
|
||||
nonVerity := []string{}
|
||||
found := false
|
||||
var rootHash string
|
||||
verityOpts := verity.InternalFilesystemOptions{
|
||||
Action: verity.PanicOnViolation,
|
||||
}
|
||||
|
@ -596,13 +597,13 @@ func parseVerityMountOptions(mopts []string) (verity.InternalFilesystemOptions,
|
|||
|
||||
k, v, ok := parseKeyValue(o)
|
||||
if !ok {
|
||||
return verityOpts, found, nonVerity, fmt.Errorf("invalid verity mount option with no value: %q", o)
|
||||
return "", verityOpts, found, nonVerity, fmt.Errorf("invalid verity mount option with no value: %q", o)
|
||||
}
|
||||
|
||||
found = true
|
||||
switch k {
|
||||
case "verity.roothash":
|
||||
verityOpts.RootHash = []byte(v)
|
||||
rootHash = v
|
||||
case "verity.action":
|
||||
switch v {
|
||||
case "error":
|
||||
|
@ -614,11 +615,12 @@ func parseVerityMountOptions(mopts []string) (verity.InternalFilesystemOptions,
|
|||
verityOpts.Action = verity.PanicOnViolation
|
||||
}
|
||||
default:
|
||||
return verityOpts, found, nonVerity, fmt.Errorf("unknown verity mount option: %q", k)
|
||||
return "", verityOpts, found, nonVerity, fmt.Errorf("unknown verity mount option: %q", k)
|
||||
}
|
||||
}
|
||||
verityOpts.AllowRuntimeEnable = len(verityOpts.RootHash) == 0
|
||||
return verityOpts, found, nonVerity, nil
|
||||
verityOpts.AllowRuntimeEnable = len(rootHash) == 0
|
||||
verityData := "root_hash=" + rootHash + ","
|
||||
return verityData, verityOpts, found, nonVerity, nil
|
||||
}
|
||||
|
||||
// mountTmpVFS2 mounts an internal tmpfs at '/tmp' if it's safe to do so.
|
||||
|
|
|
@ -317,6 +317,10 @@ syscall_test(
|
|||
test = "//test/syscalls/linux:mount_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
test = "//test/syscalls/linux:verity_mount_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "medium",
|
||||
test = "//test/syscalls/linux:mremap_test",
|
||||
|
|
|
@ -1303,6 +1303,20 @@ cc_binary(
|
|||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "verity_mount_test",
|
||||
testonly = 1,
|
||||
srcs = ["verity_mount.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
gtest,
|
||||
"//test/util:capability_util",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "mremap_test",
|
||||
testonly = 1,
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/util/capability_util.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// Mount verity file system on an existing gofer mount.
|
||||
TEST(MountTest, MountExisting) {
|
||||
// Verity is implemented in VFS2.
|
||||
SKIP_IF(IsRunningWithVFS1());
|
||||
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
// Mount a new tmpfs file system.
|
||||
auto const tmpfs_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
ASSERT_THAT(mount("", tmpfs_dir.path().c_str(), "tmpfs", 0, ""),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Mount a verity file system on the existing gofer mount.
|
||||
auto const verity_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
std::string opts = "lower_path=" + tmpfs_dir.path();
|
||||
EXPECT_THAT(mount("", verity_dir.path().c_str(), "verity", 0, opts.c_str()),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
Loading…
Reference in New Issue