diff options
Diffstat (limited to 'pkg/sentry/fsimpl/sys')
-rw-r--r-- | pkg/sentry/fsimpl/sys/BUILD | 54 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/sys/dir_refs.go | 118 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/sys/sys_state_autogen.go | 248 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/sys/sys_test.go | 89 |
4 files changed, 366 insertions, 143 deletions
diff --git a/pkg/sentry/fsimpl/sys/BUILD b/pkg/sentry/fsimpl/sys/BUILD deleted file mode 100644 index 906cd52cb..000000000 --- a/pkg/sentry/fsimpl/sys/BUILD +++ /dev/null @@ -1,54 +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 = "dir_refs", - out = "dir_refs.go", - package = "sys", - prefix = "dir", - template = "//pkg/refs_vfs2:refs_template", - types = { - "T": "dir", - }, -) - -go_library( - name = "sys", - srcs = [ - "dir_refs.go", - "kcov.go", - "sys.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/coverage", - "//pkg/log", - "//pkg/refs", - "//pkg/sentry/arch", - "//pkg/sentry/fsimpl/kernfs", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/memmap", - "//pkg/sentry/vfs", - "//pkg/syserror", - "//pkg/usermem", - ], -) - -go_test( - name = "sys_test", - srcs = ["sys_test.go"], - deps = [ - ":sys", - "//pkg/abi/linux", - "//pkg/sentry/fsimpl/testutil", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - "@com_github_google_go_cmp//cmp:go_default_library", - ], -) diff --git a/pkg/sentry/fsimpl/sys/dir_refs.go b/pkg/sentry/fsimpl/sys/dir_refs.go new file mode 100644 index 000000000..89609b198 --- /dev/null +++ b/pkg/sentry/fsimpl/sys/dir_refs.go @@ -0,0 +1,118 @@ +package sys + +import ( + "fmt" + "runtime" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/log" + refs_vfs1 "gvisor.dev/gvisor/pkg/refs" +) + +// ownerType is used to customize logging. Note that we use a pointer to T so +// that we do not copy the entire object when passed as a format parameter. +var dirownerType *dir + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// Note that the number of references is actually refCount + 1 so that a default +// zero-value Refs object contains one reference. +// +// TODO(gvisor.dev/issue/1486): Store stack traces when leak check is enabled in +// a map with 16-bit hashes, and store the hash in the top 16 bits of refCount. +// This will allow us to add stack trace information to the leak messages +// without growing the size of Refs. +// +// +stateify savable +type dirRefs struct { + // refCount is composed of two fields: + // + // [32-bit speculative references]:[32-bit real references] + // + // Speculative references are used for TryIncRef, to avoid a CompareAndSwap + // loop. See IncRef, DecRef and TryIncRef for details of how these fields are + // used. + refCount int64 +} + +func (r *dirRefs) finalize() { + var note string + switch refs_vfs1.GetLeakMode() { + case refs_vfs1.NoLeakChecking: + return + case refs_vfs1.UninitializedLeakChecking: + note = "(Leak checker uninitialized): " + } + if n := r.ReadRefs(); n != 0 { + log.Warningf("%sRefs %p owned by %T garbage collected with ref count of %d (want 0)", note, r, dirownerType, n) + } +} + +// EnableLeakCheck checks for reference leaks when Refs gets garbage collected. +func (r *dirRefs) EnableLeakCheck() { + if refs_vfs1.GetLeakMode() != refs_vfs1.NoLeakChecking { + runtime.SetFinalizer(r, (*dirRefs).finalize) + } +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *dirRefs) ReadRefs() int64 { + + return atomic.LoadInt64(&r.refCount) + 1 +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *dirRefs) IncRef() { + if v := atomic.AddInt64(&r.refCount, 1); v <= 0 { + panic(fmt.Sprintf("Incrementing non-positive ref count %p owned by %T", r, dirownerType)) + } +} + +// TryIncRef implements refs.RefCounter.TryIncRef. +// +// To do this safely without a loop, a speculative reference is first acquired +// on the object. This allows multiple concurrent TryIncRef calls to distinguish +// other TryIncRef calls from genuine references held. +// +//go:nosplit +func (r *dirRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + v := atomic.AddInt64(&r.refCount, speculativeRef) + if int32(v) < 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + atomic.AddInt64(&r.refCount, -speculativeRef+1) + return true +} + +// DecRef implements refs.RefCounter.DecRef. +// +// Note that speculative references are counted here. Since they were added +// prior to real references reaching zero, they will successfully convert to +// real references. In other words, we see speculative references only in the +// following case: +// +// A: TryIncRef [speculative increase => sees non-negative references] +// B: DecRef [real decrease] +// A: TryIncRef [transform speculative to real] +// +//go:nosplit +func (r *dirRefs) DecRef(destroy func()) { + switch v := atomic.AddInt64(&r.refCount, -1); { + case v < -1: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %T", r, dirownerType)) + + case v == -1: + + if destroy != nil { + destroy() + } + } +} diff --git a/pkg/sentry/fsimpl/sys/sys_state_autogen.go b/pkg/sentry/fsimpl/sys/sys_state_autogen.go new file mode 100644 index 000000000..64c9c9d1f --- /dev/null +++ b/pkg/sentry/fsimpl/sys/sys_state_autogen.go @@ -0,0 +1,248 @@ +// automatically generated by stateify. + +package sys + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (r *dirRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/sys.dirRefs" +} + +func (r *dirRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *dirRefs) beforeSave() {} + +func (r *dirRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +func (r *dirRefs) afterLoad() {} + +func (r *dirRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) +} + +func (i *kcovInode) StateTypeName() string { + return "pkg/sentry/fsimpl/sys.kcovInode" +} + +func (i *kcovInode) StateFields() []string { + return []string{ + "InodeAttrs", + "InodeNoopRefCount", + "InodeNotDirectory", + "InodeNotSymlink", + "implStatFS", + } +} + +func (i *kcovInode) beforeSave() {} + +func (i *kcovInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.InodeAttrs) + stateSinkObject.Save(1, &i.InodeNoopRefCount) + stateSinkObject.Save(2, &i.InodeNotDirectory) + stateSinkObject.Save(3, &i.InodeNotSymlink) + stateSinkObject.Save(4, &i.implStatFS) +} + +func (i *kcovInode) afterLoad() {} + +func (i *kcovInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeAttrs) + stateSourceObject.Load(1, &i.InodeNoopRefCount) + stateSourceObject.Load(2, &i.InodeNotDirectory) + stateSourceObject.Load(3, &i.InodeNotSymlink) + stateSourceObject.Load(4, &i.implStatFS) +} + +func (fd *kcovFD) StateTypeName() string { + return "pkg/sentry/fsimpl/sys.kcovFD" +} + +func (fd *kcovFD) StateFields() []string { + return []string{ + "FileDescriptionDefaultImpl", + "NoLockFD", + "vfsfd", + "inode", + "kcov", + } +} + +func (fd *kcovFD) beforeSave() {} + +func (fd *kcovFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl) + stateSinkObject.Save(1, &fd.NoLockFD) + stateSinkObject.Save(2, &fd.vfsfd) + stateSinkObject.Save(3, &fd.inode) + stateSinkObject.Save(4, &fd.kcov) +} + +func (fd *kcovFD) afterLoad() {} + +func (fd *kcovFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl) + stateSourceObject.Load(1, &fd.NoLockFD) + stateSourceObject.Load(2, &fd.vfsfd) + stateSourceObject.Load(3, &fd.inode) + stateSourceObject.Load(4, &fd.kcov) +} + +func (fsType *FilesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/sys.FilesystemType" +} + +func (fsType *FilesystemType) StateFields() []string { + return []string{} +} + +func (fsType *FilesystemType) beforeSave() {} + +func (fsType *FilesystemType) StateSave(stateSinkObject state.Sink) { + fsType.beforeSave() +} + +func (fsType *FilesystemType) afterLoad() {} + +func (fsType *FilesystemType) StateLoad(stateSourceObject state.Source) { +} + +func (fs *filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/sys.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "Filesystem", + "devMinor", + } +} + +func (fs *filesystem) beforeSave() {} + +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Filesystem) + stateSinkObject.Save(1, &fs.devMinor) +} + +func (fs *filesystem) afterLoad() {} + +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Filesystem) + stateSourceObject.Load(1, &fs.devMinor) +} + +func (d *dir) StateTypeName() string { + return "pkg/sentry/fsimpl/sys.dir" +} + +func (d *dir) StateFields() []string { + return []string{ + "dirRefs", + "InodeAlwaysValid", + "InodeAttrs", + "InodeNotSymlink", + "InodeDirectoryNoNewChildren", + "InodeTemporary", + "OrderedChildren", + "locks", + } +} + +func (d *dir) beforeSave() {} + +func (d *dir) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.dirRefs) + stateSinkObject.Save(1, &d.InodeAlwaysValid) + stateSinkObject.Save(2, &d.InodeAttrs) + stateSinkObject.Save(3, &d.InodeNotSymlink) + stateSinkObject.Save(4, &d.InodeDirectoryNoNewChildren) + stateSinkObject.Save(5, &d.InodeTemporary) + stateSinkObject.Save(6, &d.OrderedChildren) + stateSinkObject.Save(7, &d.locks) +} + +func (d *dir) afterLoad() {} + +func (d *dir) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.dirRefs) + stateSourceObject.Load(1, &d.InodeAlwaysValid) + stateSourceObject.Load(2, &d.InodeAttrs) + stateSourceObject.Load(3, &d.InodeNotSymlink) + stateSourceObject.Load(4, &d.InodeDirectoryNoNewChildren) + stateSourceObject.Load(5, &d.InodeTemporary) + stateSourceObject.Load(6, &d.OrderedChildren) + stateSourceObject.Load(7, &d.locks) +} + +func (c *cpuFile) StateTypeName() string { + return "pkg/sentry/fsimpl/sys.cpuFile" +} + +func (c *cpuFile) StateFields() []string { + return []string{ + "implStatFS", + "DynamicBytesFile", + "maxCores", + } +} + +func (c *cpuFile) beforeSave() {} + +func (c *cpuFile) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.implStatFS) + stateSinkObject.Save(1, &c.DynamicBytesFile) + stateSinkObject.Save(2, &c.maxCores) +} + +func (c *cpuFile) afterLoad() {} + +func (c *cpuFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.implStatFS) + stateSourceObject.Load(1, &c.DynamicBytesFile) + stateSourceObject.Load(2, &c.maxCores) +} + +func (i *implStatFS) StateTypeName() string { + return "pkg/sentry/fsimpl/sys.implStatFS" +} + +func (i *implStatFS) StateFields() []string { + return []string{} +} + +func (i *implStatFS) beforeSave() {} + +func (i *implStatFS) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *implStatFS) afterLoad() {} + +func (i *implStatFS) StateLoad(stateSourceObject state.Source) { +} + +func init() { + state.Register((*dirRefs)(nil)) + state.Register((*kcovInode)(nil)) + state.Register((*kcovFD)(nil)) + state.Register((*FilesystemType)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*dir)(nil)) + state.Register((*cpuFile)(nil)) + state.Register((*implStatFS)(nil)) +} diff --git a/pkg/sentry/fsimpl/sys/sys_test.go b/pkg/sentry/fsimpl/sys/sys_test.go deleted file mode 100644 index 0a0d914cc..000000000 --- a/pkg/sentry/fsimpl/sys/sys_test.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2019 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 sys_test - -import ( - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/sys" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -func newTestSystem(t *testing.T) *testutil.System { - k, err := testutil.Boot() - if err != nil { - t.Fatalf("Failed to create test kernel: %v", err) - } - ctx := k.SupervisorContext() - creds := auth.CredentialsFromContext(ctx) - k.VFS().MustRegisterFilesystemType(sys.Name, sys.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - - mns, err := k.VFS().NewMountNamespace(ctx, creds, "", sys.Name, &vfs.MountOptions{}) - if err != nil { - t.Fatalf("Failed to create new mount namespace: %v", err) - } - return testutil.NewSystem(ctx, t, k.VFS(), mns) -} - -func TestReadCPUFile(t *testing.T) { - s := newTestSystem(t) - defer s.Destroy() - k := kernel.KernelFromContext(s.Ctx) - maxCPUCores := k.ApplicationCores() - - expected := fmt.Sprintf("0-%d\n", maxCPUCores-1) - - for _, fname := range []string{"online", "possible", "present"} { - pop := s.PathOpAtRoot(fmt.Sprintf("devices/system/cpu/%s", fname)) - fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, &vfs.OpenOptions{}) - if err != nil { - t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err) - } - defer fd.DecRef(s.Ctx) - content, err := s.ReadToEnd(fd) - if err != nil { - t.Fatalf("Read failed: %v", err) - } - if diff := cmp.Diff(expected, content); diff != "" { - t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff) - } - } -} - -func TestSysRootContainsExpectedEntries(t *testing.T) { - s := newTestSystem(t) - defer s.Destroy() - pop := s.PathOpAtRoot("/") - s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{ - "block": linux.DT_DIR, - "bus": linux.DT_DIR, - "class": linux.DT_DIR, - "dev": linux.DT_DIR, - "devices": linux.DT_DIR, - "firmware": linux.DT_DIR, - "fs": linux.DT_DIR, - "kernel": linux.DT_DIR, - "module": linux.DT_DIR, - "power": linux.DT_DIR, - }) -} |