summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fsimpl/verity
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/fsimpl/verity')
-rw-r--r--pkg/sentry/fsimpl/verity/BUILD68
-rw-r--r--pkg/sentry/fsimpl/verity/dentry_list.go221
-rw-r--r--pkg/sentry/fsimpl/verity/verity_state_autogen.go316
-rw-r--r--pkg/sentry/fsimpl/verity/verity_test.go1211
4 files changed, 537 insertions, 1279 deletions
diff --git a/pkg/sentry/fsimpl/verity/BUILD b/pkg/sentry/fsimpl/verity/BUILD
deleted file mode 100644
index c12abdf33..000000000
--- a/pkg/sentry/fsimpl/verity/BUILD
+++ /dev/null
@@ -1,68 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-licenses(["notice"])
-
-go_template_instance(
- name = "dentry_list",
- out = "dentry_list.go",
- package = "verity",
- prefix = "dentry",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*dentry",
- "Linker": "*dentry",
- },
-)
-
-go_library(
- name = "verity",
- srcs = [
- "dentry_list.go",
- "filesystem.go",
- "save_restore.go",
- "verity.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/hostarch",
- "//pkg/marshal/primitive",
- "//pkg/merkletree",
- "//pkg/refsvfs2",
- "//pkg/safemem",
- "//pkg/sentry/arch",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/memmap",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/vfs",
- "//pkg/sync",
- "//pkg/usermem",
- ],
-)
-
-go_test(
- name = "verity_test",
- srcs = [
- "verity_test.go",
- ],
- library = ":verity",
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/errors/linuxerr",
- "//pkg/fspath",
- "//pkg/sentry/arch",
- "//pkg/sentry/fsimpl/testutil",
- "//pkg/sentry/fsimpl/tmpfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "//pkg/usermem",
- ],
-)
diff --git a/pkg/sentry/fsimpl/verity/dentry_list.go b/pkg/sentry/fsimpl/verity/dentry_list.go
new file mode 100644
index 000000000..ace6086b7
--- /dev/null
+++ b/pkg/sentry/fsimpl/verity/dentry_list.go
@@ -0,0 +1,221 @@
+package verity
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type dentryElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (dentryElementMapper) linkerFor(elem *dentry) *dentry { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type dentryList struct {
+ head *dentry
+ tail *dentry
+}
+
+// Reset resets list l to the empty state.
+func (l *dentryList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *dentryList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Front() *dentry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *dentryList) Back() *dentry {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *dentryList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (dentryElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *dentryList) PushFront(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ dentryElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *dentryList) PushBack(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *dentryList) PushBackList(m *dentryList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ dentryElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ dentryElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *dentryList) InsertAfter(b, e *dentry) {
+ bLinker := dentryElementMapper{}.linkerFor(b)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ dentryElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *dentryList) InsertBefore(a, e *dentry) {
+ aLinker := dentryElementMapper{}.linkerFor(a)
+ eLinker := dentryElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ dentryElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *dentryList) Remove(e *dentry) {
+ linker := dentryElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ dentryElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ dentryElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else if l.tail == e {
+ l.tail = prev
+ }
+
+ linker.SetNext(nil)
+ linker.SetPrev(nil)
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type dentryEntry struct {
+ next *dentry
+ prev *dentry
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Next() *dentry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) Prev() *dentry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetNext(elem *dentry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *dentryEntry) SetPrev(elem *dentry) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fsimpl/verity/verity_state_autogen.go b/pkg/sentry/fsimpl/verity/verity_state_autogen.go
new file mode 100644
index 000000000..302823121
--- /dev/null
+++ b/pkg/sentry/fsimpl/verity/verity_state_autogen.go
@@ -0,0 +1,316 @@
+// automatically generated by stateify.
+
+package verity
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (l *dentryList) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.dentryList"
+}
+
+func (l *dentryList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *dentryList) beforeSave() {}
+
+// +checklocksignore
+func (l *dentryList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *dentryList) afterLoad() {}
+
+// +checklocksignore
+func (l *dentryList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *dentryEntry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.dentryEntry"
+}
+
+func (e *dentryEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *dentryEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *dentryEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *dentryEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func (fstype *FilesystemType) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.FilesystemType"
+}
+
+func (fstype *FilesystemType) StateFields() []string {
+ return []string{}
+}
+
+func (fstype *FilesystemType) beforeSave() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateSave(stateSinkObject state.Sink) {
+ fstype.beforeSave()
+}
+
+func (fstype *FilesystemType) afterLoad() {}
+
+// +checklocksignore
+func (fstype *FilesystemType) StateLoad(stateSourceObject state.Source) {
+}
+
+func (fs *filesystem) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.filesystem"
+}
+
+func (fs *filesystem) StateFields() []string {
+ return []string{
+ "vfsfs",
+ "creds",
+ "allowRuntimeEnable",
+ "lowerMount",
+ "rootDentry",
+ "alg",
+ "action",
+ "opts",
+ "cachedDentries",
+ "cachedDentriesLen",
+ "maxCachedDentries",
+ "released",
+ }
+}
+
+func (fs *filesystem) beforeSave() {}
+
+// +checklocksignore
+func (fs *filesystem) StateSave(stateSinkObject state.Sink) {
+ fs.beforeSave()
+ stateSinkObject.Save(0, &fs.vfsfs)
+ stateSinkObject.Save(1, &fs.creds)
+ stateSinkObject.Save(2, &fs.allowRuntimeEnable)
+ stateSinkObject.Save(3, &fs.lowerMount)
+ stateSinkObject.Save(4, &fs.rootDentry)
+ stateSinkObject.Save(5, &fs.alg)
+ stateSinkObject.Save(6, &fs.action)
+ stateSinkObject.Save(7, &fs.opts)
+ stateSinkObject.Save(8, &fs.cachedDentries)
+ stateSinkObject.Save(9, &fs.cachedDentriesLen)
+ stateSinkObject.Save(10, &fs.maxCachedDentries)
+ stateSinkObject.Save(11, &fs.released)
+}
+
+func (fs *filesystem) afterLoad() {}
+
+// +checklocksignore
+func (fs *filesystem) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fs.vfsfs)
+ stateSourceObject.Load(1, &fs.creds)
+ stateSourceObject.Load(2, &fs.allowRuntimeEnable)
+ stateSourceObject.Load(3, &fs.lowerMount)
+ stateSourceObject.Load(4, &fs.rootDentry)
+ stateSourceObject.Load(5, &fs.alg)
+ stateSourceObject.Load(6, &fs.action)
+ stateSourceObject.Load(7, &fs.opts)
+ stateSourceObject.Load(8, &fs.cachedDentries)
+ stateSourceObject.Load(9, &fs.cachedDentriesLen)
+ stateSourceObject.Load(10, &fs.maxCachedDentries)
+ stateSourceObject.Load(11, &fs.released)
+}
+
+func (i *InternalFilesystemOptions) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.InternalFilesystemOptions"
+}
+
+func (i *InternalFilesystemOptions) StateFields() []string {
+ return []string{
+ "LowerName",
+ "Alg",
+ "AllowRuntimeEnable",
+ "LowerGetFSOptions",
+ "Action",
+ }
+}
+
+func (i *InternalFilesystemOptions) beforeSave() {}
+
+// +checklocksignore
+func (i *InternalFilesystemOptions) StateSave(stateSinkObject state.Sink) {
+ i.beforeSave()
+ stateSinkObject.Save(0, &i.LowerName)
+ stateSinkObject.Save(1, &i.Alg)
+ stateSinkObject.Save(2, &i.AllowRuntimeEnable)
+ stateSinkObject.Save(3, &i.LowerGetFSOptions)
+ stateSinkObject.Save(4, &i.Action)
+}
+
+func (i *InternalFilesystemOptions) afterLoad() {}
+
+// +checklocksignore
+func (i *InternalFilesystemOptions) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &i.LowerName)
+ stateSourceObject.Load(1, &i.Alg)
+ stateSourceObject.Load(2, &i.AllowRuntimeEnable)
+ stateSourceObject.Load(3, &i.LowerGetFSOptions)
+ stateSourceObject.Load(4, &i.Action)
+}
+
+func (d *dentry) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.dentry"
+}
+
+func (d *dentry) StateFields() []string {
+ return []string{
+ "vfsd",
+ "refs",
+ "fs",
+ "mode",
+ "uid",
+ "gid",
+ "size",
+ "parent",
+ "name",
+ "children",
+ "childrenNames",
+ "childrenList",
+ "lowerVD",
+ "lowerMerkleVD",
+ "symlinkTarget",
+ "hash",
+ "cached",
+ "dentryEntry",
+ }
+}
+
+func (d *dentry) beforeSave() {}
+
+// +checklocksignore
+func (d *dentry) StateSave(stateSinkObject state.Sink) {
+ d.beforeSave()
+ stateSinkObject.Save(0, &d.vfsd)
+ stateSinkObject.Save(1, &d.refs)
+ stateSinkObject.Save(2, &d.fs)
+ stateSinkObject.Save(3, &d.mode)
+ stateSinkObject.Save(4, &d.uid)
+ stateSinkObject.Save(5, &d.gid)
+ stateSinkObject.Save(6, &d.size)
+ stateSinkObject.Save(7, &d.parent)
+ stateSinkObject.Save(8, &d.name)
+ stateSinkObject.Save(9, &d.children)
+ stateSinkObject.Save(10, &d.childrenNames)
+ stateSinkObject.Save(11, &d.childrenList)
+ stateSinkObject.Save(12, &d.lowerVD)
+ stateSinkObject.Save(13, &d.lowerMerkleVD)
+ stateSinkObject.Save(14, &d.symlinkTarget)
+ stateSinkObject.Save(15, &d.hash)
+ stateSinkObject.Save(16, &d.cached)
+ stateSinkObject.Save(17, &d.dentryEntry)
+}
+
+// +checklocksignore
+func (d *dentry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &d.vfsd)
+ stateSourceObject.Load(1, &d.refs)
+ stateSourceObject.Load(2, &d.fs)
+ stateSourceObject.Load(3, &d.mode)
+ stateSourceObject.Load(4, &d.uid)
+ stateSourceObject.Load(5, &d.gid)
+ stateSourceObject.Load(6, &d.size)
+ stateSourceObject.Load(7, &d.parent)
+ stateSourceObject.Load(8, &d.name)
+ stateSourceObject.Load(9, &d.children)
+ stateSourceObject.Load(10, &d.childrenNames)
+ stateSourceObject.Load(11, &d.childrenList)
+ stateSourceObject.Load(12, &d.lowerVD)
+ stateSourceObject.Load(13, &d.lowerMerkleVD)
+ stateSourceObject.Load(14, &d.symlinkTarget)
+ stateSourceObject.Load(15, &d.hash)
+ stateSourceObject.Load(16, &d.cached)
+ stateSourceObject.Load(17, &d.dentryEntry)
+ stateSourceObject.AfterLoad(d.afterLoad)
+}
+
+func (fd *fileDescription) StateTypeName() string {
+ return "pkg/sentry/fsimpl/verity.fileDescription"
+}
+
+func (fd *fileDescription) StateFields() []string {
+ return []string{
+ "vfsfd",
+ "FileDescriptionDefaultImpl",
+ "d",
+ "isDir",
+ "lowerFD",
+ "lowerMappable",
+ "merkleReader",
+ "merkleWriter",
+ "parentMerkleWriter",
+ "off",
+ }
+}
+
+func (fd *fileDescription) beforeSave() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateSave(stateSinkObject state.Sink) {
+ fd.beforeSave()
+ stateSinkObject.Save(0, &fd.vfsfd)
+ stateSinkObject.Save(1, &fd.FileDescriptionDefaultImpl)
+ stateSinkObject.Save(2, &fd.d)
+ stateSinkObject.Save(3, &fd.isDir)
+ stateSinkObject.Save(4, &fd.lowerFD)
+ stateSinkObject.Save(5, &fd.lowerMappable)
+ stateSinkObject.Save(6, &fd.merkleReader)
+ stateSinkObject.Save(7, &fd.merkleWriter)
+ stateSinkObject.Save(8, &fd.parentMerkleWriter)
+ stateSinkObject.Save(9, &fd.off)
+}
+
+func (fd *fileDescription) afterLoad() {}
+
+// +checklocksignore
+func (fd *fileDescription) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &fd.vfsfd)
+ stateSourceObject.Load(1, &fd.FileDescriptionDefaultImpl)
+ stateSourceObject.Load(2, &fd.d)
+ stateSourceObject.Load(3, &fd.isDir)
+ stateSourceObject.Load(4, &fd.lowerFD)
+ stateSourceObject.Load(5, &fd.lowerMappable)
+ stateSourceObject.Load(6, &fd.merkleReader)
+ stateSourceObject.Load(7, &fd.merkleWriter)
+ stateSourceObject.Load(8, &fd.parentMerkleWriter)
+ stateSourceObject.Load(9, &fd.off)
+}
+
+func init() {
+ state.Register((*dentryList)(nil))
+ state.Register((*dentryEntry)(nil))
+ state.Register((*FilesystemType)(nil))
+ state.Register((*filesystem)(nil))
+ state.Register((*InternalFilesystemOptions)(nil))
+ state.Register((*dentry)(nil))
+ state.Register((*fileDescription)(nil))
+}
diff --git a/pkg/sentry/fsimpl/verity/verity_test.go b/pkg/sentry/fsimpl/verity/verity_test.go
deleted file mode 100644
index af041bd50..000000000
--- a/pkg/sentry/fsimpl/verity/verity_test.go
+++ /dev/null
@@ -1,1211 +0,0 @@
-// Copyright 2020 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 verity
-
-import (
- "fmt"
- "io"
- "math/rand"
- "strconv"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/context"
- "gvisor.dev/gvisor/pkg/errors/linuxerr"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/sentry/arch"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/usermem"
-)
-
-const (
- // rootMerkleFilename is the name of the root Merkle tree file.
- rootMerkleFilename = "root.verity"
- // maxDataSize is the maximum data size of a test file.
- maxDataSize = 100000
-)
-
-var hashAlgs = []HashAlgorithm{SHA256, SHA512}
-
-func dentryFromVD(t *testing.T, vd vfs.VirtualDentry) *dentry {
- t.Helper()
- d, ok := vd.Dentry().Impl().(*dentry)
- if !ok {
- t.Fatalf("can't assert %T as a *dentry", vd)
- }
- return d
-}
-
-// dentryFromFD returns the dentry corresponding to fd.
-func dentryFromFD(t *testing.T, fd *vfs.FileDescription) *dentry {
- t.Helper()
- f, ok := fd.Impl().(*fileDescription)
- if !ok {
- t.Fatalf("can't assert %T as a *fileDescription", fd)
- }
- return f.d
-}
-
-// newVerityRoot creates a new verity mount, and returns the root. The
-// underlying file system is tmpfs. If the error is not nil, then cleanup
-// should be called when the root is no longer needed.
-func newVerityRoot(t *testing.T, hashAlg HashAlgorithm) (*vfs.VirtualFilesystem, vfs.VirtualDentry, context.Context, error) {
- t.Helper()
- k, err := testutil.Boot()
- if err != nil {
- t.Fatalf("testutil.Boot: %v", err)
- }
-
- ctx := k.SupervisorContext()
-
- rand.Seed(time.Now().UnixNano())
- vfsObj := &vfs.VirtualFilesystem{}
- if err := vfsObj.Init(ctx); err != nil {
- return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("VFS init: %v", err)
- }
-
- vfsObj.MustRegisterFilesystemType("verity", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
-
- vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
- AllowUserMount: true,
- })
-
- data := "root_name=" + rootMerkleFilename
- mntns, err := vfsObj.NewMountNamespace(ctx, auth.CredentialsFromContext(ctx), "", "verity", &vfs.MountOptions{
- GetFilesystemOptions: vfs.GetFilesystemOptions{
- Data: data,
- InternalData: InternalFilesystemOptions{
- LowerName: "tmpfs",
- Alg: hashAlg,
- AllowRuntimeEnable: true,
- Action: ErrorOnViolation,
- },
- },
- })
- if err != nil {
- return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("NewMountNamespace: %v", err)
- }
- root := mntns.Root()
- root.IncRef()
-
- // Use lowerRoot in the task as we modify the lower file system
- // directly in many tests.
- lowerRoot := root.Dentry().Impl().(*dentry).lowerVD
- tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- task, err := testutil.CreateTask(ctx, "name", tc, mntns, lowerRoot, lowerRoot)
- if err != nil {
- t.Fatalf("testutil.CreateTask: %v", err)
- }
-
- t.Cleanup(func() {
- root.DecRef(ctx)
- mntns.DecRef(ctx)
- })
- return vfsObj, root, task.AsyncContext(), nil
-}
-
-// openVerityAt opens a verity file.
-//
-// TODO(chongc): release reference from opening the file when done.
-func openVerityAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, vd vfs.VirtualDentry, path string, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) {
- return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: vd,
- Start: vd,
- Path: fspath.Parse(path),
- }, &vfs.OpenOptions{
- Flags: flags,
- Mode: mode,
- })
-}
-
-// openLowerAt opens the file in the underlying file system.
-//
-// TODO(chongc): release reference from opening the file when done.
-func (d *dentry) openLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) {
- return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(path),
- }, &vfs.OpenOptions{
- Flags: flags,
- Mode: mode,
- })
-}
-
-// openLowerMerkleAt opens the Merkle file in the underlying file system.
-//
-// TODO(chongc): release reference from opening the file when done.
-func (d *dentry) openLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, flags uint32, mode linux.FileMode) (*vfs.FileDescription, error) {
- return vfsObj.OpenAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerMerkleVD,
- Start: d.lowerMerkleVD,
- }, &vfs.OpenOptions{
- Flags: flags,
- Mode: mode,
- })
-}
-
-// mkdirLowerAt creates a directory in the underlying file system.
-func (d *dentry) mkdirLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string, mode linux.FileMode) error {
- return vfsObj.MkdirAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(path),
- }, &vfs.MkdirOptions{
- Mode: mode,
- })
-}
-
-// unlinkLowerAt deletes the file in the underlying file system.
-func (d *dentry) unlinkLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string) error {
- return vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(path),
- })
-}
-
-// unlinkLowerMerkleAt deletes the Merkle file in the underlying file system.
-func (d *dentry) unlinkLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, path string) error {
- return vfsObj.UnlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(merklePrefix + path),
- })
-}
-
-// renameLowerAt renames file name to newName in the underlying file system.
-func (d *dentry) renameLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, name string, newName string) error {
- return vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(name),
- }, &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(newName),
- }, &vfs.RenameOptions{})
-}
-
-// renameLowerMerkleAt renames Merkle file name to newName in the underlying
-// file system.
-func (d *dentry) renameLowerMerkleAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, name string, newName string) error {
- return vfsObj.RenameAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(merklePrefix + name),
- }, &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(merklePrefix + newName),
- }, &vfs.RenameOptions{})
-}
-
-// symlinkLowerAt creates a symbolic link at symlink referring to the given target
-// in the underlying filesystem.
-func (d *dentry) symlinkLowerAt(ctx context.Context, vfsObj *vfs.VirtualFilesystem, target, symlink string) error {
- return vfsObj.SymlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: d.lowerVD,
- Start: d.lowerVD,
- Path: fspath.Parse(symlink),
- }, target)
-}
-
-// newFileFD creates a new file in the verity mount, and returns the FD. The FD
-// points to a file that has random data generated.
-func newFileFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode linux.FileMode) (*vfs.FileDescription, int, error) {
- // Create the file in the underlying file system.
- lowerFD, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, filePath, linux.O_RDWR|linux.O_CREAT|linux.O_EXCL, linux.ModeRegular|mode)
- if err != nil {
- return nil, 0, err
- }
-
- // Generate random data to be written to the file.
- dataSize := rand.Intn(maxDataSize) + 1
- data := make([]byte, dataSize)
- rand.Read(data)
-
- // Write directly to the underlying FD, since verity FD is read-only.
- n, err := lowerFD.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
- if err != nil {
- return nil, 0, err
- }
-
- if n != int64(len(data)) {
- return nil, 0, fmt.Errorf("lowerFD.Write got write length %d, want %d", n, len(data))
- }
-
- lowerFD.DecRef(ctx)
-
- // Now open the verity file descriptor.
- fd, err := openVerityAt(ctx, vfsObj, root, filePath, linux.O_RDONLY, mode)
- return fd, dataSize, err
-}
-
-// newDirFD creates a new directory in the verity mount, and returns the FD.
-func newDirFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, dirPath string, mode linux.FileMode) (*vfs.FileDescription, error) {
- // Create the directory in the underlying file system.
- if err := dentryFromVD(t, root).mkdirLowerAt(ctx, vfsObj, dirPath, linux.ModeRegular|mode); err != nil {
- return nil, err
- }
- if _, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, dirPath, linux.O_RDONLY|linux.O_DIRECTORY, linux.ModeRegular|mode); err != nil {
- return nil, err
- }
- return openVerityAt(ctx, vfsObj, root, dirPath, linux.O_RDONLY|linux.O_DIRECTORY, mode)
-}
-
-// newEmptyFileFD creates a new empty file in the verity mount, and returns the FD.
-func newEmptyFileFD(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode linux.FileMode) (*vfs.FileDescription, error) {
- // Create the file in the underlying file system.
- _, err := dentryFromVD(t, root).openLowerAt(ctx, vfsObj, filePath, linux.O_RDWR|linux.O_CREAT|linux.O_EXCL, linux.ModeRegular|mode)
- if err != nil {
- return nil, err
- }
- // Now open the verity file descriptor.
- fd, err := openVerityAt(ctx, vfsObj, root, filePath, linux.O_RDONLY, mode)
- return fd, err
-}
-
-// flipRandomBit randomly flips a bit in the file represented by fd.
-func flipRandomBit(ctx context.Context, fd *vfs.FileDescription, size int) error {
- randomPos := int64(rand.Intn(size))
- byteToModify := make([]byte, 1)
- if _, err := fd.PRead(ctx, usermem.BytesIOSequence(byteToModify), randomPos, vfs.ReadOptions{}); err != nil {
- return fmt.Errorf("lowerFD.PRead: %v", err)
- }
- byteToModify[0] ^= 1
- if _, err := fd.PWrite(ctx, usermem.BytesIOSequence(byteToModify), randomPos, vfs.WriteOptions{}); err != nil {
- return fmt.Errorf("lowerFD.PWrite: %v", err)
- }
- return nil
-}
-
-func enableVerity(ctx context.Context, t *testing.T, fd *vfs.FileDescription) {
- t.Helper()
- var args arch.SyscallArguments
- args[1] = arch.SyscallArgument{Value: linux.FS_IOC_ENABLE_VERITY}
- if _, err := fd.Ioctl(ctx, nil /* uio */, args); err != nil {
- t.Fatalf("enable verity: %v", err)
- }
-}
-
-// TestOpen ensures that when a file is created, the corresponding Merkle tree
-// file and the root Merkle tree file exist.
-func TestOpen(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Ensure that the corresponding Merkle tree file is created.
- if _, err = dentryFromFD(t, fd).openLowerMerkleAt(ctx, vfsObj, linux.O_RDONLY, linux.ModeRegular); err != nil {
- t.Errorf("OpenAt Merkle tree file %s: %v", merklePrefix+filename, err)
- }
-
- // Ensure the root merkle tree file is created.
- if _, err = dentryFromVD(t, root).openLowerMerkleAt(ctx, vfsObj, linux.O_RDONLY, linux.ModeRegular); err != nil {
- t.Errorf("OpenAt root Merkle tree file %s: %v", merklePrefix+rootMerkleFilename, err)
- }
- }
-}
-
-// TestPReadUnmodifiedFileSucceeds ensures that pread from an untouched verity
-// file succeeds after enabling verity for it.
-func TestPReadUnmodifiedFileSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirm a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- buf := make([]byte, size)
- n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{})
- if err != nil && err != io.EOF {
- t.Fatalf("fd.PRead: %v", err)
- }
-
- if n != int64(size) {
- t.Errorf("fd.PRead got read length %d, want %d", n, size)
- }
- }
-}
-
-// TestReadUnmodifiedFileSucceeds ensures that read from an untouched verity
-// file succeeds after enabling verity for it.
-func TestReadUnmodifiedFileSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirm a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- buf := make([]byte, size)
- n, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
- if err != nil && err != io.EOF {
- t.Fatalf("fd.Read: %v", err)
- }
-
- if n != int64(size) {
- t.Errorf("fd.PRead got read length %d, want %d", n, size)
- }
- }
-}
-
-// TestReadUnmodifiedEmptyFileSucceeds ensures that read from an untouched empty verity
-// file succeeds after enabling verity for it.
-func TestReadUnmodifiedEmptyFileSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-empty-file"
- fd, err := newEmptyFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newEmptyFileFD: %v", err)
- }
-
- // Enable verity on the file and confirm a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- var buf []byte
- n, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
- if err != nil && err != io.EOF {
- t.Fatalf("fd.Read: %v", err)
- }
-
- if n != 0 {
- t.Errorf("fd.Read got read length %d, expected 0", n)
- }
- }
-}
-
-// TestReopenUnmodifiedFileSucceeds ensures that reopen an untouched verity file
-// succeeds after enabling verity for it.
-func TestReopenUnmodifiedFileSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirms a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- // Ensure reopening the verity enabled file succeeds.
- if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); err != nil {
- t.Errorf("reopen enabled file failed: %v", err)
- }
- }
-}
-
-// TestOpenNonexistentFile ensures that opening a nonexistent file does not
-// trigger verification failure, even if the parent directory is verified.
-func TestOpenNonexistentFile(t *testing.T) {
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirms a normal read succeeds.
- enableVerity(ctx, t, fd)
-
- // Enable verity on the parent directory.
- parentFD, err := openVerityAt(ctx, vfsObj, root, "", linux.O_RDONLY, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
- enableVerity(ctx, t, parentFD)
-
- // Ensure open an unexpected file in the parent directory fails with
- // ENOENT rather than verification failure.
- if _, err = openVerityAt(ctx, vfsObj, root, filename+"abc", linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.ENOENT, err) {
- t.Errorf("OpenAt unexpected error: %v", err)
- }
-}
-
-// TestPReadModifiedFileFails ensures that read from a modified verity file
-// fails.
-func TestPReadModifiedFileFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- // Open a new lowerFD that's read/writable.
- lowerFD, err := dentryFromFD(t, fd).openLowerAt(ctx, vfsObj, "", linux.O_RDWR, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
-
- if err := flipRandomBit(ctx, lowerFD, size); err != nil {
- t.Fatalf("flipRandomBit: %v", err)
- }
-
- // Confirm that read from the modified file fails.
- buf := make([]byte, size)
- if _, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{}); err == nil {
- t.Fatalf("fd.PRead succeeded, expected failure")
- }
- }
-}
-
-// TestReadModifiedFileFails ensures that read from a modified verity file
-// fails.
-func TestReadModifiedFileFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- // Open a new lowerFD that's read/writable.
- lowerFD, err := dentryFromFD(t, fd).openLowerAt(ctx, vfsObj, "", linux.O_RDWR, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
-
- if err := flipRandomBit(ctx, lowerFD, size); err != nil {
- t.Fatalf("flipRandomBit: %v", err)
- }
-
- // Confirm that read from the modified file fails.
- buf := make([]byte, size)
- if _, err := fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}); err == nil {
- t.Fatalf("fd.Read succeeded, expected failure")
- }
- }
-}
-
-// TestModifiedMerkleFails ensures that read from a verity file fails if the
-// corresponding Merkle tree file is modified.
-func TestModifiedMerkleFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, size, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- // Open a new lowerMerkleFD that's read/writable.
- lowerMerkleFD, err := dentryFromFD(t, fd).openLowerMerkleAt(ctx, vfsObj, linux.O_RDWR, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
-
- // Flip a random bit in the Merkle tree file.
- stat, err := lowerMerkleFD.Stat(ctx, vfs.StatOptions{})
- if err != nil {
- t.Errorf("lowerMerkleFD.Stat: %v", err)
- }
-
- if err := flipRandomBit(ctx, lowerMerkleFD, int(stat.Size)); err != nil {
- t.Fatalf("flipRandomBit: %v", err)
- }
-
- // Confirm that read from a file with modified Merkle tree fails.
- buf := make([]byte, size)
- if _, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0 /* offset */, vfs.ReadOptions{}); err == nil {
- t.Fatalf("fd.PRead succeeded with modified Merkle file")
- }
- }
-}
-
-// TestModifiedParentMerkleFails ensures that open a verity enabled file in a
-// verity enabled directory fails if the hashes related to the target file in
-// the parent Merkle tree file is modified.
-func TestModifiedParentMerkleFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- // Enable verity on the parent directory.
- parentFD, err := openVerityAt(ctx, vfsObj, root, "", linux.O_RDONLY, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
- enableVerity(ctx, t, parentFD)
-
- // Open a new lowerMerkleFD that's read/writable.
- parentLowerMerkleFD, err := dentryFromFD(t, fd).parent.openLowerMerkleAt(ctx, vfsObj, linux.O_RDWR, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt: %v", err)
- }
-
- // Flip a random bit in the parent Merkle tree file.
- // This parent directory contains only one child, so any random
- // modification in the parent Merkle tree should cause verification
- // failure when opening the child file.
- sizeString, err := parentLowerMerkleFD.GetXattr(ctx, &vfs.GetXattrOptions{
- Name: childrenOffsetXattr,
- Size: sizeOfStringInt32,
- })
- if err != nil {
- t.Fatalf("parentLowerMerkleFD.GetXattr: %v", err)
- }
- parentMerkleSize, err := strconv.Atoi(sizeString)
- if err != nil {
- t.Fatalf("Failed convert size to int: %v", err)
- }
- if err := flipRandomBit(ctx, parentLowerMerkleFD, parentMerkleSize); err != nil {
- t.Fatalf("flipRandomBit: %v", err)
- }
-
- parentLowerMerkleFD.DecRef(ctx)
-
- // Ensure reopening the verity enabled file fails.
- if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); err == nil {
- t.Errorf("OpenAt file with modified parent Merkle succeeded")
- }
- }
-}
-
-// TestUnmodifiedStatSucceeds ensures that stat of an untouched verity file
-// succeeds after enabling verity for it.
-func TestUnmodifiedStatSucceeds(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file and confirm that stat succeeds.
- enableVerity(ctx, t, fd)
- if _, err := fd.Stat(ctx, vfs.StatOptions{}); err != nil {
- t.Errorf("fd.Stat: %v", err)
- }
- }
-}
-
-// TestModifiedStatFails checks that getting stat for a file with modified stat
-// should fail.
-func TestModifiedStatFails(t *testing.T) {
- for _, alg := range hashAlgs {
- vfsObj, root, ctx, err := newVerityRoot(t, alg)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- lowerFD := fd.Impl().(*fileDescription).lowerFD
- // Change the stat of the underlying file, and check that stat fails.
- if err := lowerFD.SetStat(ctx, vfs.SetStatOptions{
- Stat: linux.Statx{
- Mask: uint32(linux.STATX_MODE),
- Mode: 0777,
- },
- }); err != nil {
- t.Fatalf("lowerFD.SetStat: %v", err)
- }
-
- if _, err := fd.Stat(ctx, vfs.StatOptions{}); err == nil {
- t.Errorf("fd.Stat succeeded when it should fail")
- }
- }
-}
-
-// TestOpenDeletedFileFails ensures that opening a deleted verity enabled file
-// and/or the corresponding Merkle tree file fails with the verity error.
-func TestOpenDeletedFileFails(t *testing.T) {
- testCases := []struct {
- name string
- // The original file is removed if changeFile is true.
- changeFile bool
- // The Merkle tree file is removed if changeMerkleFile is true.
- changeMerkleFile bool
- }{
- {
- name: "FileOnly",
- changeFile: true,
- changeMerkleFile: false,
- },
- {
- name: "MerkleOnly",
- changeFile: false,
- changeMerkleFile: true,
- },
- {
- name: "FileAndMerkle",
- changeFile: true,
- changeMerkleFile: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- if tc.changeFile {
- if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, filename); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
- if tc.changeMerkleFile {
- if err := dentryFromVD(t, root).unlinkLowerMerkleAt(ctx, vfsObj, filename); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
-
- // Ensure reopening the verity enabled file fails.
- if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Errorf("got OpenAt error: %v, expected EIO", err)
- }
- })
- }
-}
-
-// TestOpenRenamedFileFails ensures that opening a renamed verity enabled file
-// and/or the corresponding Merkle tree file fails with the verity error.
-func TestOpenRenamedFileFails(t *testing.T) {
- testCases := []struct {
- name string
- // The original file is renamed if changeFile is true.
- changeFile bool
- // The Merkle tree file is renamed if changeMerkleFile is true.
- changeMerkleFile bool
- }{
- {
- name: "FileOnly",
- changeFile: true,
- changeMerkleFile: false,
- },
- {
- name: "MerkleOnly",
- changeFile: false,
- changeMerkleFile: true,
- },
- {
- name: "FileAndMerkle",
- changeFile: true,
- changeMerkleFile: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- filename := "verity-test-file"
- fd, _, err := newFileFD(ctx, t, vfsObj, root, filename, 0644)
- if err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
-
- // Enable verity on the file.
- enableVerity(ctx, t, fd)
-
- newFilename := "renamed-test-file"
- if tc.changeFile {
- if err := dentryFromVD(t, root).renameLowerAt(ctx, vfsObj, filename, newFilename); err != nil {
- t.Fatalf("RenameAt: %v", err)
- }
- }
- if tc.changeMerkleFile {
- if err := dentryFromVD(t, root).renameLowerMerkleAt(ctx, vfsObj, filename, newFilename); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
-
- // Ensure reopening the verity enabled file fails.
- if _, err = openVerityAt(ctx, vfsObj, root, filename, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Errorf("got OpenAt error: %v, expected EIO", err)
- }
- })
- }
-}
-
-// TestUnmodifiedSymlinkFileReadSucceeds ensures that readlink() for an
-// unmodified verity enabled symlink succeeds.
-func TestUnmodifiedSymlinkFileReadSucceeds(t *testing.T) {
- testCases := []struct {
- name string
- // The symlink target is a directory.
- hasDirectoryTarget bool
- // The symlink target is a directory and contains a regular file which will be
- // used to test walking a symlink.
- testWalk bool
- }{
- {
- name: "RegularFileTarget",
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DirectoryTarget",
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "RegularFileInSymlinkDirectory",
- hasDirectoryTarget: true,
- testWalk: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.testWalk && !tc.hasDirectoryTarget {
- t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk")
- }
-
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- var target string
- if tc.hasDirectoryTarget {
- target = "verity-test-dir"
- if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newDirFD: %v", err)
- }
- } else {
- target = "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- if tc.testWalk {
- fileInTargetDirectory := target + "/" + "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- symlink := "verity-test-symlink"
- if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil {
- t.Fatalf("SymlinkAt: %v", err)
- }
-
- fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_NOFOLLOW, linux.ModeRegular)
-
- if err != nil {
- t.Fatalf("openVerityAt symlink: %v", err)
- }
-
- enableVerity(ctx, t, fd)
-
- if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(symlink),
- }); err != nil {
- t.Fatalf("ReadlinkAt: %v", err)
- }
-
- if tc.testWalk {
- fileInSymlinkDirectory := symlink + "/verity-test-file"
- // Ensure opening the verity enabled file in the symlink directory succeeds.
- if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); err != nil {
- t.Errorf("open enabled file failed: %v", err)
- }
- }
- })
- }
-}
-
-// TestDeletedSymlinkFileReadFails ensures that reading value of a deleted verity enabled
-// symlink fails.
-func TestDeletedSymlinkFileReadFails(t *testing.T) {
- testCases := []struct {
- name string
- // The original symlink is unlinked if deleteLink is true.
- deleteLink bool
- // The Merkle tree file is renamed if deleteMerkleFile is true.
- deleteMerkleFile bool
- // The symlink target is a directory.
- hasDirectoryTarget bool
- // The symlink target is a directory and contains a regular file which will be
- // used to test walking a symlink.
- testWalk bool
- }{
- {
- name: "DeleteLinkRegularFile",
- deleteLink: true,
- deleteMerkleFile: false,
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DeleteMerkleRegFile",
- deleteLink: false,
- deleteMerkleFile: true,
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DeleteLinkAndMerkleRegFile",
- deleteLink: true,
- deleteMerkleFile: true,
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DeleteLinkDirectory",
- deleteLink: true,
- deleteMerkleFile: false,
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "DeleteMerkleDirectory",
- deleteLink: false,
- deleteMerkleFile: true,
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "DeleteLinkAndMerkleDirectory",
- deleteLink: true,
- deleteMerkleFile: true,
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "DeleteLinkDirectoryWalk",
- deleteLink: true,
- deleteMerkleFile: false,
- hasDirectoryTarget: true,
- testWalk: true,
- },
- {
- name: "DeleteMerkleDirectoryWalk",
- deleteLink: false,
- deleteMerkleFile: true,
- hasDirectoryTarget: true,
- testWalk: true,
- },
- {
- name: "DeleteLinkAndMerkleDirectoryWalk",
- deleteLink: true,
- deleteMerkleFile: true,
- hasDirectoryTarget: true,
- testWalk: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.testWalk && !tc.hasDirectoryTarget {
- t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk")
- }
-
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- var target string
- if tc.hasDirectoryTarget {
- target = "verity-test-dir"
- if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newDirFD: %v", err)
- }
- } else {
- target = "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- symlink := "verity-test-symlink"
- if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil {
- t.Fatalf("SymlinkAt: %v", err)
- }
-
- fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_NOFOLLOW, linux.ModeRegular)
-
- if err != nil {
- t.Fatalf("openVerityAt symlink: %v", err)
- }
-
- if tc.testWalk {
- fileInTargetDirectory := target + "/" + "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- enableVerity(ctx, t, fd)
-
- if tc.deleteLink {
- if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, symlink); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
- if tc.deleteMerkleFile {
- if err := dentryFromVD(t, root).unlinkLowerMerkleAt(ctx, vfsObj, symlink); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
- }
- if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(symlink),
- }); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Fatalf("ReadlinkAt succeeded with modified symlink: %v", err)
- }
-
- if tc.testWalk {
- fileInSymlinkDirectory := symlink + "/verity-test-file"
- // Ensure opening the verity enabled file in the symlink directory fails.
- if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Errorf("Open succeeded with modified symlink: %v", err)
- }
- }
- })
- }
-}
-
-// TestModifiedSymlinkFileReadFails ensures that reading value of a modified verity enabled
-// symlink fails.
-func TestModifiedSymlinkFileReadFails(t *testing.T) {
- testCases := []struct {
- name string
- // The symlink target is a directory.
- hasDirectoryTarget bool
- // The symlink target is a directory and contains a regular file which will be
- // used to test walking a symlink.
- testWalk bool
- }{
- {
- name: "RegularFileTarget",
- hasDirectoryTarget: false,
- testWalk: false,
- },
- {
- name: "DirectoryTarget",
- hasDirectoryTarget: true,
- testWalk: false,
- },
- {
- name: "RegularFileInSymlinkDirectory",
- hasDirectoryTarget: true,
- testWalk: true,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.testWalk && !tc.hasDirectoryTarget {
- t.Fatalf("Invalid test case: hasDirectoryTarget can't be false when testing symlink walk")
- }
-
- vfsObj, root, ctx, err := newVerityRoot(t, SHA256)
- if err != nil {
- t.Fatalf("newVerityRoot: %v", err)
- }
-
- var target string
- if tc.hasDirectoryTarget {
- target = "verity-test-dir"
- if _, err := newDirFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newDirFD: %v", err)
- }
- } else {
- target = "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, target, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- // Create symlink which points to target file.
- symlink := "verity-test-symlink"
- if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, target, symlink); err != nil {
- t.Fatalf("SymlinkAt: %v", err)
- }
-
- // Open symlink file to get the fd for ioctl in new step.
- fd, err := openVerityAt(ctx, vfsObj, root, symlink, linux.O_NOFOLLOW, linux.ModeRegular)
- if err != nil {
- t.Fatalf("OpenAt symlink: %v", err)
- }
-
- if tc.testWalk {
- fileInTargetDirectory := target + "/" + "verity-test-file"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, fileInTargetDirectory, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- enableVerity(ctx, t, fd)
-
- var newTarget string
- if tc.hasDirectoryTarget {
- newTarget = "verity-test-dir-new"
- if _, err := newDirFD(ctx, t, vfsObj, root, newTarget, 0644); err != nil {
- t.Fatalf("newDirFD: %v", err)
- }
- } else {
- newTarget = "verity-test-file-new"
- if _, _, err := newFileFD(ctx, t, vfsObj, root, newTarget, 0644); err != nil {
- t.Fatalf("newFileFD: %v", err)
- }
- }
-
- // Unlink symlink->target.
- if err := dentryFromVD(t, root).unlinkLowerAt(ctx, vfsObj, symlink); err != nil {
- t.Fatalf("UnlinkAt: %v", err)
- }
-
- // Link symlink->newTarget.
- if err := dentryFromVD(t, root).symlinkLowerAt(ctx, vfsObj, newTarget, symlink); err != nil {
- t.Fatalf("SymlinkAt: %v", err)
- }
-
- // Freshen lower dentry for symlink.
- symlinkVD, err := vfsObj.GetDentryAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(symlink),
- }, &vfs.GetDentryOptions{})
- if err != nil {
- t.Fatalf("Failed to get symlink dentry: %v", err)
- }
- symlinkDentry := dentryFromVD(t, symlinkVD)
-
- symlinkLowerVD, err := dentryFromVD(t, root).getLowerAt(ctx, vfsObj, symlink)
- if err != nil {
- t.Fatalf("Failed to get symlink lower dentry: %v", err)
- }
- symlinkDentry.lowerVD = symlinkLowerVD
-
- // Verify ReadlinkAt() fails.
- if _, err := vfsObj.ReadlinkAt(ctx, auth.CredentialsFromContext(ctx), &vfs.PathOperation{
- Root: root,
- Start: root,
- Path: fspath.Parse(symlink),
- }); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Fatalf("ReadlinkAt succeeded with modified symlink: %v", err)
- }
-
- if tc.testWalk {
- fileInSymlinkDirectory := symlink + "/verity-test-file"
- // Ensure opening the verity enabled file in the symlink directory fails.
- if _, err := openVerityAt(ctx, vfsObj, root, fileInSymlinkDirectory, linux.O_RDONLY, linux.ModeRegular); !linuxerr.Equals(linuxerr.EIO, err) {
- t.Errorf("Open succeeded with modified symlink: %v", err)
- }
- }
- })
- }
-}