diff options
Diffstat (limited to 'pkg/sentry/fsimpl/verity')
-rw-r--r-- | pkg/sentry/fsimpl/verity/BUILD | 68 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/verity/dentry_list.go | 221 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/verity/verity_state_autogen.go | 316 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/verity/verity_test.go | 1211 |
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) - } - } - }) - } -} |