diff options
Diffstat (limited to 'pkg/sentry/fsimpl')
80 files changed, 11576 insertions, 6518 deletions
diff --git a/pkg/sentry/fsimpl/cgroupfs/BUILD b/pkg/sentry/fsimpl/cgroupfs/BUILD deleted file mode 100644 index e5fdcc776..000000000 --- a/pkg/sentry/fsimpl/cgroupfs/BUILD +++ /dev/null @@ -1,49 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//tools/go_generics:defs.bzl", "go_template_instance") - -licenses(["notice"]) - -go_template_instance( - name = "dir_refs", - out = "dir_refs.go", - package = "cgroupfs", - prefix = "dir", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "dir", - }, -) - -go_library( - name = "cgroupfs", - srcs = [ - "base.go", - "cgroupfs.go", - "cpu.go", - "cpuacct.go", - "cpuset.go", - "dir_refs.go", - "job.go", - "memory.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/coverage", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/log", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/sentry/arch", - "//pkg/sentry/fsimpl/kernfs", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/memmap", - "//pkg/sentry/usage", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/usermem", - ], -) diff --git a/pkg/sentry/fsimpl/cgroupfs/cgroupfs_state_autogen.go b/pkg/sentry/fsimpl/cgroupfs/cgroupfs_state_autogen.go new file mode 100644 index 000000000..d390050d7 --- /dev/null +++ b/pkg/sentry/fsimpl/cgroupfs/cgroupfs_state_autogen.go @@ -0,0 +1,702 @@ +// automatically generated by stateify. + +package cgroupfs + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (c *controllerCommon) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.controllerCommon" +} + +func (c *controllerCommon) StateFields() []string { + return []string{ + "ty", + "fs", + } +} + +func (c *controllerCommon) beforeSave() {} + +// +checklocksignore +func (c *controllerCommon) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.ty) + stateSinkObject.Save(1, &c.fs) +} + +func (c *controllerCommon) afterLoad() {} + +// +checklocksignore +func (c *controllerCommon) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.ty) + stateSourceObject.Load(1, &c.fs) +} + +func (c *cgroupInode) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cgroupInode" +} + +func (c *cgroupInode) StateFields() []string { + return []string{ + "dir", + "ts", + } +} + +func (c *cgroupInode) beforeSave() {} + +// +checklocksignore +func (c *cgroupInode) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.dir) + stateSinkObject.Save(1, &c.ts) +} + +func (c *cgroupInode) afterLoad() {} + +// +checklocksignore +func (c *cgroupInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.dir) + stateSourceObject.Load(1, &c.ts) +} + +func (d *cgroupProcsData) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cgroupProcsData" +} + +func (d *cgroupProcsData) StateFields() []string { + return []string{ + "cgroupInode", + } +} + +func (d *cgroupProcsData) beforeSave() {} + +// +checklocksignore +func (d *cgroupProcsData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.cgroupInode) +} + +func (d *cgroupProcsData) afterLoad() {} + +// +checklocksignore +func (d *cgroupProcsData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.cgroupInode) +} + +func (d *tasksData) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.tasksData" +} + +func (d *tasksData) StateFields() []string { + return []string{ + "cgroupInode", + } +} + +func (d *tasksData) beforeSave() {} + +// +checklocksignore +func (d *tasksData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.cgroupInode) +} + +func (d *tasksData) afterLoad() {} + +// +checklocksignore +func (d *tasksData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.cgroupInode) +} + +func (fsType *FilesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.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 (i *InternalData) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.InternalData" +} + +func (i *InternalData) StateFields() []string { + return []string{ + "DefaultControlValues", + "InitialCgroupPath", + } +} + +func (i *InternalData) beforeSave() {} + +// +checklocksignore +func (i *InternalData) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.DefaultControlValues) + stateSinkObject.Save(1, &i.InitialCgroupPath) +} + +func (i *InternalData) afterLoad() {} + +// +checklocksignore +func (i *InternalData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.DefaultControlValues) + stateSourceObject.Load(1, &i.InitialCgroupPath) +} + +func (fs *filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "Filesystem", + "devMinor", + "hierarchyID", + "controllers", + "kcontrollers", + "numCgroups", + "root", + "effectiveRoot", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Filesystem) + stateSinkObject.Save(1, &fs.devMinor) + stateSinkObject.Save(2, &fs.hierarchyID) + stateSinkObject.Save(3, &fs.controllers) + stateSinkObject.Save(4, &fs.kcontrollers) + stateSinkObject.Save(5, &fs.numCgroups) + stateSinkObject.Save(6, &fs.root) + stateSinkObject.Save(7, &fs.effectiveRoot) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Filesystem) + stateSourceObject.Load(1, &fs.devMinor) + stateSourceObject.Load(2, &fs.hierarchyID) + stateSourceObject.Load(3, &fs.controllers) + stateSourceObject.Load(4, &fs.kcontrollers) + stateSourceObject.Load(5, &fs.numCgroups) + stateSourceObject.Load(6, &fs.root) + stateSourceObject.Load(7, &fs.effectiveRoot) +} + +func (i *implStatFS) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.implStatFS" +} + +func (i *implStatFS) StateFields() []string { + return []string{} +} + +func (i *implStatFS) beforeSave() {} + +// +checklocksignore +func (i *implStatFS) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *implStatFS) afterLoad() {} + +// +checklocksignore +func (i *implStatFS) StateLoad(stateSourceObject state.Source) { +} + +func (d *dir) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.dir" +} + +func (d *dir) StateFields() []string { + return []string{ + "InodeNoopRefCount", + "InodeAlwaysValid", + "InodeAttrs", + "InodeNotSymlink", + "InodeDirectoryNoNewChildren", + "OrderedChildren", + "implStatFS", + "locks", + "fs", + "cgi", + } +} + +func (d *dir) beforeSave() {} + +// +checklocksignore +func (d *dir) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.InodeNoopRefCount) + stateSinkObject.Save(1, &d.InodeAlwaysValid) + stateSinkObject.Save(2, &d.InodeAttrs) + stateSinkObject.Save(3, &d.InodeNotSymlink) + stateSinkObject.Save(4, &d.InodeDirectoryNoNewChildren) + stateSinkObject.Save(5, &d.OrderedChildren) + stateSinkObject.Save(6, &d.implStatFS) + stateSinkObject.Save(7, &d.locks) + stateSinkObject.Save(8, &d.fs) + stateSinkObject.Save(9, &d.cgi) +} + +func (d *dir) afterLoad() {} + +// +checklocksignore +func (d *dir) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.InodeNoopRefCount) + stateSourceObject.Load(1, &d.InodeAlwaysValid) + stateSourceObject.Load(2, &d.InodeAttrs) + stateSourceObject.Load(3, &d.InodeNotSymlink) + stateSourceObject.Load(4, &d.InodeDirectoryNoNewChildren) + stateSourceObject.Load(5, &d.OrderedChildren) + stateSourceObject.Load(6, &d.implStatFS) + stateSourceObject.Load(7, &d.locks) + stateSourceObject.Load(8, &d.fs) + stateSourceObject.Load(9, &d.cgi) +} + +func (c *controllerFile) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.controllerFile" +} + +func (c *controllerFile) StateFields() []string { + return []string{ + "DynamicBytesFile", + } +} + +func (c *controllerFile) beforeSave() {} + +// +checklocksignore +func (c *controllerFile) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.DynamicBytesFile) +} + +func (c *controllerFile) afterLoad() {} + +// +checklocksignore +func (c *controllerFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.DynamicBytesFile) +} + +func (s *staticControllerFile) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.staticControllerFile" +} + +func (s *staticControllerFile) StateFields() []string { + return []string{ + "DynamicBytesFile", + "StaticData", + } +} + +func (s *staticControllerFile) beforeSave() {} + +// +checklocksignore +func (s *staticControllerFile) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.DynamicBytesFile) + stateSinkObject.Save(1, &s.StaticData) +} + +func (s *staticControllerFile) afterLoad() {} + +// +checklocksignore +func (s *staticControllerFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.DynamicBytesFile) + stateSourceObject.Load(1, &s.StaticData) +} + +func (c *cpuController) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cpuController" +} + +func (c *cpuController) StateFields() []string { + return []string{ + "controllerCommon", + "cfsPeriod", + "cfsQuota", + "shares", + } +} + +func (c *cpuController) beforeSave() {} + +// +checklocksignore +func (c *cpuController) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.controllerCommon) + stateSinkObject.Save(1, &c.cfsPeriod) + stateSinkObject.Save(2, &c.cfsQuota) + stateSinkObject.Save(3, &c.shares) +} + +func (c *cpuController) afterLoad() {} + +// +checklocksignore +func (c *cpuController) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.controllerCommon) + stateSourceObject.Load(1, &c.cfsPeriod) + stateSourceObject.Load(2, &c.cfsQuota) + stateSourceObject.Load(3, &c.shares) +} + +func (c *cpuacctController) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cpuacctController" +} + +func (c *cpuacctController) StateFields() []string { + return []string{ + "controllerCommon", + } +} + +func (c *cpuacctController) beforeSave() {} + +// +checklocksignore +func (c *cpuacctController) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.controllerCommon) +} + +func (c *cpuacctController) afterLoad() {} + +// +checklocksignore +func (c *cpuacctController) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.controllerCommon) +} + +func (c *cpuacctCgroup) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cpuacctCgroup" +} + +func (c *cpuacctCgroup) StateFields() []string { + return []string{ + "cgroupInode", + } +} + +func (c *cpuacctCgroup) beforeSave() {} + +// +checklocksignore +func (c *cpuacctCgroup) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.cgroupInode) +} + +func (c *cpuacctCgroup) afterLoad() {} + +// +checklocksignore +func (c *cpuacctCgroup) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.cgroupInode) +} + +func (d *cpuacctStatData) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cpuacctStatData" +} + +func (d *cpuacctStatData) StateFields() []string { + return []string{ + "cpuacctCgroup", + } +} + +func (d *cpuacctStatData) beforeSave() {} + +// +checklocksignore +func (d *cpuacctStatData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.cpuacctCgroup) +} + +func (d *cpuacctStatData) afterLoad() {} + +// +checklocksignore +func (d *cpuacctStatData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.cpuacctCgroup) +} + +func (d *cpuacctUsageData) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cpuacctUsageData" +} + +func (d *cpuacctUsageData) StateFields() []string { + return []string{ + "cpuacctCgroup", + } +} + +func (d *cpuacctUsageData) beforeSave() {} + +// +checklocksignore +func (d *cpuacctUsageData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.cpuacctCgroup) +} + +func (d *cpuacctUsageData) afterLoad() {} + +// +checklocksignore +func (d *cpuacctUsageData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.cpuacctCgroup) +} + +func (d *cpuacctUsageUserData) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cpuacctUsageUserData" +} + +func (d *cpuacctUsageUserData) StateFields() []string { + return []string{ + "cpuacctCgroup", + } +} + +func (d *cpuacctUsageUserData) beforeSave() {} + +// +checklocksignore +func (d *cpuacctUsageUserData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.cpuacctCgroup) +} + +func (d *cpuacctUsageUserData) afterLoad() {} + +// +checklocksignore +func (d *cpuacctUsageUserData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.cpuacctCgroup) +} + +func (d *cpuacctUsageSysData) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cpuacctUsageSysData" +} + +func (d *cpuacctUsageSysData) StateFields() []string { + return []string{ + "cpuacctCgroup", + } +} + +func (d *cpuacctUsageSysData) beforeSave() {} + +// +checklocksignore +func (d *cpuacctUsageSysData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.cpuacctCgroup) +} + +func (d *cpuacctUsageSysData) afterLoad() {} + +// +checklocksignore +func (d *cpuacctUsageSysData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.cpuacctCgroup) +} + +func (c *cpusetController) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.cpusetController" +} + +func (c *cpusetController) StateFields() []string { + return []string{ + "controllerCommon", + } +} + +func (c *cpusetController) beforeSave() {} + +// +checklocksignore +func (c *cpusetController) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.controllerCommon) +} + +func (c *cpusetController) afterLoad() {} + +// +checklocksignore +func (c *cpusetController) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.controllerCommon) +} + +func (r *dirRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.dirRefs" +} + +func (r *dirRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *dirRefs) beforeSave() {} + +// +checklocksignore +func (r *dirRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *dirRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (c *jobController) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.jobController" +} + +func (c *jobController) StateFields() []string { + return []string{ + "controllerCommon", + "id", + } +} + +func (c *jobController) beforeSave() {} + +// +checklocksignore +func (c *jobController) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.controllerCommon) + stateSinkObject.Save(1, &c.id) +} + +func (c *jobController) afterLoad() {} + +// +checklocksignore +func (c *jobController) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.controllerCommon) + stateSourceObject.Load(1, &c.id) +} + +func (d *jobIDData) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.jobIDData" +} + +func (d *jobIDData) StateFields() []string { + return []string{ + "c", + } +} + +func (d *jobIDData) beforeSave() {} + +// +checklocksignore +func (d *jobIDData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.c) +} + +func (d *jobIDData) afterLoad() {} + +// +checklocksignore +func (d *jobIDData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.c) +} + +func (c *memoryController) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.memoryController" +} + +func (c *memoryController) StateFields() []string { + return []string{ + "controllerCommon", + "limitBytes", + "softLimitBytes", + "moveChargeAtImmigrate", + } +} + +func (c *memoryController) beforeSave() {} + +// +checklocksignore +func (c *memoryController) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.controllerCommon) + stateSinkObject.Save(1, &c.limitBytes) + stateSinkObject.Save(2, &c.softLimitBytes) + stateSinkObject.Save(3, &c.moveChargeAtImmigrate) +} + +func (c *memoryController) afterLoad() {} + +// +checklocksignore +func (c *memoryController) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.controllerCommon) + stateSourceObject.Load(1, &c.limitBytes) + stateSourceObject.Load(2, &c.softLimitBytes) + stateSourceObject.Load(3, &c.moveChargeAtImmigrate) +} + +func (d *memoryUsageInBytesData) StateTypeName() string { + return "pkg/sentry/fsimpl/cgroupfs.memoryUsageInBytesData" +} + +func (d *memoryUsageInBytesData) StateFields() []string { + return []string{} +} + +func (d *memoryUsageInBytesData) beforeSave() {} + +// +checklocksignore +func (d *memoryUsageInBytesData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() +} + +func (d *memoryUsageInBytesData) afterLoad() {} + +// +checklocksignore +func (d *memoryUsageInBytesData) StateLoad(stateSourceObject state.Source) { +} + +func init() { + state.Register((*controllerCommon)(nil)) + state.Register((*cgroupInode)(nil)) + state.Register((*cgroupProcsData)(nil)) + state.Register((*tasksData)(nil)) + state.Register((*FilesystemType)(nil)) + state.Register((*InternalData)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*implStatFS)(nil)) + state.Register((*dir)(nil)) + state.Register((*controllerFile)(nil)) + state.Register((*staticControllerFile)(nil)) + state.Register((*cpuController)(nil)) + state.Register((*cpuacctController)(nil)) + state.Register((*cpuacctCgroup)(nil)) + state.Register((*cpuacctStatData)(nil)) + state.Register((*cpuacctUsageData)(nil)) + state.Register((*cpuacctUsageUserData)(nil)) + state.Register((*cpuacctUsageSysData)(nil)) + state.Register((*cpusetController)(nil)) + state.Register((*dirRefs)(nil)) + state.Register((*jobController)(nil)) + state.Register((*jobIDData)(nil)) + state.Register((*memoryController)(nil)) + state.Register((*memoryUsageInBytesData)(nil)) +} diff --git a/pkg/sentry/fsimpl/cgroupfs/dir_refs.go b/pkg/sentry/fsimpl/cgroupfs/dir_refs.go new file mode 100644 index 000000000..c29f0c9ae --- /dev/null +++ b/pkg/sentry/fsimpl/cgroupfs/dir_refs.go @@ -0,0 +1,140 @@ +package cgroupfs + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const direnableLogging = false + +// obj 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 dirobj *dir + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *dirRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *dirRefs) RefType() string { + return fmt.Sprintf("%T", dirobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *dirRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *dirRefs) LogRefs() bool { + return direnableLogging +} + +// 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) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *dirRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if direnableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if direnableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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()) { + v := atomic.AddInt64(&r.refCount, -1) + if direnableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *dirRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/devpts/BUILD b/pkg/sentry/fsimpl/devpts/BUILD deleted file mode 100644 index e0b879339..000000000 --- a/pkg/sentry/fsimpl/devpts/BUILD +++ /dev/null @@ -1,64 +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 = "root_inode_refs", - out = "root_inode_refs.go", - package = "devpts", - prefix = "rootInode", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "rootInode", - }, -) - -go_library( - name = "devpts", - srcs = [ - "devpts.go", - "line_discipline.go", - "master.go", - "queue.go", - "replica.go", - "root_inode_refs.go", - "terminal.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/log", - "//pkg/marshal", - "//pkg/marshal/primitive", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/safemem", - "//pkg/sentry/arch", - "//pkg/sentry/fs", - "//pkg/sentry/fs/lock", - "//pkg/sentry/fsimpl/kernfs", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/unimpl", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/usermem", - "//pkg/waiter", - ], -) - -go_test( - name = "devpts_test", - size = "small", - srcs = ["devpts_test.go"], - library = ":devpts", - deps = [ - "//pkg/abi/linux", - "//pkg/sentry/contexttest", - "//pkg/usermem", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/fsimpl/devpts/devpts_state_autogen.go b/pkg/sentry/fsimpl/devpts/devpts_state_autogen.go new file mode 100644 index 000000000..78e685c7e --- /dev/null +++ b/pkg/sentry/fsimpl/devpts/devpts_state_autogen.go @@ -0,0 +1,502 @@ +// automatically generated by stateify. + +package devpts + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (fstype *FilesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.FilesystemType" +} + +func (fstype *FilesystemType) StateFields() []string { + return []string{ + "initErr", + "fs", + "root", + } +} + +func (fstype *FilesystemType) beforeSave() {} + +// +checklocksignore +func (fstype *FilesystemType) StateSave(stateSinkObject state.Sink) { + fstype.beforeSave() + stateSinkObject.Save(0, &fstype.initErr) + stateSinkObject.Save(1, &fstype.fs) + stateSinkObject.Save(2, &fstype.root) +} + +func (fstype *FilesystemType) afterLoad() {} + +// +checklocksignore +func (fstype *FilesystemType) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fstype.initErr) + stateSourceObject.Load(1, &fstype.fs) + stateSourceObject.Load(2, &fstype.root) +} + +func (fs *filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "Filesystem", + "devMinor", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Filesystem) + stateSinkObject.Save(1, &fs.devMinor) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Filesystem) + stateSourceObject.Load(1, &fs.devMinor) +} + +func (i *rootInode) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.rootInode" +} + +func (i *rootInode) StateFields() []string { + return []string{ + "implStatFS", + "InodeAlwaysValid", + "InodeAttrs", + "InodeDirectoryNoNewChildren", + "InodeNotSymlink", + "InodeTemporary", + "OrderedChildren", + "rootInodeRefs", + "locks", + "master", + "replicas", + "nextIdx", + } +} + +func (i *rootInode) beforeSave() {} + +// +checklocksignore +func (i *rootInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.implStatFS) + stateSinkObject.Save(1, &i.InodeAlwaysValid) + stateSinkObject.Save(2, &i.InodeAttrs) + stateSinkObject.Save(3, &i.InodeDirectoryNoNewChildren) + stateSinkObject.Save(4, &i.InodeNotSymlink) + stateSinkObject.Save(5, &i.InodeTemporary) + stateSinkObject.Save(6, &i.OrderedChildren) + stateSinkObject.Save(7, &i.rootInodeRefs) + stateSinkObject.Save(8, &i.locks) + stateSinkObject.Save(9, &i.master) + stateSinkObject.Save(10, &i.replicas) + stateSinkObject.Save(11, &i.nextIdx) +} + +func (i *rootInode) afterLoad() {} + +// +checklocksignore +func (i *rootInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.implStatFS) + stateSourceObject.Load(1, &i.InodeAlwaysValid) + stateSourceObject.Load(2, &i.InodeAttrs) + stateSourceObject.Load(3, &i.InodeDirectoryNoNewChildren) + stateSourceObject.Load(4, &i.InodeNotSymlink) + stateSourceObject.Load(5, &i.InodeTemporary) + stateSourceObject.Load(6, &i.OrderedChildren) + stateSourceObject.Load(7, &i.rootInodeRefs) + stateSourceObject.Load(8, &i.locks) + stateSourceObject.Load(9, &i.master) + stateSourceObject.Load(10, &i.replicas) + stateSourceObject.Load(11, &i.nextIdx) +} + +func (i *implStatFS) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.implStatFS" +} + +func (i *implStatFS) StateFields() []string { + return []string{} +} + +func (i *implStatFS) beforeSave() {} + +// +checklocksignore +func (i *implStatFS) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *implStatFS) afterLoad() {} + +// +checklocksignore +func (i *implStatFS) StateLoad(stateSourceObject state.Source) { +} + +func (l *lineDiscipline) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.lineDiscipline" +} + +func (l *lineDiscipline) StateFields() []string { + return []string{ + "size", + "inQueue", + "outQueue", + "termios", + "column", + "masterWaiter", + "replicaWaiter", + } +} + +func (l *lineDiscipline) beforeSave() {} + +// +checklocksignore +func (l *lineDiscipline) StateSave(stateSinkObject state.Sink) { + l.beforeSave() + stateSinkObject.Save(0, &l.size) + stateSinkObject.Save(1, &l.inQueue) + stateSinkObject.Save(2, &l.outQueue) + stateSinkObject.Save(3, &l.termios) + stateSinkObject.Save(4, &l.column) + stateSinkObject.Save(5, &l.masterWaiter) + stateSinkObject.Save(6, &l.replicaWaiter) +} + +func (l *lineDiscipline) afterLoad() {} + +// +checklocksignore +func (l *lineDiscipline) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &l.size) + stateSourceObject.Load(1, &l.inQueue) + stateSourceObject.Load(2, &l.outQueue) + stateSourceObject.Load(3, &l.termios) + stateSourceObject.Load(4, &l.column) + stateSourceObject.Load(5, &l.masterWaiter) + stateSourceObject.Load(6, &l.replicaWaiter) +} + +func (o *outputQueueTransformer) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.outputQueueTransformer" +} + +func (o *outputQueueTransformer) StateFields() []string { + return []string{} +} + +func (o *outputQueueTransformer) beforeSave() {} + +// +checklocksignore +func (o *outputQueueTransformer) StateSave(stateSinkObject state.Sink) { + o.beforeSave() +} + +func (o *outputQueueTransformer) afterLoad() {} + +// +checklocksignore +func (o *outputQueueTransformer) StateLoad(stateSourceObject state.Source) { +} + +func (i *inputQueueTransformer) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.inputQueueTransformer" +} + +func (i *inputQueueTransformer) StateFields() []string { + return []string{} +} + +func (i *inputQueueTransformer) beforeSave() {} + +// +checklocksignore +func (i *inputQueueTransformer) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *inputQueueTransformer) afterLoad() {} + +// +checklocksignore +func (i *inputQueueTransformer) StateLoad(stateSourceObject state.Source) { +} + +func (mi *masterInode) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.masterInode" +} + +func (mi *masterInode) StateFields() []string { + return []string{ + "implStatFS", + "InodeAttrs", + "InodeNoopRefCount", + "InodeNotDirectory", + "InodeNotSymlink", + "locks", + "root", + } +} + +func (mi *masterInode) beforeSave() {} + +// +checklocksignore +func (mi *masterInode) StateSave(stateSinkObject state.Sink) { + mi.beforeSave() + stateSinkObject.Save(0, &mi.implStatFS) + stateSinkObject.Save(1, &mi.InodeAttrs) + stateSinkObject.Save(2, &mi.InodeNoopRefCount) + stateSinkObject.Save(3, &mi.InodeNotDirectory) + stateSinkObject.Save(4, &mi.InodeNotSymlink) + stateSinkObject.Save(5, &mi.locks) + stateSinkObject.Save(6, &mi.root) +} + +func (mi *masterInode) afterLoad() {} + +// +checklocksignore +func (mi *masterInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &mi.implStatFS) + stateSourceObject.Load(1, &mi.InodeAttrs) + stateSourceObject.Load(2, &mi.InodeNoopRefCount) + stateSourceObject.Load(3, &mi.InodeNotDirectory) + stateSourceObject.Load(4, &mi.InodeNotSymlink) + stateSourceObject.Load(5, &mi.locks) + stateSourceObject.Load(6, &mi.root) +} + +func (mfd *masterFileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.masterFileDescription" +} + +func (mfd *masterFileDescription) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "LockFD", + "inode", + "t", + } +} + +func (mfd *masterFileDescription) beforeSave() {} + +// +checklocksignore +func (mfd *masterFileDescription) StateSave(stateSinkObject state.Sink) { + mfd.beforeSave() + stateSinkObject.Save(0, &mfd.vfsfd) + stateSinkObject.Save(1, &mfd.FileDescriptionDefaultImpl) + stateSinkObject.Save(2, &mfd.LockFD) + stateSinkObject.Save(3, &mfd.inode) + stateSinkObject.Save(4, &mfd.t) +} + +func (mfd *masterFileDescription) afterLoad() {} + +// +checklocksignore +func (mfd *masterFileDescription) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &mfd.vfsfd) + stateSourceObject.Load(1, &mfd.FileDescriptionDefaultImpl) + stateSourceObject.Load(2, &mfd.LockFD) + stateSourceObject.Load(3, &mfd.inode) + stateSourceObject.Load(4, &mfd.t) +} + +func (q *queue) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.queue" +} + +func (q *queue) StateFields() []string { + return []string{ + "readBuf", + "waitBuf", + "waitBufLen", + "readable", + "transformer", + } +} + +func (q *queue) beforeSave() {} + +// +checklocksignore +func (q *queue) StateSave(stateSinkObject state.Sink) { + q.beforeSave() + stateSinkObject.Save(0, &q.readBuf) + stateSinkObject.Save(1, &q.waitBuf) + stateSinkObject.Save(2, &q.waitBufLen) + stateSinkObject.Save(3, &q.readable) + stateSinkObject.Save(4, &q.transformer) +} + +func (q *queue) afterLoad() {} + +// +checklocksignore +func (q *queue) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &q.readBuf) + stateSourceObject.Load(1, &q.waitBuf) + stateSourceObject.Load(2, &q.waitBufLen) + stateSourceObject.Load(3, &q.readable) + stateSourceObject.Load(4, &q.transformer) +} + +func (ri *replicaInode) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.replicaInode" +} + +func (ri *replicaInode) StateFields() []string { + return []string{ + "implStatFS", + "InodeAttrs", + "InodeNoopRefCount", + "InodeNotDirectory", + "InodeNotSymlink", + "locks", + "root", + "t", + } +} + +func (ri *replicaInode) beforeSave() {} + +// +checklocksignore +func (ri *replicaInode) StateSave(stateSinkObject state.Sink) { + ri.beforeSave() + stateSinkObject.Save(0, &ri.implStatFS) + stateSinkObject.Save(1, &ri.InodeAttrs) + stateSinkObject.Save(2, &ri.InodeNoopRefCount) + stateSinkObject.Save(3, &ri.InodeNotDirectory) + stateSinkObject.Save(4, &ri.InodeNotSymlink) + stateSinkObject.Save(5, &ri.locks) + stateSinkObject.Save(6, &ri.root) + stateSinkObject.Save(7, &ri.t) +} + +func (ri *replicaInode) afterLoad() {} + +// +checklocksignore +func (ri *replicaInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &ri.implStatFS) + stateSourceObject.Load(1, &ri.InodeAttrs) + stateSourceObject.Load(2, &ri.InodeNoopRefCount) + stateSourceObject.Load(3, &ri.InodeNotDirectory) + stateSourceObject.Load(4, &ri.InodeNotSymlink) + stateSourceObject.Load(5, &ri.locks) + stateSourceObject.Load(6, &ri.root) + stateSourceObject.Load(7, &ri.t) +} + +func (rfd *replicaFileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.replicaFileDescription" +} + +func (rfd *replicaFileDescription) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "LockFD", + "inode", + } +} + +func (rfd *replicaFileDescription) beforeSave() {} + +// +checklocksignore +func (rfd *replicaFileDescription) StateSave(stateSinkObject state.Sink) { + rfd.beforeSave() + stateSinkObject.Save(0, &rfd.vfsfd) + stateSinkObject.Save(1, &rfd.FileDescriptionDefaultImpl) + stateSinkObject.Save(2, &rfd.LockFD) + stateSinkObject.Save(3, &rfd.inode) +} + +func (rfd *replicaFileDescription) afterLoad() {} + +// +checklocksignore +func (rfd *replicaFileDescription) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &rfd.vfsfd) + stateSourceObject.Load(1, &rfd.FileDescriptionDefaultImpl) + stateSourceObject.Load(2, &rfd.LockFD) + stateSourceObject.Load(3, &rfd.inode) +} + +func (r *rootInodeRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.rootInodeRefs" +} + +func (r *rootInodeRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *rootInodeRefs) beforeSave() {} + +// +checklocksignore +func (r *rootInodeRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *rootInodeRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (tm *Terminal) StateTypeName() string { + return "pkg/sentry/fsimpl/devpts.Terminal" +} + +func (tm *Terminal) StateFields() []string { + return []string{ + "n", + "ld", + "masterKTTY", + "replicaKTTY", + } +} + +func (tm *Terminal) beforeSave() {} + +// +checklocksignore +func (tm *Terminal) StateSave(stateSinkObject state.Sink) { + tm.beforeSave() + stateSinkObject.Save(0, &tm.n) + stateSinkObject.Save(1, &tm.ld) + stateSinkObject.Save(2, &tm.masterKTTY) + stateSinkObject.Save(3, &tm.replicaKTTY) +} + +func (tm *Terminal) afterLoad() {} + +// +checklocksignore +func (tm *Terminal) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &tm.n) + stateSourceObject.Load(1, &tm.ld) + stateSourceObject.Load(2, &tm.masterKTTY) + stateSourceObject.Load(3, &tm.replicaKTTY) +} + +func init() { + state.Register((*FilesystemType)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*rootInode)(nil)) + state.Register((*implStatFS)(nil)) + state.Register((*lineDiscipline)(nil)) + state.Register((*outputQueueTransformer)(nil)) + state.Register((*inputQueueTransformer)(nil)) + state.Register((*masterInode)(nil)) + state.Register((*masterFileDescription)(nil)) + state.Register((*queue)(nil)) + state.Register((*replicaInode)(nil)) + state.Register((*replicaFileDescription)(nil)) + state.Register((*rootInodeRefs)(nil)) + state.Register((*Terminal)(nil)) +} diff --git a/pkg/sentry/fsimpl/devpts/devpts_test.go b/pkg/sentry/fsimpl/devpts/devpts_test.go deleted file mode 100644 index 1ef07d702..000000000 --- a/pkg/sentry/fsimpl/devpts/devpts_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2018 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 devpts - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -func TestSimpleMasterToReplica(t *testing.T) { - ld := newLineDiscipline(linux.DefaultReplicaTermios) - ctx := contexttest.Context(t) - inBytes := []byte("hello, tty\n") - src := usermem.BytesIOSequence(inBytes) - outBytes := make([]byte, 32) - dst := usermem.BytesIOSequence(outBytes) - - // Write to the input queue. - nw, err := ld.inputQueueWrite(ctx, src) - if err != nil { - t.Fatalf("error writing to input queue: %v", err) - } - if nw != int64(len(inBytes)) { - t.Fatalf("wrote wrong length: got %d, want %d", nw, len(inBytes)) - } - - // Read from the input queue. - nr, err := ld.inputQueueRead(ctx, dst) - if err != nil { - t.Fatalf("error reading from input queue: %v", err) - } - if nr != int64(len(inBytes)) { - t.Fatalf("read wrong length: got %d, want %d", nr, len(inBytes)) - } - - outStr := string(outBytes[:nr]) - inStr := string(inBytes) - if outStr != inStr { - t.Fatalf("written and read strings do not match: got %q, want %q", outStr, inStr) - } -} - -type callback func(*waiter.Entry, waiter.EventMask) - -func (cb callback) Callback(entry *waiter.Entry, mask waiter.EventMask) { - cb(entry, mask) -} - -func TestEchoDeadlock(t *testing.T) { - ctx := contexttest.Context(t) - termios := linux.DefaultReplicaTermios - termios.LocalFlags |= linux.ECHO - ld := newLineDiscipline(termios) - outBytes := make([]byte, 32) - dst := usermem.BytesIOSequence(outBytes) - entry := &waiter.Entry{Callback: callback(func(*waiter.Entry, waiter.EventMask) { - ld.inputQueueRead(ctx, dst) - })} - ld.masterWaiter.EventRegister(entry, waiter.ReadableEvents) - defer ld.masterWaiter.EventUnregister(entry) - inBytes := []byte("hello, tty\n") - n, err := ld.inputQueueWrite(ctx, usermem.BytesIOSequence(inBytes)) - if err != nil { - t.Fatalf("inputQueueWrite: %v", err) - } - if int(n) != len(inBytes) { - t.Fatalf("read wrong length: got %d, want %d", n, len(inBytes)) - } - outStr := string(outBytes[:n]) - inStr := string(inBytes) - if outStr != inStr { - t.Fatalf("written and read strings do not match: got %q, want %q", outStr, inStr) - } -} diff --git a/pkg/sentry/fsimpl/devpts/root_inode_refs.go b/pkg/sentry/fsimpl/devpts/root_inode_refs.go new file mode 100644 index 000000000..e53739a90 --- /dev/null +++ b/pkg/sentry/fsimpl/devpts/root_inode_refs.go @@ -0,0 +1,140 @@ +package devpts + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const rootInodeenableLogging = false + +// obj 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 rootInodeobj *rootInode + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type rootInodeRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *rootInodeRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *rootInodeRefs) RefType() string { + return fmt.Sprintf("%T", rootInodeobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *rootInodeRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *rootInodeRefs) LogRefs() bool { + return rootInodeenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *rootInodeRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *rootInodeRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if rootInodeenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *rootInodeRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if rootInodeenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *rootInodeRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if rootInodeenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *rootInodeRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/devtmpfs/BUILD b/pkg/sentry/fsimpl/devtmpfs/BUILD deleted file mode 100644 index e49a04c1b..000000000 --- a/pkg/sentry/fsimpl/devtmpfs/BUILD +++ /dev/null @@ -1,37 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -licenses(["notice"]) - -go_library( - name = "devtmpfs", - srcs = [ - "devtmpfs.go", - "save_restore.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/fspath", - "//pkg/sentry/fsimpl/tmpfs", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - "//pkg/sync", - ], -) - -go_test( - name = "devtmpfs_test", - size = "small", - srcs = ["devtmpfs_test.go"], - library = ":devtmpfs", - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/fspath", - "//pkg/sentry/contexttest", - "//pkg/sentry/fsimpl/tmpfs", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - ], -) diff --git a/pkg/sentry/fsimpl/devtmpfs/devtmpfs_state_autogen.go b/pkg/sentry/fsimpl/devtmpfs/devtmpfs_state_autogen.go new file mode 100644 index 000000000..900c7d8fe --- /dev/null +++ b/pkg/sentry/fsimpl/devtmpfs/devtmpfs_state_autogen.go @@ -0,0 +1,41 @@ +// automatically generated by stateify. + +package devtmpfs + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (fst *FilesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/devtmpfs.FilesystemType" +} + +func (fst *FilesystemType) StateFields() []string { + return []string{ + "initErr", + "fs", + "root", + } +} + +func (fst *FilesystemType) beforeSave() {} + +// +checklocksignore +func (fst *FilesystemType) StateSave(stateSinkObject state.Sink) { + fst.beforeSave() + stateSinkObject.Save(0, &fst.initErr) + stateSinkObject.Save(1, &fst.fs) + stateSinkObject.Save(2, &fst.root) +} + +// +checklocksignore +func (fst *FilesystemType) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fst.initErr) + stateSourceObject.Load(1, &fst.fs) + stateSourceObject.Load(2, &fst.root) + stateSourceObject.AfterLoad(fst.afterLoad) +} + +func init() { + state.Register((*FilesystemType)(nil)) +} diff --git a/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go b/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go deleted file mode 100644 index e058eda7a..000000000 --- a/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go +++ /dev/null @@ -1,230 +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 devtmpfs - -import ( - "path" - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -const devPath = "/dev" - -func setupDevtmpfs(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesystem, vfs.VirtualDentry, func()) { - t.Helper() - - ctx := contexttest.Context(t) - creds := auth.CredentialsFromContext(ctx) - vfsObj := &vfs.VirtualFilesystem{} - if err := vfsObj.Init(ctx); err != nil { - t.Fatalf("VFS init: %v", err) - } - // Register tmpfs just so that we can have a root filesystem that isn't - // devtmpfs. - vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - vfsObj.MustRegisterFilesystemType("devtmpfs", &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - - // Create a test mount namespace with devtmpfs mounted at "/dev". - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "tmpfs" /* source */, "tmpfs" /* fsTypeName */, &vfs.MountOptions{}) - if err != nil { - t.Fatalf("failed to create tmpfs root mount: %v", err) - } - root := mntns.Root() - root.IncRef() - devpop := vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(devPath), - } - if err := vfsObj.MkdirAt(ctx, creds, &devpop, &vfs.MkdirOptions{ - Mode: 0755, - }); err != nil { - t.Fatalf("failed to create mount point: %v", err) - } - if _, err := vfsObj.MountAt(ctx, creds, "devtmpfs" /* source */, &devpop, "devtmpfs" /* fsTypeName */, &vfs.MountOptions{}); err != nil { - t.Fatalf("failed to mount devtmpfs: %v", err) - } - - return ctx, creds, vfsObj, root, func() { - root.DecRef(ctx) - mntns.DecRef(ctx) - } -} - -func TestUserspaceInit(t *testing.T) { - ctx, creds, vfsObj, root, cleanup := setupDevtmpfs(t) - defer cleanup() - - a, err := NewAccessor(ctx, vfsObj, creds, "devtmpfs") - if err != nil { - t.Fatalf("failed to create devtmpfs.Accessor: %v", err) - } - defer a.Release(ctx) - - // Create "userspace-initialized" files using a devtmpfs.Accessor. - if err := a.UserspaceInit(ctx); err != nil { - t.Fatalf("failed to userspace-initialize devtmpfs: %v", err) - } - - // Created files should be visible in the test mount namespace. - links := []struct { - source string - target string - }{ - { - source: "fd", - target: "/proc/self/fd", - }, - { - source: "stdin", - target: "/proc/self/fd/0", - }, - { - source: "stdout", - target: "/proc/self/fd/1", - }, - { - source: "stderr", - target: "/proc/self/fd/2", - }, - { - source: "ptmx", - target: "pts/ptmx", - }, - } - - for _, link := range links { - abspath := path.Join(devPath, link.source) - if gotTarget, err := vfsObj.ReadlinkAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(abspath), - }); err != nil || gotTarget != link.target { - t.Errorf("readlink(%q): got (%q, %v), wanted (%q, nil)", abspath, gotTarget, err, link.target) - } - } - - dirs := []string{"shm", "pts"} - for _, dir := range dirs { - abspath := path.Join(devPath, dir) - statx, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(abspath), - }, &vfs.StatOptions{ - Mask: linux.STATX_MODE, - }) - if err != nil { - t.Errorf("stat(%q): got error %v ", abspath, err) - continue - } - if want := uint16(0755) | linux.S_IFDIR; statx.Mode != want { - t.Errorf("stat(%q): got mode %x, want %x", abspath, statx.Mode, want) - } - } -} - -func TestCreateDeviceFile(t *testing.T) { - ctx, creds, vfsObj, root, cleanup := setupDevtmpfs(t) - defer cleanup() - - a, err := NewAccessor(ctx, vfsObj, creds, "devtmpfs") - if err != nil { - t.Fatalf("failed to create devtmpfs.Accessor: %v", err) - } - defer a.Release(ctx) - - devFiles := []struct { - path string - kind vfs.DeviceKind - major uint32 - minor uint32 - perms uint16 - }{ - { - path: "dummy", - kind: vfs.CharDevice, - major: 12, - minor: 34, - perms: 0600, - }, - { - path: "foo/bar", - kind: vfs.BlockDevice, - major: 13, - minor: 35, - perms: 0660, - }, - { - path: "foo/baz", - kind: vfs.CharDevice, - major: 12, - minor: 40, - perms: 0666, - }, - { - path: "a/b/c/d/e", - kind: vfs.BlockDevice, - major: 12, - minor: 34, - perms: 0600, - }, - } - - for _, f := range devFiles { - if err := a.CreateDeviceFile(ctx, f.path, f.kind, f.major, f.minor, f.perms); err != nil { - t.Fatalf("failed to create device file: %v", err) - } - // The device special file should be visible in the test mount namespace. - abspath := path.Join(devPath, f.path) - stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(abspath), - }, &vfs.StatOptions{ - Mask: linux.STATX_TYPE | linux.STATX_MODE, - }) - if err != nil { - t.Fatalf("failed to stat device file at %q: %v", abspath, err) - } - if stat.RdevMajor != f.major { - t.Errorf("major device number: got %v, wanted %v", stat.RdevMajor, f.major) - } - if stat.RdevMinor != f.minor { - t.Errorf("minor device number: got %v, wanted %v", stat.RdevMinor, f.minor) - } - wantMode := f.perms - switch f.kind { - case vfs.CharDevice: - wantMode |= linux.S_IFCHR - case vfs.BlockDevice: - wantMode |= linux.S_IFBLK - } - if stat.Mode != wantMode { - t.Errorf("device file mode: got %v, wanted %v", stat.Mode, wantMode) - } - } -} diff --git a/pkg/sentry/fsimpl/eventfd/BUILD b/pkg/sentry/fsimpl/eventfd/BUILD deleted file mode 100644 index 1cb049a29..000000000 --- a/pkg/sentry/fsimpl/eventfd/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -licenses(["notice"]) - -go_library( - name = "eventfd", - srcs = ["eventfd.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fdnotifier", - "//pkg/hostarch", - "//pkg/log", - "//pkg/sentry/vfs", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "eventfd_test", - size = "small", - srcs = ["eventfd_test.go"], - library = ":eventfd", - deps = [ - "//pkg/abi/linux", - "//pkg/sentry/contexttest", - "//pkg/sentry/vfs", - "//pkg/usermem", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/fsimpl/eventfd/eventfd_state_autogen.go b/pkg/sentry/fsimpl/eventfd/eventfd_state_autogen.go new file mode 100644 index 000000000..93de7f32e --- /dev/null +++ b/pkg/sentry/fsimpl/eventfd/eventfd_state_autogen.go @@ -0,0 +1,57 @@ +// automatically generated by stateify. + +package eventfd + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (efd *EventFileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/eventfd.EventFileDescription" +} + +func (efd *EventFileDescription) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "DentryMetadataFileDescriptionImpl", + "NoLockFD", + "queue", + "val", + "semMode", + "hostfd", + } +} + +func (efd *EventFileDescription) beforeSave() {} + +// +checklocksignore +func (efd *EventFileDescription) StateSave(stateSinkObject state.Sink) { + efd.beforeSave() + stateSinkObject.Save(0, &efd.vfsfd) + stateSinkObject.Save(1, &efd.FileDescriptionDefaultImpl) + stateSinkObject.Save(2, &efd.DentryMetadataFileDescriptionImpl) + stateSinkObject.Save(3, &efd.NoLockFD) + stateSinkObject.Save(4, &efd.queue) + stateSinkObject.Save(5, &efd.val) + stateSinkObject.Save(6, &efd.semMode) + stateSinkObject.Save(7, &efd.hostfd) +} + +func (efd *EventFileDescription) afterLoad() {} + +// +checklocksignore +func (efd *EventFileDescription) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &efd.vfsfd) + stateSourceObject.Load(1, &efd.FileDescriptionDefaultImpl) + stateSourceObject.Load(2, &efd.DentryMetadataFileDescriptionImpl) + stateSourceObject.Load(3, &efd.NoLockFD) + stateSourceObject.Load(4, &efd.queue) + stateSourceObject.Load(5, &efd.val) + stateSourceObject.Load(6, &efd.semMode) + stateSourceObject.Load(7, &efd.hostfd) +} + +func init() { + state.Register((*EventFileDescription)(nil)) +} diff --git a/pkg/sentry/fsimpl/eventfd/eventfd_test.go b/pkg/sentry/fsimpl/eventfd/eventfd_test.go deleted file mode 100644 index 85718f813..000000000 --- a/pkg/sentry/fsimpl/eventfd/eventfd_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2018 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 eventfd - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -func TestEventFD(t *testing.T) { - initVals := []uint64{ - 0, - // Using a non-zero initial value verifies that writing to an - // eventfd signals when the eventfd's counter was already - // non-zero. - 343, - } - - for _, initVal := range initVals { - ctx := contexttest.Context(t) - vfsObj := &vfs.VirtualFilesystem{} - if err := vfsObj.Init(ctx); err != nil { - t.Fatalf("VFS init: %v", err) - } - - // Make a new eventfd that is writable. - eventfd, err := New(ctx, vfsObj, initVal, false, linux.O_RDWR) - if err != nil { - t.Fatalf("New() failed: %v", err) - } - defer eventfd.DecRef(ctx) - - // Register a callback for a write event. - w, ch := waiter.NewChannelEntry(nil) - eventfd.EventRegister(&w, waiter.ReadableEvents) - defer eventfd.EventUnregister(&w) - - data := []byte("00000124") - // Create and submit a write request. - n, err := eventfd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{}) - if err != nil { - t.Fatal(err) - } - if n != 8 { - t.Errorf("eventfd.write wrote %d bytes, not full int64", n) - } - - // Check if the callback fired due to the write event. - select { - case <-ch: - default: - t.Errorf("Didn't get notified of EventIn after write") - } - } -} - -func TestEventFDStat(t *testing.T) { - ctx := contexttest.Context(t) - vfsObj := &vfs.VirtualFilesystem{} - if err := vfsObj.Init(ctx); err != nil { - t.Fatalf("VFS init: %v", err) - } - - // Make a new eventfd that is writable. - eventfd, err := New(ctx, vfsObj, 0, false, linux.O_RDWR) - if err != nil { - t.Fatalf("New() failed: %v", err) - } - defer eventfd.DecRef(ctx) - - statx, err := eventfd.Stat(ctx, vfs.StatOptions{ - Mask: linux.STATX_BASIC_STATS, - }) - if err != nil { - t.Fatalf("eventfd.Stat failed: %v", err) - } - if statx.Size != 0 { - t.Errorf("eventfd size should be 0") - } -} diff --git a/pkg/sentry/fsimpl/ext/BUILD b/pkg/sentry/fsimpl/ext/BUILD deleted file mode 100644 index e69de29bb..000000000 --- a/pkg/sentry/fsimpl/ext/BUILD +++ /dev/null diff --git a/pkg/sentry/fsimpl/fuse/BUILD b/pkg/sentry/fsimpl/fuse/BUILD deleted file mode 100644 index 05c4fbeb2..000000000 --- a/pkg/sentry/fsimpl/fuse/BUILD +++ /dev/null @@ -1,90 +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 = "request_list", - out = "request_list.go", - package = "fuse", - prefix = "request", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*Request", - "Linker": "*Request", - }, -) - -go_template_instance( - name = "inode_refs", - out = "inode_refs.go", - package = "fuse", - prefix = "inode", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "inode", - }, -) - -go_library( - name = "fuse", - srcs = [ - "connection.go", - "connection_control.go", - "dev.go", - "directory.go", - "file.go", - "fusefs.go", - "inode_refs.go", - "read_write.go", - "register.go", - "regular_file.go", - "request_list.go", - "request_response.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/log", - "//pkg/marshal", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/safemem", - "//pkg/sentry/fsimpl/devtmpfs", - "//pkg/sentry/fsimpl/kernfs", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "fuse_test", - size = "small", - srcs = [ - "connection_test.go", - "dev_test.go", - "utils_test.go", - ], - library = ":fuse", - deps = [ - "//pkg/abi/linux", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/marshal", - "//pkg/sentry/fsimpl/testutil", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/pkg/sentry/fsimpl/fuse/connection_test.go b/pkg/sentry/fsimpl/fuse/connection_test.go deleted file mode 100644 index 1fddd858e..000000000 --- a/pkg/sentry/fsimpl/fuse/connection_test.go +++ /dev/null @@ -1,111 +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 fuse - -import ( - "math/rand" - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" -) - -// TestConnectionInitBlock tests if initialization -// correctly blocks and unblocks the connection. -// Since it's unfeasible to test kernelTask.Block() in unit test, -// the code in Call() are not tested here. -func TestConnectionInitBlock(t *testing.T) { - s := setup(t) - defer s.Destroy() - - k := kernel.KernelFromContext(s.Ctx) - - conn, _, err := newTestConnection(s, k, maxActiveRequestsDefault) - if err != nil { - t.Fatalf("newTestConnection: %v", err) - } - - select { - case <-conn.initializedChan: - t.Fatalf("initializedChan should be blocking before SetInitialized") - default: - } - - conn.SetInitialized() - - select { - case <-conn.initializedChan: - default: - t.Fatalf("initializedChan should not be blocking after SetInitialized") - } -} - -func TestConnectionAbort(t *testing.T) { - s := setup(t) - defer s.Destroy() - - k := kernel.KernelFromContext(s.Ctx) - creds := auth.CredentialsFromContext(s.Ctx) - task := kernel.TaskFromContext(s.Ctx) - - const numRequests uint64 = 256 - - conn, _, err := newTestConnection(s, k, numRequests) - if err != nil { - t.Fatalf("newTestConnection: %v", err) - } - - testObj := &testPayload{ - data: rand.Uint32(), - } - - var futNormal []*futureResponse - - for i := 0; i < int(numRequests); i++ { - req := conn.NewRequest(creds, uint32(i), uint64(i), 0, testObj) - fut, err := conn.callFutureLocked(task, req) - if err != nil { - t.Fatalf("callFutureLocked failed: %v", err) - } - futNormal = append(futNormal, fut) - } - - conn.Abort(s.Ctx) - - // Abort should unblock the initialization channel. - // Note: no test requests are actually blocked on `conn.initializedChan`. - select { - case <-conn.initializedChan: - default: - t.Fatalf("initializedChan should not be blocking after SetInitialized") - } - - // Abort will return ECONNABORTED error to unblocked requests. - for _, fut := range futNormal { - if fut.getResponse().hdr.Error != -int32(unix.ECONNABORTED) { - t.Fatalf("Incorrect error code received for aborted connection: %v", fut.getResponse().hdr.Error) - } - } - - // After abort, Call() should return directly with ENOTCONN. - req := conn.NewRequest(creds, 0, 0, 0, testObj) - _, err = conn.Call(task, req) - if !linuxerr.Equals(linuxerr.ENOTCONN, err) { - t.Fatalf("Incorrect error code received for Call() after connection aborted") - } - -} diff --git a/pkg/sentry/fsimpl/fuse/dev_test.go b/pkg/sentry/fsimpl/fuse/dev_test.go deleted file mode 100644 index 8951b5ba8..000000000 --- a/pkg/sentry/fsimpl/fuse/dev_test.go +++ /dev/null @@ -1,320 +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 fuse - -import ( - "fmt" - "math/rand" - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "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" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// echoTestOpcode is the Opcode used during testing. The server used in tests -// will simply echo the payload back with the appropriate headers. -const echoTestOpcode linux.FUSEOpcode = 1000 - -// TestFUSECommunication tests that the communication layer between the Sentry and the -// FUSE server daemon works as expected. -func TestFUSECommunication(t *testing.T) { - s := setup(t) - defer s.Destroy() - - k := kernel.KernelFromContext(s.Ctx) - creds := auth.CredentialsFromContext(s.Ctx) - - // Create test cases with different number of concurrent clients and servers. - testCases := []struct { - Name string - NumClients int - NumServers int - MaxActiveRequests uint64 - }{ - { - Name: "SingleClientSingleServer", - NumClients: 1, - NumServers: 1, - MaxActiveRequests: maxActiveRequestsDefault, - }, - { - Name: "SingleClientMultipleServers", - NumClients: 1, - NumServers: 10, - MaxActiveRequests: maxActiveRequestsDefault, - }, - { - Name: "MultipleClientsSingleServer", - NumClients: 10, - NumServers: 1, - MaxActiveRequests: maxActiveRequestsDefault, - }, - { - Name: "MultipleClientsMultipleServers", - NumClients: 10, - NumServers: 10, - MaxActiveRequests: maxActiveRequestsDefault, - }, - { - Name: "RequestCapacityFull", - NumClients: 10, - NumServers: 1, - MaxActiveRequests: 1, - }, - { - Name: "RequestCapacityContinuouslyFull", - NumClients: 100, - NumServers: 2, - MaxActiveRequests: 2, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - conn, fd, err := newTestConnection(s, k, testCase.MaxActiveRequests) - if err != nil { - t.Fatalf("newTestConnection: %v", err) - } - - clientsDone := make([]chan struct{}, testCase.NumClients) - serversDone := make([]chan struct{}, testCase.NumServers) - serversKill := make([]chan struct{}, testCase.NumServers) - - // FUSE clients. - for i := 0; i < testCase.NumClients; i++ { - clientsDone[i] = make(chan struct{}) - go func(i int) { - fuseClientRun(t, s, k, conn, creds, uint32(i), uint64(i), clientsDone[i]) - }(i) - } - - // FUSE servers. - for j := 0; j < testCase.NumServers; j++ { - serversDone[j] = make(chan struct{}) - serversKill[j] = make(chan struct{}, 1) // The kill command shouldn't block. - go func(j int) { - fuseServerRun(t, s, k, fd, serversDone[j], serversKill[j]) - }(j) - } - - // Tear down. - // - // Make sure all the clients are done. - for i := 0; i < testCase.NumClients; i++ { - <-clientsDone[i] - } - - // Kill any server that is potentially waiting. - for j := 0; j < testCase.NumServers; j++ { - serversKill[j] <- struct{}{} - } - - // Make sure all the servers are done. - for j := 0; j < testCase.NumServers; j++ { - <-serversDone[j] - } - }) - } -} - -// CallTest makes a request to the server and blocks the invoking -// goroutine until a server responds with a response. Doesn't block -// a kernel.Task. Analogous to Connection.Call but used for testing. -func CallTest(conn *connection, t *kernel.Task, r *Request, i uint32) (*Response, error) { - conn.fd.mu.Lock() - - // Wait until we're certain that a new request can be processed. - for conn.fd.numActiveRequests == conn.fd.fs.opts.maxActiveRequests { - conn.fd.mu.Unlock() - select { - case <-conn.fd.fullQueueCh: - } - conn.fd.mu.Lock() - } - - fut, err := conn.callFutureLocked(t, r) // No task given. - conn.fd.mu.Unlock() - - if err != nil { - return nil, err - } - - // Resolve the response. - // - // Block without a task. - select { - case <-fut.ch: - } - - // A response is ready. Resolve and return it. - return fut.getResponse(), nil -} - -// ReadTest is analogous to vfs.FileDescription.Read and reads from the FUSE -// device. However, it does so by - not blocking the task that is calling - and -// instead just waits on a channel. The behaviour is essentially the same as -// DeviceFD.Read except it guarantees that the task is not blocked. -func ReadTest(serverTask *kernel.Task, fd *vfs.FileDescription, inIOseq usermem.IOSequence, killServer chan struct{}) (int64, bool, error) { - var err error - var n, total int64 - - dev := fd.Impl().(*DeviceFD) - - // Register for notifications. - w, ch := waiter.NewChannelEntry(nil) - dev.EventRegister(&w, waiter.ReadableEvents) - for { - // Issue the request and break out if it completes with anything other than - // "would block". - n, err = dev.Read(serverTask, inIOseq, vfs.ReadOptions{}) - total += n - if err != linuxerr.ErrWouldBlock { - break - } - - // Wait for a notification that we should retry. - // Emulate the blocking for when no requests are available - select { - case <-ch: - case <-killServer: - // Server killed by the main program. - return 0, true, nil - } - } - - dev.EventUnregister(&w) - return total, false, err -} - -// fuseClientRun emulates all the actions of a normal FUSE request. It creates -// a header, a payload, calls the server, waits for the response, and processes -// the response. -func fuseClientRun(t *testing.T, s *testutil.System, k *kernel.Kernel, conn *connection, creds *auth.Credentials, pid uint32, inode uint64, clientDone chan struct{}) { - defer func() { clientDone <- struct{}{} }() - - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) - clientTask, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("fuse-client-%v", pid), tc, s.MntNs, s.Root, s.Root) - if err != nil { - t.Fatal(err) - } - testObj := &testPayload{ - data: rand.Uint32(), - } - - req := conn.NewRequest(creds, pid, inode, echoTestOpcode, testObj) - - // Queue up a request. - // Analogous to Call except it doesn't block on the task. - resp, err := CallTest(conn, clientTask, req, pid) - if err != nil { - t.Fatalf("CallTaskNonBlock failed: %v", err) - } - - if err = resp.Error(); err != nil { - t.Fatalf("Server responded with an error: %v", err) - } - - var respTestPayload testPayload - if err := resp.UnmarshalPayload(&respTestPayload); err != nil { - t.Fatalf("Unmarshalling payload error: %v", err) - } - - if resp.hdr.Unique != req.hdr.Unique { - t.Fatalf("got response for another request. Expected response for req %v but got response for req %v", - req.hdr.Unique, resp.hdr.Unique) - } - - if respTestPayload.data != testObj.data { - t.Fatalf("read incorrect data. Data expected: %v, but got %v", testObj.data, respTestPayload.data) - } - -} - -// fuseServerRun creates a task and emulates all the actions of a simple FUSE server -// that simply reads a request and echos the same struct back as a response using the -// appropriate headers. -func fuseServerRun(t *testing.T, s *testutil.System, k *kernel.Kernel, fd *vfs.FileDescription, serverDone, killServer chan struct{}) { - defer func() { serverDone <- struct{}{} }() - - // Create the tasks that the server will be using. - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) - var readPayload testPayload - - serverTask, err := testutil.CreateTask(s.Ctx, "fuse-server", tc, s.MntNs, s.Root, s.Root) - if err != nil { - t.Fatal(err) - } - - // Read the request. - for { - inHdrLen := uint32((*linux.FUSEHeaderIn)(nil).SizeBytes()) - payloadLen := uint32(readPayload.SizeBytes()) - - // The raed buffer must meet some certain size criteria. - buffSize := inHdrLen + payloadLen - if buffSize < linux.FUSE_MIN_READ_BUFFER { - buffSize = linux.FUSE_MIN_READ_BUFFER - } - inBuf := make([]byte, buffSize) - inIOseq := usermem.BytesIOSequence(inBuf) - - n, serverKilled, err := ReadTest(serverTask, fd, inIOseq, killServer) - if err != nil { - t.Fatalf("Read failed :%v", err) - } - - // Server should shut down. No new requests are going to be made. - if serverKilled { - break - } - - if n <= 0 { - t.Fatalf("Read read no bytes") - } - - var readFUSEHeaderIn linux.FUSEHeaderIn - readFUSEHeaderIn.UnmarshalUnsafe(inBuf[:inHdrLen]) - readPayload.UnmarshalUnsafe(inBuf[inHdrLen : inHdrLen+payloadLen]) - - if readFUSEHeaderIn.Opcode != echoTestOpcode { - t.Fatalf("read incorrect data. Header: %v, Payload: %v", readFUSEHeaderIn, readPayload) - } - - // Write the response. - outHdrLen := uint32((*linux.FUSEHeaderOut)(nil).SizeBytes()) - outBuf := make([]byte, outHdrLen+payloadLen) - outHeader := linux.FUSEHeaderOut{ - Len: outHdrLen + payloadLen, - Error: 0, - Unique: readFUSEHeaderIn.Unique, - } - - // Echo the payload back. - outHeader.MarshalUnsafe(outBuf[:outHdrLen]) - readPayload.MarshalUnsafe(outBuf[outHdrLen:]) - outIOseq := usermem.BytesIOSequence(outBuf) - - _, err = fd.Write(s.Ctx, outIOseq, vfs.WriteOptions{}) - if err != nil { - t.Fatalf("Write failed :%v", err) - } - } -} diff --git a/pkg/sentry/fsimpl/fuse/fuse_state_autogen.go b/pkg/sentry/fsimpl/fuse/fuse_state_autogen.go new file mode 100644 index 000000000..711a6c425 --- /dev/null +++ b/pkg/sentry/fsimpl/fuse/fuse_state_autogen.go @@ -0,0 +1,562 @@ +// automatically generated by stateify. + +package fuse + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (conn *connection) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.connection" +} + +func (conn *connection) StateFields() []string { + return []string{ + "fd", + "attributeVersion", + "initialized", + "initializedChan", + "connected", + "connInitError", + "connInitSuccess", + "aborted", + "numWaiting", + "asyncNum", + "asyncCongestionThreshold", + "asyncNumMax", + "maxRead", + "maxWrite", + "maxPages", + "minor", + "atomicOTrunc", + "asyncRead", + "writebackCache", + "bigWrites", + "dontMask", + "noOpen", + } +} + +func (conn *connection) beforeSave() {} + +// +checklocksignore +func (conn *connection) StateSave(stateSinkObject state.Sink) { + conn.beforeSave() + var initializedChanValue bool + initializedChanValue = conn.saveInitializedChan() + stateSinkObject.SaveValue(3, initializedChanValue) + stateSinkObject.Save(0, &conn.fd) + stateSinkObject.Save(1, &conn.attributeVersion) + stateSinkObject.Save(2, &conn.initialized) + stateSinkObject.Save(4, &conn.connected) + stateSinkObject.Save(5, &conn.connInitError) + stateSinkObject.Save(6, &conn.connInitSuccess) + stateSinkObject.Save(7, &conn.aborted) + stateSinkObject.Save(8, &conn.numWaiting) + stateSinkObject.Save(9, &conn.asyncNum) + stateSinkObject.Save(10, &conn.asyncCongestionThreshold) + stateSinkObject.Save(11, &conn.asyncNumMax) + stateSinkObject.Save(12, &conn.maxRead) + stateSinkObject.Save(13, &conn.maxWrite) + stateSinkObject.Save(14, &conn.maxPages) + stateSinkObject.Save(15, &conn.minor) + stateSinkObject.Save(16, &conn.atomicOTrunc) + stateSinkObject.Save(17, &conn.asyncRead) + stateSinkObject.Save(18, &conn.writebackCache) + stateSinkObject.Save(19, &conn.bigWrites) + stateSinkObject.Save(20, &conn.dontMask) + stateSinkObject.Save(21, &conn.noOpen) +} + +func (conn *connection) afterLoad() {} + +// +checklocksignore +func (conn *connection) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &conn.fd) + stateSourceObject.Load(1, &conn.attributeVersion) + stateSourceObject.Load(2, &conn.initialized) + stateSourceObject.Load(4, &conn.connected) + stateSourceObject.Load(5, &conn.connInitError) + stateSourceObject.Load(6, &conn.connInitSuccess) + stateSourceObject.Load(7, &conn.aborted) + stateSourceObject.Load(8, &conn.numWaiting) + stateSourceObject.Load(9, &conn.asyncNum) + stateSourceObject.Load(10, &conn.asyncCongestionThreshold) + stateSourceObject.Load(11, &conn.asyncNumMax) + stateSourceObject.Load(12, &conn.maxRead) + stateSourceObject.Load(13, &conn.maxWrite) + stateSourceObject.Load(14, &conn.maxPages) + stateSourceObject.Load(15, &conn.minor) + stateSourceObject.Load(16, &conn.atomicOTrunc) + stateSourceObject.Load(17, &conn.asyncRead) + stateSourceObject.Load(18, &conn.writebackCache) + stateSourceObject.Load(19, &conn.bigWrites) + stateSourceObject.Load(20, &conn.dontMask) + stateSourceObject.Load(21, &conn.noOpen) + stateSourceObject.LoadValue(3, new(bool), func(y interface{}) { conn.loadInitializedChan(y.(bool)) }) +} + +func (f *fuseDevice) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.fuseDevice" +} + +func (f *fuseDevice) StateFields() []string { + return []string{} +} + +func (f *fuseDevice) beforeSave() {} + +// +checklocksignore +func (f *fuseDevice) StateSave(stateSinkObject state.Sink) { + f.beforeSave() +} + +func (f *fuseDevice) afterLoad() {} + +// +checklocksignore +func (f *fuseDevice) StateLoad(stateSourceObject state.Source) { +} + +func (fd *DeviceFD) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.DeviceFD" +} + +func (fd *DeviceFD) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "DentryMetadataFileDescriptionImpl", + "NoLockFD", + "nextOpID", + "queue", + "numActiveRequests", + "completions", + "writeCursor", + "writeBuf", + "writeCursorFR", + "waitQueue", + "fullQueueCh", + "fs", + } +} + +func (fd *DeviceFD) beforeSave() {} + +// +checklocksignore +func (fd *DeviceFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + var fullQueueChValue int + fullQueueChValue = fd.saveFullQueueCh() + stateSinkObject.SaveValue(12, fullQueueChValue) + stateSinkObject.Save(0, &fd.vfsfd) + stateSinkObject.Save(1, &fd.FileDescriptionDefaultImpl) + stateSinkObject.Save(2, &fd.DentryMetadataFileDescriptionImpl) + stateSinkObject.Save(3, &fd.NoLockFD) + stateSinkObject.Save(4, &fd.nextOpID) + stateSinkObject.Save(5, &fd.queue) + stateSinkObject.Save(6, &fd.numActiveRequests) + stateSinkObject.Save(7, &fd.completions) + stateSinkObject.Save(8, &fd.writeCursor) + stateSinkObject.Save(9, &fd.writeBuf) + stateSinkObject.Save(10, &fd.writeCursorFR) + stateSinkObject.Save(11, &fd.waitQueue) + stateSinkObject.Save(13, &fd.fs) +} + +func (fd *DeviceFD) afterLoad() {} + +// +checklocksignore +func (fd *DeviceFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.vfsfd) + stateSourceObject.Load(1, &fd.FileDescriptionDefaultImpl) + stateSourceObject.Load(2, &fd.DentryMetadataFileDescriptionImpl) + stateSourceObject.Load(3, &fd.NoLockFD) + stateSourceObject.Load(4, &fd.nextOpID) + stateSourceObject.Load(5, &fd.queue) + stateSourceObject.Load(6, &fd.numActiveRequests) + stateSourceObject.Load(7, &fd.completions) + stateSourceObject.Load(8, &fd.writeCursor) + stateSourceObject.Load(9, &fd.writeBuf) + stateSourceObject.Load(10, &fd.writeCursorFR) + stateSourceObject.Load(11, &fd.waitQueue) + stateSourceObject.Load(13, &fd.fs) + stateSourceObject.LoadValue(12, new(int), func(y interface{}) { fd.loadFullQueueCh(y.(int)) }) +} + +func (fsType *FilesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.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 (f *filesystemOptions) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.filesystemOptions" +} + +func (f *filesystemOptions) StateFields() []string { + return []string{ + "mopts", + "uid", + "gid", + "rootMode", + "maxActiveRequests", + "maxRead", + "defaultPermissions", + "allowOther", + } +} + +func (f *filesystemOptions) beforeSave() {} + +// +checklocksignore +func (f *filesystemOptions) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.mopts) + stateSinkObject.Save(1, &f.uid) + stateSinkObject.Save(2, &f.gid) + stateSinkObject.Save(3, &f.rootMode) + stateSinkObject.Save(4, &f.maxActiveRequests) + stateSinkObject.Save(5, &f.maxRead) + stateSinkObject.Save(6, &f.defaultPermissions) + stateSinkObject.Save(7, &f.allowOther) +} + +func (f *filesystemOptions) afterLoad() {} + +// +checklocksignore +func (f *filesystemOptions) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.mopts) + stateSourceObject.Load(1, &f.uid) + stateSourceObject.Load(2, &f.gid) + stateSourceObject.Load(3, &f.rootMode) + stateSourceObject.Load(4, &f.maxActiveRequests) + stateSourceObject.Load(5, &f.maxRead) + stateSourceObject.Load(6, &f.defaultPermissions) + stateSourceObject.Load(7, &f.allowOther) +} + +func (fs *filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "Filesystem", + "devMinor", + "conn", + "opts", + "umounted", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Filesystem) + stateSinkObject.Save(1, &fs.devMinor) + stateSinkObject.Save(2, &fs.conn) + stateSinkObject.Save(3, &fs.opts) + stateSinkObject.Save(4, &fs.umounted) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Filesystem) + stateSourceObject.Load(1, &fs.devMinor) + stateSourceObject.Load(2, &fs.conn) + stateSourceObject.Load(3, &fs.opts) + stateSourceObject.Load(4, &fs.umounted) +} + +func (i *inode) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.inode" +} + +func (i *inode) StateFields() []string { + return []string{ + "inodeRefs", + "InodeAlwaysValid", + "InodeAttrs", + "InodeDirectoryNoNewChildren", + "InodeNotSymlink", + "OrderedChildren", + "fs", + "metadataMu", + "nodeID", + "locks", + "size", + "attributeVersion", + "attributeTime", + "version", + "link", + } +} + +func (i *inode) beforeSave() {} + +// +checklocksignore +func (i *inode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.inodeRefs) + stateSinkObject.Save(1, &i.InodeAlwaysValid) + stateSinkObject.Save(2, &i.InodeAttrs) + stateSinkObject.Save(3, &i.InodeDirectoryNoNewChildren) + stateSinkObject.Save(4, &i.InodeNotSymlink) + stateSinkObject.Save(5, &i.OrderedChildren) + stateSinkObject.Save(6, &i.fs) + stateSinkObject.Save(7, &i.metadataMu) + stateSinkObject.Save(8, &i.nodeID) + stateSinkObject.Save(9, &i.locks) + stateSinkObject.Save(10, &i.size) + stateSinkObject.Save(11, &i.attributeVersion) + stateSinkObject.Save(12, &i.attributeTime) + stateSinkObject.Save(13, &i.version) + stateSinkObject.Save(14, &i.link) +} + +func (i *inode) afterLoad() {} + +// +checklocksignore +func (i *inode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.inodeRefs) + stateSourceObject.Load(1, &i.InodeAlwaysValid) + stateSourceObject.Load(2, &i.InodeAttrs) + stateSourceObject.Load(3, &i.InodeDirectoryNoNewChildren) + stateSourceObject.Load(4, &i.InodeNotSymlink) + stateSourceObject.Load(5, &i.OrderedChildren) + stateSourceObject.Load(6, &i.fs) + stateSourceObject.Load(7, &i.metadataMu) + stateSourceObject.Load(8, &i.nodeID) + stateSourceObject.Load(9, &i.locks) + stateSourceObject.Load(10, &i.size) + stateSourceObject.Load(11, &i.attributeVersion) + stateSourceObject.Load(12, &i.attributeTime) + stateSourceObject.Load(13, &i.version) + stateSourceObject.Load(14, &i.link) +} + +func (r *inodeRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.inodeRefs" +} + +func (r *inodeRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *inodeRefs) beforeSave() {} + +// +checklocksignore +func (r *inodeRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *inodeRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (l *requestList) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.requestList" +} + +func (l *requestList) StateFields() []string { + return []string{ + "head", + "tail", + } +} + +func (l *requestList) beforeSave() {} + +// +checklocksignore +func (l *requestList) StateSave(stateSinkObject state.Sink) { + l.beforeSave() + stateSinkObject.Save(0, &l.head) + stateSinkObject.Save(1, &l.tail) +} + +func (l *requestList) afterLoad() {} + +// +checklocksignore +func (l *requestList) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &l.head) + stateSourceObject.Load(1, &l.tail) +} + +func (e *requestEntry) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.requestEntry" +} + +func (e *requestEntry) StateFields() []string { + return []string{ + "next", + "prev", + } +} + +func (e *requestEntry) beforeSave() {} + +// +checklocksignore +func (e *requestEntry) StateSave(stateSinkObject state.Sink) { + e.beforeSave() + stateSinkObject.Save(0, &e.next) + stateSinkObject.Save(1, &e.prev) +} + +func (e *requestEntry) afterLoad() {} + +// +checklocksignore +func (e *requestEntry) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &e.next) + stateSourceObject.Load(1, &e.prev) +} + +func (r *Request) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.Request" +} + +func (r *Request) StateFields() []string { + return []string{ + "requestEntry", + "id", + "hdr", + "data", + "payload", + "async", + "noReply", + } +} + +func (r *Request) beforeSave() {} + +// +checklocksignore +func (r *Request) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.requestEntry) + stateSinkObject.Save(1, &r.id) + stateSinkObject.Save(2, &r.hdr) + stateSinkObject.Save(3, &r.data) + stateSinkObject.Save(4, &r.payload) + stateSinkObject.Save(5, &r.async) + stateSinkObject.Save(6, &r.noReply) +} + +func (r *Request) afterLoad() {} + +// +checklocksignore +func (r *Request) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.requestEntry) + stateSourceObject.Load(1, &r.id) + stateSourceObject.Load(2, &r.hdr) + stateSourceObject.Load(3, &r.data) + stateSourceObject.Load(4, &r.payload) + stateSourceObject.Load(5, &r.async) + stateSourceObject.Load(6, &r.noReply) +} + +func (f *futureResponse) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.futureResponse" +} + +func (f *futureResponse) StateFields() []string { + return []string{ + "opcode", + "ch", + "hdr", + "data", + "async", + } +} + +func (f *futureResponse) beforeSave() {} + +// +checklocksignore +func (f *futureResponse) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.opcode) + stateSinkObject.Save(1, &f.ch) + stateSinkObject.Save(2, &f.hdr) + stateSinkObject.Save(3, &f.data) + stateSinkObject.Save(4, &f.async) +} + +func (f *futureResponse) afterLoad() {} + +// +checklocksignore +func (f *futureResponse) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.opcode) + stateSourceObject.Load(1, &f.ch) + stateSourceObject.Load(2, &f.hdr) + stateSourceObject.Load(3, &f.data) + stateSourceObject.Load(4, &f.async) +} + +func (r *Response) StateTypeName() string { + return "pkg/sentry/fsimpl/fuse.Response" +} + +func (r *Response) StateFields() []string { + return []string{ + "opcode", + "hdr", + "data", + } +} + +func (r *Response) beforeSave() {} + +// +checklocksignore +func (r *Response) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.opcode) + stateSinkObject.Save(1, &r.hdr) + stateSinkObject.Save(2, &r.data) +} + +func (r *Response) afterLoad() {} + +// +checklocksignore +func (r *Response) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.opcode) + stateSourceObject.Load(1, &r.hdr) + stateSourceObject.Load(2, &r.data) +} + +func init() { + state.Register((*connection)(nil)) + state.Register((*fuseDevice)(nil)) + state.Register((*DeviceFD)(nil)) + state.Register((*FilesystemType)(nil)) + state.Register((*filesystemOptions)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*inode)(nil)) + state.Register((*inodeRefs)(nil)) + state.Register((*requestList)(nil)) + state.Register((*requestEntry)(nil)) + state.Register((*Request)(nil)) + state.Register((*futureResponse)(nil)) + state.Register((*Response)(nil)) +} diff --git a/pkg/sentry/fsimpl/fuse/inode_refs.go b/pkg/sentry/fsimpl/fuse/inode_refs.go new file mode 100644 index 000000000..74489cf5e --- /dev/null +++ b/pkg/sentry/fsimpl/fuse/inode_refs.go @@ -0,0 +1,140 @@ +package fuse + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const inodeenableLogging = false + +// obj 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 inodeobj *inode + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type inodeRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *inodeRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *inodeRefs) RefType() string { + return fmt.Sprintf("%T", inodeobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *inodeRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *inodeRefs) LogRefs() bool { + return inodeenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *inodeRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *inodeRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if inodeenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *inodeRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if inodeenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *inodeRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if inodeenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *inodeRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/fuse/request_list.go b/pkg/sentry/fsimpl/fuse/request_list.go new file mode 100644 index 000000000..060ac4a3f --- /dev/null +++ b/pkg/sentry/fsimpl/fuse/request_list.go @@ -0,0 +1,221 @@ +package fuse + +// 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 requestElementMapper struct{} + +// linkerFor maps an Element to a Linker. +// +// This default implementation should be inlined. +// +//go:nosplit +func (requestElementMapper) linkerFor(elem *Request) *Request { 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 requestList struct { + head *Request + tail *Request +} + +// Reset resets list l to the empty state. +func (l *requestList) Reset() { + l.head = nil + l.tail = nil +} + +// Empty returns true iff the list is empty. +// +//go:nosplit +func (l *requestList) Empty() bool { + return l.head == nil +} + +// Front returns the first element of list l or nil. +// +//go:nosplit +func (l *requestList) Front() *Request { + return l.head +} + +// Back returns the last element of list l or nil. +// +//go:nosplit +func (l *requestList) Back() *Request { + return l.tail +} + +// Len returns the number of elements in the list. +// +// NOTE: This is an O(n) operation. +// +//go:nosplit +func (l *requestList) Len() (count int) { + for e := l.Front(); e != nil; e = (requestElementMapper{}.linkerFor(e)).Next() { + count++ + } + return count +} + +// PushFront inserts the element e at the front of list l. +// +//go:nosplit +func (l *requestList) PushFront(e *Request) { + linker := requestElementMapper{}.linkerFor(e) + linker.SetNext(l.head) + linker.SetPrev(nil) + if l.head != nil { + requestElementMapper{}.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 *requestList) PushBack(e *Request) { + linker := requestElementMapper{}.linkerFor(e) + linker.SetNext(nil) + linker.SetPrev(l.tail) + if l.tail != nil { + requestElementMapper{}.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 *requestList) PushBackList(m *requestList) { + if l.head == nil { + l.head = m.head + l.tail = m.tail + } else if m.head != nil { + requestElementMapper{}.linkerFor(l.tail).SetNext(m.head) + requestElementMapper{}.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 *requestList) InsertAfter(b, e *Request) { + bLinker := requestElementMapper{}.linkerFor(b) + eLinker := requestElementMapper{}.linkerFor(e) + + a := bLinker.Next() + + eLinker.SetNext(a) + eLinker.SetPrev(b) + bLinker.SetNext(e) + + if a != nil { + requestElementMapper{}.linkerFor(a).SetPrev(e) + } else { + l.tail = e + } +} + +// InsertBefore inserts e before a. +// +//go:nosplit +func (l *requestList) InsertBefore(a, e *Request) { + aLinker := requestElementMapper{}.linkerFor(a) + eLinker := requestElementMapper{}.linkerFor(e) + + b := aLinker.Prev() + eLinker.SetNext(a) + eLinker.SetPrev(b) + aLinker.SetPrev(e) + + if b != nil { + requestElementMapper{}.linkerFor(b).SetNext(e) + } else { + l.head = e + } +} + +// Remove removes e from l. +// +//go:nosplit +func (l *requestList) Remove(e *Request) { + linker := requestElementMapper{}.linkerFor(e) + prev := linker.Prev() + next := linker.Next() + + if prev != nil { + requestElementMapper{}.linkerFor(prev).SetNext(next) + } else if l.head == e { + l.head = next + } + + if next != nil { + requestElementMapper{}.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 requestEntry struct { + next *Request + prev *Request +} + +// Next returns the entry that follows e in the list. +// +//go:nosplit +func (e *requestEntry) Next() *Request { + return e.next +} + +// Prev returns the entry that precedes e in the list. +// +//go:nosplit +func (e *requestEntry) Prev() *Request { + return e.prev +} + +// SetNext assigns 'entry' as the entry that follows e in the list. +// +//go:nosplit +func (e *requestEntry) SetNext(elem *Request) { + e.next = elem +} + +// SetPrev assigns 'entry' as the entry that precedes e in the list. +// +//go:nosplit +func (e *requestEntry) SetPrev(elem *Request) { + e.prev = elem +} diff --git a/pkg/sentry/fsimpl/fuse/utils_test.go b/pkg/sentry/fsimpl/fuse/utils_test.go deleted file mode 100644 index b0bab0066..000000000 --- a/pkg/sentry/fsimpl/fuse/utils_test.go +++ /dev/null @@ -1,127 +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 fuse - -import ( - "io" - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/marshal" - "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" - - "gvisor.dev/gvisor/pkg/hostarch" -) - -func setup(t *testing.T) *testutil.System { - k, err := testutil.Boot() - if err != nil { - t.Fatalf("Error creating kernel: %v", err) - } - - ctx := k.SupervisorContext() - creds := auth.CredentialsFromContext(ctx) - - k.VFS().MustRegisterFilesystemType(Name, &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserList: true, - AllowUserMount: true, - }) - - mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) - if err != nil { - t.Fatalf("NewMountNamespace(): %v", err) - } - - return testutil.NewSystem(ctx, t, k.VFS(), mntns) -} - -// newTestConnection creates a fuse connection that the sentry can communicate with -// and the FD for the server to communicate with. -func newTestConnection(system *testutil.System, k *kernel.Kernel, maxActiveRequests uint64) (*connection, *vfs.FileDescription, error) { - fuseDev := &DeviceFD{} - - vd := system.VFS.NewAnonVirtualDentry("fuse") - defer vd.DecRef(system.Ctx) - if err := fuseDev.vfsfd.Init(fuseDev, linux.O_RDWR, vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{}); err != nil { - return nil, nil, err - } - - fsopts := filesystemOptions{ - maxActiveRequests: maxActiveRequests, - } - fs, err := newFUSEFilesystem(system.Ctx, system.VFS, &FilesystemType{}, fuseDev, 0, &fsopts) - if err != nil { - return nil, nil, err - } - return fs.conn, &fuseDev.vfsfd, nil -} - -type testPayload struct { - marshal.StubMarshallable - data uint32 -} - -// SizeBytes implements marshal.Marshallable.SizeBytes. -func (t *testPayload) SizeBytes() int { - return 4 -} - -// MarshalBytes implements marshal.Marshallable.MarshalBytes. -func (t *testPayload) MarshalBytes(dst []byte) { - hostarch.ByteOrder.PutUint32(dst[:4], t.data) -} - -// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes. -func (t *testPayload) UnmarshalBytes(src []byte) { - *t = testPayload{data: hostarch.ByteOrder.Uint32(src[:4])} -} - -// Packed implements marshal.Marshallable.Packed. -func (t *testPayload) Packed() bool { - return true -} - -// MarshalUnsafe implements marshal.Marshallable.MarshalUnsafe. -func (t *testPayload) MarshalUnsafe(dst []byte) { - t.MarshalBytes(dst) -} - -// UnmarshalUnsafe implements marshal.Marshallable.UnmarshalUnsafe. -func (t *testPayload) UnmarshalUnsafe(src []byte) { - t.UnmarshalBytes(src) -} - -// CopyOutN implements marshal.Marshallable.CopyOutN. -func (t *testPayload) CopyOutN(task marshal.CopyContext, addr hostarch.Addr, limit int) (int, error) { - panic("not implemented") -} - -// CopyOut implements marshal.Marshallable.CopyOut. -func (t *testPayload) CopyOut(task marshal.CopyContext, addr hostarch.Addr) (int, error) { - panic("not implemented") -} - -// CopyIn implements marshal.Marshallable.CopyIn. -func (t *testPayload) CopyIn(task marshal.CopyContext, addr hostarch.Addr) (int, error) { - panic("not implemented") -} - -// WriteTo implements io.WriterTo.WriteTo. -func (t *testPayload) WriteTo(w io.Writer) (int64, error) { - panic("not implemented") -} diff --git a/pkg/sentry/fsimpl/gofer/BUILD b/pkg/sentry/fsimpl/gofer/BUILD deleted file mode 100644 index 509dd0e1a..000000000 --- a/pkg/sentry/fsimpl/gofer/BUILD +++ /dev/null @@ -1,101 +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 = "gofer", - prefix = "dentry", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*dentry", - "Linker": "*dentry", - }, -) - -go_template_instance( - name = "fstree", - out = "fstree.go", - package = "gofer", - prefix = "generic", - template = "//pkg/sentry/vfs/genericfstree:generic_fstree", - types = { - "Dentry": "dentry", - }, -) - -go_library( - name = "gofer", - srcs = [ - "dentry_list.go", - "directory.go", - "filesystem.go", - "fstree.go", - "gofer.go", - "handle.go", - "host_named_pipe.go", - "p9file.go", - "regular_file.go", - "revalidate.go", - "save_restore.go", - "socket.go", - "special_file.go", - "symlink.go", - "time.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fd", - "//pkg/fdnotifier", - "//pkg/fspath", - "//pkg/hostarch", - "//pkg/lisafs", - "//pkg/log", - "//pkg/marshal", - "//pkg/marshal/primitive", - "//pkg/metric", - "//pkg/p9", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/safemem", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/lock", - "//pkg/sentry/fsimpl/host", - "//pkg/sentry/fsmetric", - "//pkg/sentry/hostfd", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/pipe", - "//pkg/sentry/kernel/time", - "//pkg/sentry/memmap", - "//pkg/sentry/pgalloc", - "//pkg/sentry/platform", - "//pkg/sentry/socket/control", - "//pkg/sentry/socket/unix", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/usage", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/syserr", - "//pkg/unet", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "gofer_test", - srcs = ["gofer_test.go"], - library = ":gofer", - deps = [ - "//pkg/p9", - "//pkg/sentry/contexttest", - "//pkg/sentry/pgalloc", - ], -) diff --git a/pkg/sentry/fsimpl/gofer/dentry_list.go b/pkg/sentry/fsimpl/gofer/dentry_list.go new file mode 100644 index 000000000..2e43b8e02 --- /dev/null +++ b/pkg/sentry/fsimpl/gofer/dentry_list.go @@ -0,0 +1,221 @@ +package gofer + +// 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/gofer/fstree.go b/pkg/sentry/fsimpl/gofer/fstree.go new file mode 100644 index 000000000..6e43d4a4b --- /dev/null +++ b/pkg/sentry/fsimpl/gofer/fstree.go @@ -0,0 +1,55 @@ +package gofer + +import ( + "gvisor.dev/gvisor/pkg/fspath" + "gvisor.dev/gvisor/pkg/sentry/vfs" +) + +// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is +// either d2's parent or an ancestor of d2's parent. +func genericIsAncestorDentry(d, d2 *dentry) bool { + for d2 != nil { + if d2.parent == d { + return true + } + if d2.parent == d2 { + return false + } + d2 = d2.parent + } + return false +} + +// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d. +func genericParentOrSelf(d *dentry) *dentry { + if d.parent != nil { + return d.parent + } + return d +} + +// PrependPath is a generic implementation of FilesystemImpl.PrependPath(). +func genericPrependPath(vfsroot vfs.VirtualDentry, mnt *vfs.Mount, d *dentry, b *fspath.Builder) error { + for { + if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() { + return vfs.PrependPathAtVFSRootError{} + } + if mnt != nil && &d.vfsd == mnt.Root() { + return nil + } + if d.parent == nil { + return vfs.PrependPathAtNonMountRootError{} + } + b.PrependComponent(d.name) + d = d.parent + } +} + +// DebugPathname returns a pathname to d relative to its filesystem root. +// DebugPathname does not correspond to any Linux function; it's used to +// generate dentry pathnames for debugging. +func genericDebugPathname(d *dentry) string { + var b fspath.Builder + _ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b) + return b.String() +} diff --git a/pkg/sentry/fsimpl/gofer/gofer_state_autogen.go b/pkg/sentry/fsimpl/gofer/gofer_state_autogen.go new file mode 100644 index 000000000..0e99b2c45 --- /dev/null +++ b/pkg/sentry/fsimpl/gofer/gofer_state_autogen.go @@ -0,0 +1,652 @@ +// automatically generated by stateify. + +package gofer + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (l *dentryList) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.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/gofer.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 (fd *directoryFD) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.directoryFD" +} + +func (fd *directoryFD) StateFields() []string { + return []string{ + "fileDescription", + "DirectoryFileDescriptionDefaultImpl", + "off", + "dirents", + } +} + +func (fd *directoryFD) beforeSave() {} + +// +checklocksignore +func (fd *directoryFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.fileDescription) + stateSinkObject.Save(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSinkObject.Save(2, &fd.off) + stateSinkObject.Save(3, &fd.dirents) +} + +func (fd *directoryFD) afterLoad() {} + +// +checklocksignore +func (fd *directoryFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.fileDescription) + stateSourceObject.Load(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSourceObject.Load(2, &fd.off) + stateSourceObject.Load(3, &fd.dirents) +} + +func (fstype *FilesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.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/gofer.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "vfsfs", + "mfp", + "opts", + "iopts", + "clock", + "devMinor", + "root", + "cachedDentries", + "cachedDentriesLen", + "syncableDentries", + "specialFileFDs", + "lastIno", + "savedDentryRW", + "released", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.vfsfs) + stateSinkObject.Save(1, &fs.mfp) + stateSinkObject.Save(2, &fs.opts) + stateSinkObject.Save(3, &fs.iopts) + stateSinkObject.Save(4, &fs.clock) + stateSinkObject.Save(5, &fs.devMinor) + stateSinkObject.Save(6, &fs.root) + stateSinkObject.Save(7, &fs.cachedDentries) + stateSinkObject.Save(8, &fs.cachedDentriesLen) + stateSinkObject.Save(9, &fs.syncableDentries) + stateSinkObject.Save(10, &fs.specialFileFDs) + stateSinkObject.Save(11, &fs.lastIno) + stateSinkObject.Save(12, &fs.savedDentryRW) + stateSinkObject.Save(13, &fs.released) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.vfsfs) + stateSourceObject.Load(1, &fs.mfp) + stateSourceObject.Load(2, &fs.opts) + stateSourceObject.Load(3, &fs.iopts) + stateSourceObject.Load(4, &fs.clock) + stateSourceObject.Load(5, &fs.devMinor) + stateSourceObject.Load(6, &fs.root) + stateSourceObject.Load(7, &fs.cachedDentries) + stateSourceObject.Load(8, &fs.cachedDentriesLen) + stateSourceObject.Load(9, &fs.syncableDentries) + stateSourceObject.Load(10, &fs.specialFileFDs) + stateSourceObject.Load(11, &fs.lastIno) + stateSourceObject.Load(12, &fs.savedDentryRW) + stateSourceObject.Load(13, &fs.released) +} + +func (f *filesystemOptions) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.filesystemOptions" +} + +func (f *filesystemOptions) StateFields() []string { + return []string{ + "fd", + "aname", + "interop", + "dfltuid", + "dfltgid", + "msize", + "version", + "maxCachedDentries", + "forcePageCache", + "limitHostFDTranslation", + "overlayfsStaleRead", + "regularFilesUseSpecialFileFD", + "lisaEnabled", + } +} + +func (f *filesystemOptions) beforeSave() {} + +// +checklocksignore +func (f *filesystemOptions) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.fd) + stateSinkObject.Save(1, &f.aname) + stateSinkObject.Save(2, &f.interop) + stateSinkObject.Save(3, &f.dfltuid) + stateSinkObject.Save(4, &f.dfltgid) + stateSinkObject.Save(5, &f.msize) + stateSinkObject.Save(6, &f.version) + stateSinkObject.Save(7, &f.maxCachedDentries) + stateSinkObject.Save(8, &f.forcePageCache) + stateSinkObject.Save(9, &f.limitHostFDTranslation) + stateSinkObject.Save(10, &f.overlayfsStaleRead) + stateSinkObject.Save(11, &f.regularFilesUseSpecialFileFD) + stateSinkObject.Save(12, &f.lisaEnabled) +} + +func (f *filesystemOptions) afterLoad() {} + +// +checklocksignore +func (f *filesystemOptions) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.fd) + stateSourceObject.Load(1, &f.aname) + stateSourceObject.Load(2, &f.interop) + stateSourceObject.Load(3, &f.dfltuid) + stateSourceObject.Load(4, &f.dfltgid) + stateSourceObject.Load(5, &f.msize) + stateSourceObject.Load(6, &f.version) + stateSourceObject.Load(7, &f.maxCachedDentries) + stateSourceObject.Load(8, &f.forcePageCache) + stateSourceObject.Load(9, &f.limitHostFDTranslation) + stateSourceObject.Load(10, &f.overlayfsStaleRead) + stateSourceObject.Load(11, &f.regularFilesUseSpecialFileFD) + stateSourceObject.Load(12, &f.lisaEnabled) +} + +func (i *InteropMode) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.InteropMode" +} + +func (i *InteropMode) StateFields() []string { + return nil +} + +func (i *InternalFilesystemOptions) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.InternalFilesystemOptions" +} + +func (i *InternalFilesystemOptions) StateFields() []string { + return []string{ + "UniqueID", + "LeakConnection", + "OpenSocketsByConnecting", + } +} + +func (i *InternalFilesystemOptions) beforeSave() {} + +// +checklocksignore +func (i *InternalFilesystemOptions) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.UniqueID) + stateSinkObject.Save(1, &i.LeakConnection) + stateSinkObject.Save(2, &i.OpenSocketsByConnecting) +} + +func (i *InternalFilesystemOptions) afterLoad() {} + +// +checklocksignore +func (i *InternalFilesystemOptions) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.UniqueID) + stateSourceObject.Load(1, &i.LeakConnection) + stateSourceObject.Load(2, &i.OpenSocketsByConnecting) +} + +func (i *inoKey) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.inoKey" +} + +func (i *inoKey) StateFields() []string { + return []string{ + "ino", + "devMinor", + "devMajor", + } +} + +func (i *inoKey) beforeSave() {} + +// +checklocksignore +func (i *inoKey) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.ino) + stateSinkObject.Save(1, &i.devMinor) + stateSinkObject.Save(2, &i.devMajor) +} + +func (i *inoKey) afterLoad() {} + +// +checklocksignore +func (i *inoKey) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.ino) + stateSourceObject.Load(1, &i.devMinor) + stateSourceObject.Load(2, &i.devMajor) +} + +func (d *dentry) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.dentry" +} + +func (d *dentry) StateFields() []string { + return []string{ + "vfsd", + "refs", + "fs", + "parent", + "name", + "qidPath", + "inoKey", + "deleted", + "cached", + "dentryEntry", + "children", + "syntheticChildren", + "dirents", + "ino", + "mode", + "uid", + "gid", + "blockSize", + "atime", + "mtime", + "ctime", + "btime", + "size", + "atimeDirty", + "mtimeDirty", + "nlink", + "mappings", + "cache", + "dirty", + "pf", + "haveTarget", + "target", + "endpoint", + "pipe", + "locks", + "watches", + } +} + +// +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.parent) + stateSinkObject.Save(4, &d.name) + stateSinkObject.Save(5, &d.qidPath) + stateSinkObject.Save(6, &d.inoKey) + stateSinkObject.Save(7, &d.deleted) + stateSinkObject.Save(8, &d.cached) + stateSinkObject.Save(9, &d.dentryEntry) + stateSinkObject.Save(10, &d.children) + stateSinkObject.Save(11, &d.syntheticChildren) + stateSinkObject.Save(12, &d.dirents) + stateSinkObject.Save(13, &d.ino) + stateSinkObject.Save(14, &d.mode) + stateSinkObject.Save(15, &d.uid) + stateSinkObject.Save(16, &d.gid) + stateSinkObject.Save(17, &d.blockSize) + stateSinkObject.Save(18, &d.atime) + stateSinkObject.Save(19, &d.mtime) + stateSinkObject.Save(20, &d.ctime) + stateSinkObject.Save(21, &d.btime) + stateSinkObject.Save(22, &d.size) + stateSinkObject.Save(23, &d.atimeDirty) + stateSinkObject.Save(24, &d.mtimeDirty) + stateSinkObject.Save(25, &d.nlink) + stateSinkObject.Save(26, &d.mappings) + stateSinkObject.Save(27, &d.cache) + stateSinkObject.Save(28, &d.dirty) + stateSinkObject.Save(29, &d.pf) + stateSinkObject.Save(30, &d.haveTarget) + stateSinkObject.Save(31, &d.target) + stateSinkObject.Save(32, &d.endpoint) + stateSinkObject.Save(33, &d.pipe) + stateSinkObject.Save(34, &d.locks) + stateSinkObject.Save(35, &d.watches) +} + +// +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.parent) + stateSourceObject.Load(4, &d.name) + stateSourceObject.Load(5, &d.qidPath) + stateSourceObject.Load(6, &d.inoKey) + stateSourceObject.Load(7, &d.deleted) + stateSourceObject.Load(8, &d.cached) + stateSourceObject.Load(9, &d.dentryEntry) + stateSourceObject.Load(10, &d.children) + stateSourceObject.Load(11, &d.syntheticChildren) + stateSourceObject.Load(12, &d.dirents) + stateSourceObject.Load(13, &d.ino) + stateSourceObject.Load(14, &d.mode) + stateSourceObject.Load(15, &d.uid) + stateSourceObject.Load(16, &d.gid) + stateSourceObject.Load(17, &d.blockSize) + stateSourceObject.Load(18, &d.atime) + stateSourceObject.Load(19, &d.mtime) + stateSourceObject.Load(20, &d.ctime) + stateSourceObject.Load(21, &d.btime) + stateSourceObject.Load(22, &d.size) + stateSourceObject.Load(23, &d.atimeDirty) + stateSourceObject.Load(24, &d.mtimeDirty) + stateSourceObject.Load(25, &d.nlink) + stateSourceObject.Load(26, &d.mappings) + stateSourceObject.Load(27, &d.cache) + stateSourceObject.Load(28, &d.dirty) + stateSourceObject.Load(29, &d.pf) + stateSourceObject.Load(30, &d.haveTarget) + stateSourceObject.Load(31, &d.target) + stateSourceObject.Load(32, &d.endpoint) + stateSourceObject.Load(33, &d.pipe) + stateSourceObject.Load(34, &d.locks) + stateSourceObject.Load(35, &d.watches) + stateSourceObject.AfterLoad(d.afterLoad) +} + +func (fd *fileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.fileDescription" +} + +func (fd *fileDescription) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "LockFD", + } +} + +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.LockFD) +} + +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.LockFD) +} + +func (fd *regularFileFD) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.regularFileFD" +} + +func (fd *regularFileFD) StateFields() []string { + return []string{ + "fileDescription", + "off", + } +} + +func (fd *regularFileFD) beforeSave() {} + +// +checklocksignore +func (fd *regularFileFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.fileDescription) + stateSinkObject.Save(1, &fd.off) +} + +func (fd *regularFileFD) afterLoad() {} + +// +checklocksignore +func (fd *regularFileFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.fileDescription) + stateSourceObject.Load(1, &fd.off) +} + +func (d *dentryPlatformFile) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.dentryPlatformFile" +} + +func (d *dentryPlatformFile) StateFields() []string { + return []string{ + "dentry", + "fdRefs", + "hostFileMapper", + } +} + +func (d *dentryPlatformFile) beforeSave() {} + +// +checklocksignore +func (d *dentryPlatformFile) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.dentry) + stateSinkObject.Save(1, &d.fdRefs) + stateSinkObject.Save(2, &d.hostFileMapper) +} + +// +checklocksignore +func (d *dentryPlatformFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.dentry) + stateSourceObject.Load(1, &d.fdRefs) + stateSourceObject.Load(2, &d.hostFileMapper) + stateSourceObject.AfterLoad(d.afterLoad) +} + +func (s *savedDentryRW) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.savedDentryRW" +} + +func (s *savedDentryRW) StateFields() []string { + return []string{ + "read", + "write", + } +} + +func (s *savedDentryRW) beforeSave() {} + +// +checklocksignore +func (s *savedDentryRW) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.read) + stateSinkObject.Save(1, &s.write) +} + +func (s *savedDentryRW) afterLoad() {} + +// +checklocksignore +func (s *savedDentryRW) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.read) + stateSourceObject.Load(1, &s.write) +} + +func (e *endpoint) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.endpoint" +} + +func (e *endpoint) StateFields() []string { + return []string{ + "dentry", + "path", + } +} + +func (e *endpoint) beforeSave() {} + +// +checklocksignore +func (e *endpoint) StateSave(stateSinkObject state.Sink) { + e.beforeSave() + stateSinkObject.Save(0, &e.dentry) + stateSinkObject.Save(1, &e.path) +} + +func (e *endpoint) afterLoad() {} + +// +checklocksignore +func (e *endpoint) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &e.dentry) + stateSourceObject.Load(1, &e.path) +} + +func (fd *specialFileFD) StateTypeName() string { + return "pkg/sentry/fsimpl/gofer.specialFileFD" +} + +func (fd *specialFileFD) StateFields() []string { + return []string{ + "fileDescription", + "isRegularFile", + "seekable", + "queue", + "off", + "haveBuf", + "buf", + "hostFileMapper", + "fileRefs", + } +} + +func (fd *specialFileFD) beforeSave() {} + +// +checklocksignore +func (fd *specialFileFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.fileDescription) + stateSinkObject.Save(1, &fd.isRegularFile) + stateSinkObject.Save(2, &fd.seekable) + stateSinkObject.Save(3, &fd.queue) + stateSinkObject.Save(4, &fd.off) + stateSinkObject.Save(5, &fd.haveBuf) + stateSinkObject.Save(6, &fd.buf) + stateSinkObject.Save(7, &fd.hostFileMapper) + stateSinkObject.Save(8, &fd.fileRefs) +} + +// +checklocksignore +func (fd *specialFileFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.fileDescription) + stateSourceObject.Load(1, &fd.isRegularFile) + stateSourceObject.Load(2, &fd.seekable) + stateSourceObject.Load(3, &fd.queue) + stateSourceObject.Load(4, &fd.off) + stateSourceObject.Load(5, &fd.haveBuf) + stateSourceObject.Load(6, &fd.buf) + stateSourceObject.Load(7, &fd.hostFileMapper) + stateSourceObject.Load(8, &fd.fileRefs) + stateSourceObject.AfterLoad(fd.afterLoad) +} + +func init() { + state.Register((*dentryList)(nil)) + state.Register((*dentryEntry)(nil)) + state.Register((*directoryFD)(nil)) + state.Register((*FilesystemType)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*filesystemOptions)(nil)) + state.Register((*InteropMode)(nil)) + state.Register((*InternalFilesystemOptions)(nil)) + state.Register((*inoKey)(nil)) + state.Register((*dentry)(nil)) + state.Register((*fileDescription)(nil)) + state.Register((*regularFileFD)(nil)) + state.Register((*dentryPlatformFile)(nil)) + state.Register((*savedDentryRW)(nil)) + state.Register((*endpoint)(nil)) + state.Register((*specialFileFD)(nil)) +} diff --git a/pkg/sentry/fsimpl/gofer/gofer_test.go b/pkg/sentry/fsimpl/gofer/gofer_test.go deleted file mode 100644 index d5cc73f33..000000000 --- a/pkg/sentry/fsimpl/gofer/gofer_test.go +++ /dev/null @@ -1,69 +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 gofer - -import ( - "sync/atomic" - "testing" - - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/pgalloc" -) - -func TestDestroyIdempotent(t *testing.T) { - ctx := contexttest.Context(t) - fs := filesystem{ - mfp: pgalloc.MemoryFileProviderFromContext(ctx), - opts: filesystemOptions{ - // Test relies on no dentry being held in the cache. - maxCachedDentries: 0, - }, - syncableDentries: make(map[*dentry]struct{}), - inoByQIDPath: make(map[uint64]uint64), - inoByKey: make(map[inoKey]uint64), - } - - attr := &p9.Attr{ - Mode: p9.ModeRegular, - } - mask := p9.AttrMask{ - Mode: true, - Size: true, - } - parent, err := fs.newDentry(ctx, p9file{}, p9.QID{}, mask, attr) - if err != nil { - t.Fatalf("fs.newDentry(): %v", err) - } - - child, err := fs.newDentry(ctx, p9file{}, p9.QID{}, mask, attr) - if err != nil { - t.Fatalf("fs.newDentry(): %v", err) - } - parent.cacheNewChildLocked(child, "child") - - fs.renameMu.Lock() - defer fs.renameMu.Unlock() - child.checkCachingLocked(ctx, true /* renameMuWriteLocked */) - if got := atomic.LoadInt64(&child.refs); got != -1 { - t.Fatalf("child.refs=%d, want: -1", got) - } - // Parent will also be destroyed when child reference is removed. - if got := atomic.LoadInt64(&parent.refs); got != -1 { - t.Fatalf("parent.refs=%d, want: -1", got) - } - child.checkCachingLocked(ctx, true /* renameMuWriteLocked */) - child.checkCachingLocked(ctx, true /* renameMuWriteLocked */) -} diff --git a/pkg/sentry/fsimpl/host/BUILD b/pkg/sentry/fsimpl/host/BUILD deleted file mode 100644 index 180a35583..000000000 --- a/pkg/sentry/fsimpl/host/BUILD +++ /dev/null @@ -1,79 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//tools/go_generics:defs.bzl", "go_template_instance") - -licenses(["notice"]) - -go_template_instance( - name = "inode_refs", - out = "inode_refs.go", - package = "host", - prefix = "inode", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "inode", - }, -) - -go_template_instance( - name = "connected_endpoint_refs", - out = "connected_endpoint_refs.go", - package = "host", - prefix = "ConnectedEndpoint", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "ConnectedEndpoint", - }, -) - -go_library( - name = "host", - srcs = [ - "connected_endpoint_refs.go", - "control.go", - "host.go", - "inode_refs.go", - "ioctl_unsafe.go", - "save_restore.go", - "socket.go", - "socket_iovec.go", - "socket_unsafe.go", - "tty.go", - "util.go", - "util_unsafe.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fdnotifier", - "//pkg/fspath", - "//pkg/hostarch", - "//pkg/log", - "//pkg/marshal/primitive", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/safemem", - "//pkg/sentry/arch", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/lock", - "//pkg/sentry/fsimpl/kernfs", - "//pkg/sentry/hostfd", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/memmap", - "//pkg/sentry/socket/control", - "//pkg/sentry/socket/unix", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/unimpl", - "//pkg/sentry/uniqueid", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/syserr", - "//pkg/tcpip", - "//pkg/unet", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/pkg/sentry/fsimpl/host/connected_endpoint_refs.go b/pkg/sentry/fsimpl/host/connected_endpoint_refs.go new file mode 100644 index 000000000..c0a87f656 --- /dev/null +++ b/pkg/sentry/fsimpl/host/connected_endpoint_refs.go @@ -0,0 +1,140 @@ +package host + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const ConnectedEndpointenableLogging = false + +// obj 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 ConnectedEndpointobj *ConnectedEndpoint + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type ConnectedEndpointRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *ConnectedEndpointRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *ConnectedEndpointRefs) RefType() string { + return fmt.Sprintf("%T", ConnectedEndpointobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *ConnectedEndpointRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *ConnectedEndpointRefs) LogRefs() bool { + return ConnectedEndpointenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *ConnectedEndpointRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *ConnectedEndpointRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if ConnectedEndpointenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *ConnectedEndpointRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if ConnectedEndpointenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *ConnectedEndpointRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if ConnectedEndpointenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *ConnectedEndpointRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/host/host_state_autogen.go b/pkg/sentry/fsimpl/host/host_state_autogen.go new file mode 100644 index 000000000..607474165 --- /dev/null +++ b/pkg/sentry/fsimpl/host/host_state_autogen.go @@ -0,0 +1,327 @@ +// automatically generated by stateify. + +package host + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (r *ConnectedEndpointRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/host.ConnectedEndpointRefs" +} + +func (r *ConnectedEndpointRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *ConnectedEndpointRefs) beforeSave() {} + +// +checklocksignore +func (r *ConnectedEndpointRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *ConnectedEndpointRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (v *virtualOwner) StateTypeName() string { + return "pkg/sentry/fsimpl/host.virtualOwner" +} + +func (v *virtualOwner) StateFields() []string { + return []string{ + "enabled", + "uid", + "gid", + "mode", + } +} + +func (v *virtualOwner) beforeSave() {} + +// +checklocksignore +func (v *virtualOwner) StateSave(stateSinkObject state.Sink) { + v.beforeSave() + stateSinkObject.Save(0, &v.enabled) + stateSinkObject.Save(1, &v.uid) + stateSinkObject.Save(2, &v.gid) + stateSinkObject.Save(3, &v.mode) +} + +func (v *virtualOwner) afterLoad() {} + +// +checklocksignore +func (v *virtualOwner) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &v.enabled) + stateSourceObject.Load(1, &v.uid) + stateSourceObject.Load(2, &v.gid) + stateSourceObject.Load(3, &v.mode) +} + +func (i *inode) StateTypeName() string { + return "pkg/sentry/fsimpl/host.inode" +} + +func (i *inode) StateFields() []string { + return []string{ + "InodeNoStatFS", + "InodeNotDirectory", + "InodeNotSymlink", + "CachedMappable", + "InodeTemporary", + "locks", + "inodeRefs", + "hostFD", + "ino", + "ftype", + "mayBlock", + "seekable", + "isTTY", + "savable", + "queue", + "virtualOwner", + "haveBuf", + "buf", + } +} + +// +checklocksignore +func (i *inode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.InodeNoStatFS) + stateSinkObject.Save(1, &i.InodeNotDirectory) + stateSinkObject.Save(2, &i.InodeNotSymlink) + stateSinkObject.Save(3, &i.CachedMappable) + stateSinkObject.Save(4, &i.InodeTemporary) + stateSinkObject.Save(5, &i.locks) + stateSinkObject.Save(6, &i.inodeRefs) + stateSinkObject.Save(7, &i.hostFD) + stateSinkObject.Save(8, &i.ino) + stateSinkObject.Save(9, &i.ftype) + stateSinkObject.Save(10, &i.mayBlock) + stateSinkObject.Save(11, &i.seekable) + stateSinkObject.Save(12, &i.isTTY) + stateSinkObject.Save(13, &i.savable) + stateSinkObject.Save(14, &i.queue) + stateSinkObject.Save(15, &i.virtualOwner) + stateSinkObject.Save(16, &i.haveBuf) + stateSinkObject.Save(17, &i.buf) +} + +// +checklocksignore +func (i *inode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeNoStatFS) + stateSourceObject.Load(1, &i.InodeNotDirectory) + stateSourceObject.Load(2, &i.InodeNotSymlink) + stateSourceObject.Load(3, &i.CachedMappable) + stateSourceObject.Load(4, &i.InodeTemporary) + stateSourceObject.Load(5, &i.locks) + stateSourceObject.Load(6, &i.inodeRefs) + stateSourceObject.Load(7, &i.hostFD) + stateSourceObject.Load(8, &i.ino) + stateSourceObject.Load(9, &i.ftype) + stateSourceObject.Load(10, &i.mayBlock) + stateSourceObject.Load(11, &i.seekable) + stateSourceObject.Load(12, &i.isTTY) + stateSourceObject.Load(13, &i.savable) + stateSourceObject.Load(14, &i.queue) + stateSourceObject.Load(15, &i.virtualOwner) + stateSourceObject.Load(16, &i.haveBuf) + stateSourceObject.Load(17, &i.buf) + stateSourceObject.AfterLoad(i.afterLoad) +} + +func (f *filesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/host.filesystemType" +} + +func (f *filesystemType) StateFields() []string { + return []string{} +} + +func (f *filesystemType) beforeSave() {} + +// +checklocksignore +func (f *filesystemType) StateSave(stateSinkObject state.Sink) { + f.beforeSave() +} + +func (f *filesystemType) afterLoad() {} + +// +checklocksignore +func (f *filesystemType) StateLoad(stateSourceObject state.Source) { +} + +func (fs *filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/host.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "Filesystem", + "devMinor", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Filesystem) + stateSinkObject.Save(1, &fs.devMinor) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Filesystem) + stateSourceObject.Load(1, &fs.devMinor) +} + +func (f *fileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/host.fileDescription" +} + +func (f *fileDescription) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "LockFD", + "inode", + "offset", + } +} + +func (f *fileDescription) beforeSave() {} + +// +checklocksignore +func (f *fileDescription) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.vfsfd) + stateSinkObject.Save(1, &f.FileDescriptionDefaultImpl) + stateSinkObject.Save(2, &f.LockFD) + stateSinkObject.Save(3, &f.inode) + stateSinkObject.Save(4, &f.offset) +} + +func (f *fileDescription) afterLoad() {} + +// +checklocksignore +func (f *fileDescription) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.vfsfd) + stateSourceObject.Load(1, &f.FileDescriptionDefaultImpl) + stateSourceObject.Load(2, &f.LockFD) + stateSourceObject.Load(3, &f.inode) + stateSourceObject.Load(4, &f.offset) +} + +func (r *inodeRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/host.inodeRefs" +} + +func (r *inodeRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *inodeRefs) beforeSave() {} + +// +checklocksignore +func (r *inodeRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *inodeRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (c *ConnectedEndpoint) StateTypeName() string { + return "pkg/sentry/fsimpl/host.ConnectedEndpoint" +} + +func (c *ConnectedEndpoint) StateFields() []string { + return []string{ + "ConnectedEndpointRefs", + "fd", + "addr", + "stype", + } +} + +func (c *ConnectedEndpoint) beforeSave() {} + +// +checklocksignore +func (c *ConnectedEndpoint) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.ConnectedEndpointRefs) + stateSinkObject.Save(1, &c.fd) + stateSinkObject.Save(2, &c.addr) + stateSinkObject.Save(3, &c.stype) +} + +// +checklocksignore +func (c *ConnectedEndpoint) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.ConnectedEndpointRefs) + stateSourceObject.Load(1, &c.fd) + stateSourceObject.Load(2, &c.addr) + stateSourceObject.Load(3, &c.stype) + stateSourceObject.AfterLoad(c.afterLoad) +} + +func (t *TTYFileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/host.TTYFileDescription" +} + +func (t *TTYFileDescription) StateFields() []string { + return []string{ + "fileDescription", + "session", + "fgProcessGroup", + "termios", + } +} + +func (t *TTYFileDescription) beforeSave() {} + +// +checklocksignore +func (t *TTYFileDescription) StateSave(stateSinkObject state.Sink) { + t.beforeSave() + stateSinkObject.Save(0, &t.fileDescription) + stateSinkObject.Save(1, &t.session) + stateSinkObject.Save(2, &t.fgProcessGroup) + stateSinkObject.Save(3, &t.termios) +} + +func (t *TTYFileDescription) afterLoad() {} + +// +checklocksignore +func (t *TTYFileDescription) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &t.fileDescription) + stateSourceObject.Load(1, &t.session) + stateSourceObject.Load(2, &t.fgProcessGroup) + stateSourceObject.Load(3, &t.termios) +} + +func init() { + state.Register((*ConnectedEndpointRefs)(nil)) + state.Register((*virtualOwner)(nil)) + state.Register((*inode)(nil)) + state.Register((*filesystemType)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*fileDescription)(nil)) + state.Register((*inodeRefs)(nil)) + state.Register((*ConnectedEndpoint)(nil)) + state.Register((*TTYFileDescription)(nil)) +} diff --git a/pkg/sentry/fsimpl/host/host_unsafe_state_autogen.go b/pkg/sentry/fsimpl/host/host_unsafe_state_autogen.go new file mode 100644 index 000000000..b2d8c661f --- /dev/null +++ b/pkg/sentry/fsimpl/host/host_unsafe_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package host diff --git a/pkg/sentry/fsimpl/host/inode_refs.go b/pkg/sentry/fsimpl/host/inode_refs.go new file mode 100644 index 000000000..112f39850 --- /dev/null +++ b/pkg/sentry/fsimpl/host/inode_refs.go @@ -0,0 +1,140 @@ +package host + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const inodeenableLogging = false + +// obj 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 inodeobj *inode + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type inodeRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *inodeRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *inodeRefs) RefType() string { + return fmt.Sprintf("%T", inodeobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *inodeRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *inodeRefs) LogRefs() bool { + return inodeenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *inodeRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *inodeRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if inodeenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *inodeRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if inodeenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *inodeRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if inodeenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *inodeRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/kernfs/BUILD b/pkg/sentry/fsimpl/kernfs/BUILD deleted file mode 100644 index 4b577ea43..000000000 --- a/pkg/sentry/fsimpl/kernfs/BUILD +++ /dev/null @@ -1,150 +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 = "kernfs", - prefix = "dentry", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*Dentry", - "Linker": "*Dentry", - }, -) - -go_template_instance( - name = "fstree", - out = "fstree.go", - package = "kernfs", - prefix = "generic", - template = "//pkg/sentry/vfs/genericfstree:generic_fstree", - types = { - "Dentry": "Dentry", - }, -) - -go_template_instance( - name = "slot_list", - out = "slot_list.go", - package = "kernfs", - prefix = "slot", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*slot", - "Linker": "*slot", - }, -) - -go_template_instance( - name = "static_directory_refs", - out = "static_directory_refs.go", - package = "kernfs", - prefix = "StaticDirectory", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "StaticDirectory", - }, -) - -go_template_instance( - name = "dir_refs", - out = "dir_refs.go", - package = "kernfs_test", - prefix = "dir", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "dir", - }, -) - -go_template_instance( - name = "readonly_dir_refs", - out = "readonly_dir_refs.go", - package = "kernfs_test", - prefix = "readonlyDir", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "readonlyDir", - }, -) - -go_template_instance( - name = "synthetic_directory_refs", - out = "synthetic_directory_refs.go", - package = "kernfs", - prefix = "syntheticDirectory", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "syntheticDirectory", - }, -) - -go_library( - name = "kernfs", - srcs = [ - "dentry_list.go", - "dynamic_bytes_file.go", - "fd_impl_util.go", - "filesystem.go", - "fstree.go", - "inode_impl_util.go", - "kernfs.go", - "mmap_util.go", - "save_restore.go", - "slot_list.go", - "static_directory_refs.go", - "symlink.go", - "synthetic_directory.go", - "synthetic_directory_refs.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/hostarch", - "//pkg/log", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/safemem", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/lock", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/time", - "//pkg/sentry/memmap", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/usermem", - ], -) - -go_test( - name = "kernfs_test", - size = "small", - srcs = [ - "dir_refs.go", - "kernfs_test.go", - "readonly_dir_refs.go", - ], - deps = [ - ":kernfs", - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/log", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/sentry/contexttest", - "//pkg/sentry/fsimpl/testutil", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - "//pkg/usermem", - "@com_github_google_go_cmp//cmp:go_default_library", - ], -) diff --git a/pkg/sentry/fsimpl/kernfs/dentry_list.go b/pkg/sentry/fsimpl/kernfs/dentry_list.go new file mode 100644 index 000000000..e73cde1f1 --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/dentry_list.go @@ -0,0 +1,221 @@ +package kernfs + +// 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/kernfs/fstree.go b/pkg/sentry/fsimpl/kernfs/fstree.go new file mode 100644 index 000000000..9dc52dabc --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/fstree.go @@ -0,0 +1,55 @@ +package kernfs + +import ( + "gvisor.dev/gvisor/pkg/fspath" + "gvisor.dev/gvisor/pkg/sentry/vfs" +) + +// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is +// either d2's parent or an ancestor of d2's parent. +func genericIsAncestorDentry(d, d2 *Dentry) bool { + for d2 != nil { + if d2.parent == d { + return true + } + if d2.parent == d2 { + return false + } + d2 = d2.parent + } + return false +} + +// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d. +func genericParentOrSelf(d *Dentry) *Dentry { + if d.parent != nil { + return d.parent + } + return d +} + +// PrependPath is a generic implementation of FilesystemImpl.PrependPath(). +func genericPrependPath(vfsroot vfs.VirtualDentry, mnt *vfs.Mount, d *Dentry, b *fspath.Builder) error { + for { + if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() { + return vfs.PrependPathAtVFSRootError{} + } + if mnt != nil && &d.vfsd == mnt.Root() { + return nil + } + if d.parent == nil { + return vfs.PrependPathAtNonMountRootError{} + } + b.PrependComponent(d.name) + d = d.parent + } +} + +// DebugPathname returns a pathname to d relative to its filesystem root. +// DebugPathname does not correspond to any Linux function; it's used to +// generate dentry pathnames for debugging. +func genericDebugPathname(d *Dentry) string { + var b fspath.Builder + _ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b) + return b.String() +} diff --git a/pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go b/pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go new file mode 100644 index 000000000..f8add23f8 --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/kernfs_state_autogen.go @@ -0,0 +1,965 @@ +// automatically generated by stateify. + +package kernfs + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (l *dentryList) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.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/kernfs.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 (f *DynamicBytesFile) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.DynamicBytesFile" +} + +func (f *DynamicBytesFile) StateFields() []string { + return []string{ + "InodeAttrs", + "InodeNoStatFS", + "InodeNoopRefCount", + "InodeNotDirectory", + "InodeNotSymlink", + "locks", + "data", + } +} + +func (f *DynamicBytesFile) beforeSave() {} + +// +checklocksignore +func (f *DynamicBytesFile) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.InodeAttrs) + stateSinkObject.Save(1, &f.InodeNoStatFS) + stateSinkObject.Save(2, &f.InodeNoopRefCount) + stateSinkObject.Save(3, &f.InodeNotDirectory) + stateSinkObject.Save(4, &f.InodeNotSymlink) + stateSinkObject.Save(5, &f.locks) + stateSinkObject.Save(6, &f.data) +} + +func (f *DynamicBytesFile) afterLoad() {} + +// +checklocksignore +func (f *DynamicBytesFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.InodeAttrs) + stateSourceObject.Load(1, &f.InodeNoStatFS) + stateSourceObject.Load(2, &f.InodeNoopRefCount) + stateSourceObject.Load(3, &f.InodeNotDirectory) + stateSourceObject.Load(4, &f.InodeNotSymlink) + stateSourceObject.Load(5, &f.locks) + stateSourceObject.Load(6, &f.data) +} + +func (fd *DynamicBytesFD) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.DynamicBytesFD" +} + +func (fd *DynamicBytesFD) StateFields() []string { + return []string{ + "FileDescriptionDefaultImpl", + "DynamicBytesFileDescriptionImpl", + "LockFD", + "vfsfd", + "inode", + } +} + +func (fd *DynamicBytesFD) beforeSave() {} + +// +checklocksignore +func (fd *DynamicBytesFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl) + stateSinkObject.Save(1, &fd.DynamicBytesFileDescriptionImpl) + stateSinkObject.Save(2, &fd.LockFD) + stateSinkObject.Save(3, &fd.vfsfd) + stateSinkObject.Save(4, &fd.inode) +} + +func (fd *DynamicBytesFD) afterLoad() {} + +// +checklocksignore +func (fd *DynamicBytesFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl) + stateSourceObject.Load(1, &fd.DynamicBytesFileDescriptionImpl) + stateSourceObject.Load(2, &fd.LockFD) + stateSourceObject.Load(3, &fd.vfsfd) + stateSourceObject.Load(4, &fd.inode) +} + +func (s *SeekEndConfig) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.SeekEndConfig" +} + +func (s *SeekEndConfig) StateFields() []string { + return nil +} + +func (g *GenericDirectoryFDOptions) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.GenericDirectoryFDOptions" +} + +func (g *GenericDirectoryFDOptions) StateFields() []string { + return []string{ + "SeekEnd", + } +} + +func (g *GenericDirectoryFDOptions) beforeSave() {} + +// +checklocksignore +func (g *GenericDirectoryFDOptions) StateSave(stateSinkObject state.Sink) { + g.beforeSave() + stateSinkObject.Save(0, &g.SeekEnd) +} + +func (g *GenericDirectoryFDOptions) afterLoad() {} + +// +checklocksignore +func (g *GenericDirectoryFDOptions) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &g.SeekEnd) +} + +func (fd *GenericDirectoryFD) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.GenericDirectoryFD" +} + +func (fd *GenericDirectoryFD) StateFields() []string { + return []string{ + "FileDescriptionDefaultImpl", + "DirectoryFileDescriptionDefaultImpl", + "LockFD", + "seekEnd", + "vfsfd", + "children", + "off", + } +} + +func (fd *GenericDirectoryFD) beforeSave() {} + +// +checklocksignore +func (fd *GenericDirectoryFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl) + stateSinkObject.Save(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSinkObject.Save(2, &fd.LockFD) + stateSinkObject.Save(3, &fd.seekEnd) + stateSinkObject.Save(4, &fd.vfsfd) + stateSinkObject.Save(5, &fd.children) + stateSinkObject.Save(6, &fd.off) +} + +func (fd *GenericDirectoryFD) afterLoad() {} + +// +checklocksignore +func (fd *GenericDirectoryFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl) + stateSourceObject.Load(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSourceObject.Load(2, &fd.LockFD) + stateSourceObject.Load(3, &fd.seekEnd) + stateSourceObject.Load(4, &fd.vfsfd) + stateSourceObject.Load(5, &fd.children) + stateSourceObject.Load(6, &fd.off) +} + +func (i *InodeNoopRefCount) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeNoopRefCount" +} + +func (i *InodeNoopRefCount) StateFields() []string { + return []string{ + "InodeTemporary", + } +} + +func (i *InodeNoopRefCount) beforeSave() {} + +// +checklocksignore +func (i *InodeNoopRefCount) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.InodeTemporary) +} + +func (i *InodeNoopRefCount) afterLoad() {} + +// +checklocksignore +func (i *InodeNoopRefCount) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeTemporary) +} + +func (i *InodeDirectoryNoNewChildren) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeDirectoryNoNewChildren" +} + +func (i *InodeDirectoryNoNewChildren) StateFields() []string { + return []string{} +} + +func (i *InodeDirectoryNoNewChildren) beforeSave() {} + +// +checklocksignore +func (i *InodeDirectoryNoNewChildren) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeDirectoryNoNewChildren) afterLoad() {} + +// +checklocksignore +func (i *InodeDirectoryNoNewChildren) StateLoad(stateSourceObject state.Source) { +} + +func (i *InodeNotDirectory) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeNotDirectory" +} + +func (i *InodeNotDirectory) StateFields() []string { + return []string{ + "InodeAlwaysValid", + } +} + +func (i *InodeNotDirectory) beforeSave() {} + +// +checklocksignore +func (i *InodeNotDirectory) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.InodeAlwaysValid) +} + +func (i *InodeNotDirectory) afterLoad() {} + +// +checklocksignore +func (i *InodeNotDirectory) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeAlwaysValid) +} + +func (i *InodeNotSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeNotSymlink" +} + +func (i *InodeNotSymlink) StateFields() []string { + return []string{} +} + +func (i *InodeNotSymlink) beforeSave() {} + +// +checklocksignore +func (i *InodeNotSymlink) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeNotSymlink) afterLoad() {} + +// +checklocksignore +func (i *InodeNotSymlink) StateLoad(stateSourceObject state.Source) { +} + +func (a *InodeAttrs) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeAttrs" +} + +func (a *InodeAttrs) StateFields() []string { + return []string{ + "devMajor", + "devMinor", + "ino", + "mode", + "uid", + "gid", + "nlink", + "blockSize", + "atime", + "mtime", + "ctime", + } +} + +func (a *InodeAttrs) beforeSave() {} + +// +checklocksignore +func (a *InodeAttrs) StateSave(stateSinkObject state.Sink) { + a.beforeSave() + stateSinkObject.Save(0, &a.devMajor) + stateSinkObject.Save(1, &a.devMinor) + stateSinkObject.Save(2, &a.ino) + stateSinkObject.Save(3, &a.mode) + stateSinkObject.Save(4, &a.uid) + stateSinkObject.Save(5, &a.gid) + stateSinkObject.Save(6, &a.nlink) + stateSinkObject.Save(7, &a.blockSize) + stateSinkObject.Save(8, &a.atime) + stateSinkObject.Save(9, &a.mtime) + stateSinkObject.Save(10, &a.ctime) +} + +func (a *InodeAttrs) afterLoad() {} + +// +checklocksignore +func (a *InodeAttrs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &a.devMajor) + stateSourceObject.Load(1, &a.devMinor) + stateSourceObject.Load(2, &a.ino) + stateSourceObject.Load(3, &a.mode) + stateSourceObject.Load(4, &a.uid) + stateSourceObject.Load(5, &a.gid) + stateSourceObject.Load(6, &a.nlink) + stateSourceObject.Load(7, &a.blockSize) + stateSourceObject.Load(8, &a.atime) + stateSourceObject.Load(9, &a.mtime) + stateSourceObject.Load(10, &a.ctime) +} + +func (s *slot) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.slot" +} + +func (s *slot) StateFields() []string { + return []string{ + "name", + "inode", + "static", + "slotEntry", + } +} + +func (s *slot) beforeSave() {} + +// +checklocksignore +func (s *slot) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.name) + stateSinkObject.Save(1, &s.inode) + stateSinkObject.Save(2, &s.static) + stateSinkObject.Save(3, &s.slotEntry) +} + +func (s *slot) afterLoad() {} + +// +checklocksignore +func (s *slot) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.name) + stateSourceObject.Load(1, &s.inode) + stateSourceObject.Load(2, &s.static) + stateSourceObject.Load(3, &s.slotEntry) +} + +func (o *OrderedChildrenOptions) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.OrderedChildrenOptions" +} + +func (o *OrderedChildrenOptions) StateFields() []string { + return []string{ + "Writable", + } +} + +func (o *OrderedChildrenOptions) beforeSave() {} + +// +checklocksignore +func (o *OrderedChildrenOptions) StateSave(stateSinkObject state.Sink) { + o.beforeSave() + stateSinkObject.Save(0, &o.Writable) +} + +func (o *OrderedChildrenOptions) afterLoad() {} + +// +checklocksignore +func (o *OrderedChildrenOptions) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &o.Writable) +} + +func (o *OrderedChildren) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.OrderedChildren" +} + +func (o *OrderedChildren) StateFields() []string { + return []string{ + "writable", + "order", + "set", + } +} + +func (o *OrderedChildren) beforeSave() {} + +// +checklocksignore +func (o *OrderedChildren) StateSave(stateSinkObject state.Sink) { + o.beforeSave() + stateSinkObject.Save(0, &o.writable) + stateSinkObject.Save(1, &o.order) + stateSinkObject.Save(2, &o.set) +} + +func (o *OrderedChildren) afterLoad() {} + +// +checklocksignore +func (o *OrderedChildren) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &o.writable) + stateSourceObject.Load(1, &o.order) + stateSourceObject.Load(2, &o.set) +} + +func (i *InodeSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeSymlink" +} + +func (i *InodeSymlink) StateFields() []string { + return []string{ + "InodeNotDirectory", + } +} + +func (i *InodeSymlink) beforeSave() {} + +// +checklocksignore +func (i *InodeSymlink) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.InodeNotDirectory) +} + +func (i *InodeSymlink) afterLoad() {} + +// +checklocksignore +func (i *InodeSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeNotDirectory) +} + +func (s *StaticDirectory) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.StaticDirectory" +} + +func (s *StaticDirectory) StateFields() []string { + return []string{ + "InodeAlwaysValid", + "InodeAttrs", + "InodeDirectoryNoNewChildren", + "InodeNoStatFS", + "InodeNotSymlink", + "InodeTemporary", + "OrderedChildren", + "StaticDirectoryRefs", + "locks", + "fdOpts", + } +} + +func (s *StaticDirectory) beforeSave() {} + +// +checklocksignore +func (s *StaticDirectory) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.InodeAlwaysValid) + stateSinkObject.Save(1, &s.InodeAttrs) + stateSinkObject.Save(2, &s.InodeDirectoryNoNewChildren) + stateSinkObject.Save(3, &s.InodeNoStatFS) + stateSinkObject.Save(4, &s.InodeNotSymlink) + stateSinkObject.Save(5, &s.InodeTemporary) + stateSinkObject.Save(6, &s.OrderedChildren) + stateSinkObject.Save(7, &s.StaticDirectoryRefs) + stateSinkObject.Save(8, &s.locks) + stateSinkObject.Save(9, &s.fdOpts) +} + +func (s *StaticDirectory) afterLoad() {} + +// +checklocksignore +func (s *StaticDirectory) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.InodeAlwaysValid) + stateSourceObject.Load(1, &s.InodeAttrs) + stateSourceObject.Load(2, &s.InodeDirectoryNoNewChildren) + stateSourceObject.Load(3, &s.InodeNoStatFS) + stateSourceObject.Load(4, &s.InodeNotSymlink) + stateSourceObject.Load(5, &s.InodeTemporary) + stateSourceObject.Load(6, &s.OrderedChildren) + stateSourceObject.Load(7, &s.StaticDirectoryRefs) + stateSourceObject.Load(8, &s.locks) + stateSourceObject.Load(9, &s.fdOpts) +} + +func (i *InodeAlwaysValid) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeAlwaysValid" +} + +func (i *InodeAlwaysValid) StateFields() []string { + return []string{} +} + +func (i *InodeAlwaysValid) beforeSave() {} + +// +checklocksignore +func (i *InodeAlwaysValid) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeAlwaysValid) afterLoad() {} + +// +checklocksignore +func (i *InodeAlwaysValid) StateLoad(stateSourceObject state.Source) { +} + +func (i *InodeTemporary) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeTemporary" +} + +func (i *InodeTemporary) StateFields() []string { + return []string{} +} + +func (i *InodeTemporary) beforeSave() {} + +// +checklocksignore +func (i *InodeTemporary) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeTemporary) afterLoad() {} + +// +checklocksignore +func (i *InodeTemporary) StateLoad(stateSourceObject state.Source) { +} + +func (i *InodeNoStatFS) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.InodeNoStatFS" +} + +func (i *InodeNoStatFS) StateFields() []string { + return []string{} +} + +func (i *InodeNoStatFS) beforeSave() {} + +// +checklocksignore +func (i *InodeNoStatFS) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *InodeNoStatFS) afterLoad() {} + +// +checklocksignore +func (i *InodeNoStatFS) StateLoad(stateSourceObject state.Source) { +} + +func (fs *Filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.Filesystem" +} + +func (fs *Filesystem) StateFields() []string { + return []string{ + "vfsfs", + "deferredDecRefs", + "nextInoMinusOne", + "cachedDentries", + "cachedDentriesLen", + "MaxCachedDentries", + "root", + } +} + +func (fs *Filesystem) beforeSave() {} + +// +checklocksignore +func (fs *Filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.vfsfs) + stateSinkObject.Save(1, &fs.deferredDecRefs) + stateSinkObject.Save(2, &fs.nextInoMinusOne) + stateSinkObject.Save(3, &fs.cachedDentries) + stateSinkObject.Save(4, &fs.cachedDentriesLen) + stateSinkObject.Save(5, &fs.MaxCachedDentries) + stateSinkObject.Save(6, &fs.root) +} + +func (fs *Filesystem) afterLoad() {} + +// +checklocksignore +func (fs *Filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.vfsfs) + stateSourceObject.Load(1, &fs.deferredDecRefs) + stateSourceObject.Load(2, &fs.nextInoMinusOne) + stateSourceObject.Load(3, &fs.cachedDentries) + stateSourceObject.Load(4, &fs.cachedDentriesLen) + stateSourceObject.Load(5, &fs.MaxCachedDentries) + stateSourceObject.Load(6, &fs.root) +} + +func (d *Dentry) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.Dentry" +} + +func (d *Dentry) StateFields() []string { + return []string{ + "vfsd", + "refs", + "fs", + "flags", + "parent", + "name", + "cached", + "dentryEntry", + "children", + "inode", + } +} + +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.flags) + stateSinkObject.Save(4, &d.parent) + stateSinkObject.Save(5, &d.name) + stateSinkObject.Save(6, &d.cached) + stateSinkObject.Save(7, &d.dentryEntry) + stateSinkObject.Save(8, &d.children) + stateSinkObject.Save(9, &d.inode) +} + +// +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.flags) + stateSourceObject.Load(4, &d.parent) + stateSourceObject.Load(5, &d.name) + stateSourceObject.Load(6, &d.cached) + stateSourceObject.Load(7, &d.dentryEntry) + stateSourceObject.Load(8, &d.children) + stateSourceObject.Load(9, &d.inode) + stateSourceObject.AfterLoad(d.afterLoad) +} + +func (i *inodePlatformFile) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.inodePlatformFile" +} + +func (i *inodePlatformFile) StateFields() []string { + return []string{ + "hostFD", + "fdRefs", + "fileMapper", + } +} + +func (i *inodePlatformFile) beforeSave() {} + +// +checklocksignore +func (i *inodePlatformFile) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.hostFD) + stateSinkObject.Save(1, &i.fdRefs) + stateSinkObject.Save(2, &i.fileMapper) +} + +// +checklocksignore +func (i *inodePlatformFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.hostFD) + stateSourceObject.Load(1, &i.fdRefs) + stateSourceObject.Load(2, &i.fileMapper) + stateSourceObject.AfterLoad(i.afterLoad) +} + +func (i *CachedMappable) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.CachedMappable" +} + +func (i *CachedMappable) StateFields() []string { + return []string{ + "mappings", + "pf", + } +} + +func (i *CachedMappable) beforeSave() {} + +// +checklocksignore +func (i *CachedMappable) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.mappings) + stateSinkObject.Save(1, &i.pf) +} + +func (i *CachedMappable) afterLoad() {} + +// +checklocksignore +func (i *CachedMappable) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.mappings) + stateSourceObject.Load(1, &i.pf) +} + +func (l *slotList) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.slotList" +} + +func (l *slotList) StateFields() []string { + return []string{ + "head", + "tail", + } +} + +func (l *slotList) beforeSave() {} + +// +checklocksignore +func (l *slotList) StateSave(stateSinkObject state.Sink) { + l.beforeSave() + stateSinkObject.Save(0, &l.head) + stateSinkObject.Save(1, &l.tail) +} + +func (l *slotList) afterLoad() {} + +// +checklocksignore +func (l *slotList) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &l.head) + stateSourceObject.Load(1, &l.tail) +} + +func (e *slotEntry) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.slotEntry" +} + +func (e *slotEntry) StateFields() []string { + return []string{ + "next", + "prev", + } +} + +func (e *slotEntry) beforeSave() {} + +// +checklocksignore +func (e *slotEntry) StateSave(stateSinkObject state.Sink) { + e.beforeSave() + stateSinkObject.Save(0, &e.next) + stateSinkObject.Save(1, &e.prev) +} + +func (e *slotEntry) afterLoad() {} + +// +checklocksignore +func (e *slotEntry) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &e.next) + stateSourceObject.Load(1, &e.prev) +} + +func (r *StaticDirectoryRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.StaticDirectoryRefs" +} + +func (r *StaticDirectoryRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *StaticDirectoryRefs) beforeSave() {} + +// +checklocksignore +func (r *StaticDirectoryRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *StaticDirectoryRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (s *StaticSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.StaticSymlink" +} + +func (s *StaticSymlink) StateFields() []string { + return []string{ + "InodeAttrs", + "InodeNoopRefCount", + "InodeSymlink", + "InodeNoStatFS", + "target", + } +} + +func (s *StaticSymlink) beforeSave() {} + +// +checklocksignore +func (s *StaticSymlink) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.InodeAttrs) + stateSinkObject.Save(1, &s.InodeNoopRefCount) + stateSinkObject.Save(2, &s.InodeSymlink) + stateSinkObject.Save(3, &s.InodeNoStatFS) + stateSinkObject.Save(4, &s.target) +} + +func (s *StaticSymlink) afterLoad() {} + +// +checklocksignore +func (s *StaticSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.InodeAttrs) + stateSourceObject.Load(1, &s.InodeNoopRefCount) + stateSourceObject.Load(2, &s.InodeSymlink) + stateSourceObject.Load(3, &s.InodeNoStatFS) + stateSourceObject.Load(4, &s.target) +} + +func (dir *syntheticDirectory) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.syntheticDirectory" +} + +func (dir *syntheticDirectory) StateFields() []string { + return []string{ + "InodeAlwaysValid", + "InodeAttrs", + "InodeNoStatFS", + "InodeNotSymlink", + "OrderedChildren", + "syntheticDirectoryRefs", + "locks", + } +} + +func (dir *syntheticDirectory) beforeSave() {} + +// +checklocksignore +func (dir *syntheticDirectory) StateSave(stateSinkObject state.Sink) { + dir.beforeSave() + stateSinkObject.Save(0, &dir.InodeAlwaysValid) + stateSinkObject.Save(1, &dir.InodeAttrs) + stateSinkObject.Save(2, &dir.InodeNoStatFS) + stateSinkObject.Save(3, &dir.InodeNotSymlink) + stateSinkObject.Save(4, &dir.OrderedChildren) + stateSinkObject.Save(5, &dir.syntheticDirectoryRefs) + stateSinkObject.Save(6, &dir.locks) +} + +func (dir *syntheticDirectory) afterLoad() {} + +// +checklocksignore +func (dir *syntheticDirectory) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &dir.InodeAlwaysValid) + stateSourceObject.Load(1, &dir.InodeAttrs) + stateSourceObject.Load(2, &dir.InodeNoStatFS) + stateSourceObject.Load(3, &dir.InodeNotSymlink) + stateSourceObject.Load(4, &dir.OrderedChildren) + stateSourceObject.Load(5, &dir.syntheticDirectoryRefs) + stateSourceObject.Load(6, &dir.locks) +} + +func (r *syntheticDirectoryRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/kernfs.syntheticDirectoryRefs" +} + +func (r *syntheticDirectoryRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *syntheticDirectoryRefs) beforeSave() {} + +// +checklocksignore +func (r *syntheticDirectoryRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *syntheticDirectoryRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func init() { + state.Register((*dentryList)(nil)) + state.Register((*dentryEntry)(nil)) + state.Register((*DynamicBytesFile)(nil)) + state.Register((*DynamicBytesFD)(nil)) + state.Register((*SeekEndConfig)(nil)) + state.Register((*GenericDirectoryFDOptions)(nil)) + state.Register((*GenericDirectoryFD)(nil)) + state.Register((*InodeNoopRefCount)(nil)) + state.Register((*InodeDirectoryNoNewChildren)(nil)) + state.Register((*InodeNotDirectory)(nil)) + state.Register((*InodeNotSymlink)(nil)) + state.Register((*InodeAttrs)(nil)) + state.Register((*slot)(nil)) + state.Register((*OrderedChildrenOptions)(nil)) + state.Register((*OrderedChildren)(nil)) + state.Register((*InodeSymlink)(nil)) + state.Register((*StaticDirectory)(nil)) + state.Register((*InodeAlwaysValid)(nil)) + state.Register((*InodeTemporary)(nil)) + state.Register((*InodeNoStatFS)(nil)) + state.Register((*Filesystem)(nil)) + state.Register((*Dentry)(nil)) + state.Register((*inodePlatformFile)(nil)) + state.Register((*CachedMappable)(nil)) + state.Register((*slotList)(nil)) + state.Register((*slotEntry)(nil)) + state.Register((*StaticDirectoryRefs)(nil)) + state.Register((*StaticSymlink)(nil)) + state.Register((*syntheticDirectory)(nil)) + state.Register((*syntheticDirectoryRefs)(nil)) +} diff --git a/pkg/sentry/fsimpl/kernfs/kernfs_test.go b/pkg/sentry/fsimpl/kernfs/kernfs_test.go deleted file mode 100644 index a2aba9321..000000000 --- a/pkg/sentry/fsimpl/kernfs/kernfs_test.go +++ /dev/null @@ -1,409 +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 kernfs_test - -import ( - "bytes" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "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/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/usermem" -) - -const defaultMode linux.FileMode = 01777 -const staticFileContent = "This is sample content for a static test file." - -// RootDentryFn is a generator function for creating the root dentry of a test -// filesystem. See newTestSystem. -type RootDentryFn func(context.Context, *auth.Credentials, *filesystem) kernfs.Inode - -// newTestSystem sets up a minimal environment for running a test, including an -// instance of a test filesystem. Tests can control the contents of the -// filesystem by providing an appropriate rootFn, which should return a -// pre-populated root dentry. -func newTestSystem(t *testing.T, rootFn RootDentryFn) *testutil.System { - ctx := contexttest.Context(t) - creds := auth.CredentialsFromContext(ctx) - v := &vfs.VirtualFilesystem{} - if err := v.Init(ctx); err != nil { - t.Fatalf("VFS init: %v", err) - } - v.MustRegisterFilesystemType("testfs", &fsType{rootFn: rootFn}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - mns, err := v.NewMountNamespace(ctx, creds, "", "testfs", &vfs.MountOptions{}) - if err != nil { - t.Fatalf("Failed to create testfs root mount: %v", err) - } - return testutil.NewSystem(ctx, t, v, mns) -} - -type fsType struct { - rootFn RootDentryFn -} - -type filesystem struct { - kernfs.Filesystem -} - -// MountOptions implements vfs.FilesystemImpl.MountOptions. -func (fs *filesystem) MountOptions() string { - return "" -} - -type file struct { - kernfs.DynamicBytesFile - content string -} - -func (fs *filesystem) newFile(ctx context.Context, creds *auth.Credentials, content string) kernfs.Inode { - f := &file{} - f.content = content - f.DynamicBytesFile.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), f, 0777) - return f -} - -func (f *file) Generate(ctx context.Context, buf *bytes.Buffer) error { - fmt.Fprintf(buf, "%s", f.content) - return nil -} - -type attrs struct { - kernfs.InodeAttrs -} - -func (*attrs) SetStat(context.Context, *vfs.Filesystem, *auth.Credentials, vfs.SetStatOptions) error { - return linuxerr.EPERM -} - -type readonlyDir struct { - readonlyDirRefs - attrs - kernfs.InodeAlwaysValid - kernfs.InodeDirectoryNoNewChildren - kernfs.InodeNoStatFS - kernfs.InodeNotSymlink - kernfs.InodeTemporary - kernfs.OrderedChildren - - locks vfs.FileLocks -} - -func (fs *filesystem) newReadonlyDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, contents map[string]kernfs.Inode) kernfs.Inode { - dir := &readonlyDir{} - dir.attrs.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), linux.ModeDirectory|mode) - dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{}) - dir.InitRefs() - dir.IncLinks(dir.OrderedChildren.Populate(contents)) - return dir -} - -func (d *readonlyDir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { - fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), kd, &d.OrderedChildren, &d.locks, &opts, kernfs.GenericDirectoryFDOptions{ - SeekEnd: kernfs.SeekEndStaticEntries, - }) - if err != nil { - return nil, err - } - return fd.VFSFileDescription(), nil -} - -func (d *readonlyDir) DecRef(ctx context.Context) { - d.readonlyDirRefs.DecRef(func() { d.Destroy(ctx) }) -} - -type dir struct { - dirRefs - attrs - kernfs.InodeAlwaysValid - kernfs.InodeNotSymlink - kernfs.InodeNoStatFS - kernfs.InodeTemporary - kernfs.OrderedChildren - - locks vfs.FileLocks - - fs *filesystem -} - -func (fs *filesystem) newDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, contents map[string]kernfs.Inode) kernfs.Inode { - dir := &dir{} - dir.fs = fs - dir.attrs.Init(ctx, creds, 0 /* devMajor */, 0 /* devMinor */, fs.NextIno(), linux.ModeDirectory|mode) - dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{Writable: true}) - dir.InitRefs() - - dir.IncLinks(dir.OrderedChildren.Populate(contents)) - return dir -} - -func (d *dir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { - fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), kd, &d.OrderedChildren, &d.locks, &opts, kernfs.GenericDirectoryFDOptions{ - SeekEnd: kernfs.SeekEndStaticEntries, - }) - if err != nil { - return nil, err - } - return fd.VFSFileDescription(), nil -} - -func (d *dir) DecRef(ctx context.Context) { - d.dirRefs.DecRef(func() { d.Destroy(ctx) }) -} - -func (d *dir) NewDir(ctx context.Context, name string, opts vfs.MkdirOptions) (kernfs.Inode, error) { - creds := auth.CredentialsFromContext(ctx) - dir := d.fs.newDir(ctx, creds, opts.Mode, nil) - if err := d.OrderedChildren.Insert(name, dir); err != nil { - dir.DecRef(ctx) - return nil, err - } - d.TouchCMtime(ctx) - d.IncLinks(1) - return dir, nil -} - -func (d *dir) NewFile(ctx context.Context, name string, opts vfs.OpenOptions) (kernfs.Inode, error) { - creds := auth.CredentialsFromContext(ctx) - f := d.fs.newFile(ctx, creds, "") - if err := d.OrderedChildren.Insert(name, f); err != nil { - f.DecRef(ctx) - return nil, err - } - d.TouchCMtime(ctx) - return f, nil -} - -func (*dir) NewLink(context.Context, string, kernfs.Inode) (kernfs.Inode, error) { - return nil, linuxerr.EPERM -} - -func (*dir) NewSymlink(context.Context, string, string) (kernfs.Inode, error) { - return nil, linuxerr.EPERM -} - -func (*dir) NewNode(context.Context, string, vfs.MknodOptions) (kernfs.Inode, error) { - return nil, linuxerr.EPERM -} - -func (fsType) Name() string { - return "kernfs" -} - -func (fsType) Release(ctx context.Context) {} - -func (fst fsType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opt vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) { - fs := &filesystem{} - fs.VFSFilesystem().Init(vfsObj, &fst, fs) - root := fst.rootFn(ctx, creds, fs) - var d kernfs.Dentry - d.Init(&fs.Filesystem, root) - return fs.VFSFilesystem(), d.VFSDentry(), nil -} - -// -------------------- Remainder of the file are test cases -------------------- - -func TestBasic(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "file1": fs.newFile(ctx, creds, staticFileContent), - }) - }) - defer sys.Destroy() - sys.GetDentryOrDie(sys.PathOpAtRoot("file1")).DecRef(sys.Ctx) -} - -func TestMkdirGetDentry(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "dir1": fs.newDir(ctx, creds, 0755, nil), - }) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("dir1/a new directory") - if err := sys.VFS.MkdirAt(sys.Ctx, sys.Creds, pop, &vfs.MkdirOptions{Mode: 0755}); err != nil { - t.Fatalf("MkdirAt for PathOperation %+v failed: %v", pop, err) - } - sys.GetDentryOrDie(pop).DecRef(sys.Ctx) -} - -func TestReadStaticFile(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "file1": fs.newFile(ctx, creds, staticFileContent), - }) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("file1") - fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - if err != nil { - t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err) - } - defer fd.DecRef(sys.Ctx) - - content, err := sys.ReadToEnd(fd) - if err != nil { - t.Fatalf("Read failed: %v", err) - } - if diff := cmp.Diff(staticFileContent, content); diff != "" { - t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff) - } -} - -func TestCreateNewFileInStaticDir(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "dir1": fs.newDir(ctx, creds, 0755, nil), - }) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("dir1/newfile") - opts := &vfs.OpenOptions{Flags: linux.O_CREAT | linux.O_EXCL, Mode: defaultMode} - fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, opts) - if err != nil { - t.Fatalf("OpenAt(pop:%+v, opts:%+v) failed: %v", pop, opts, err) - } - - // Close the file. The file should persist. - fd.DecRef(sys.Ctx) - - fd, err = sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - if err != nil { - t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err) - } - fd.DecRef(sys.Ctx) -} - -func TestDirFDReadWrite(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, nil) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("/") - fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{ - Flags: linux.O_RDONLY, - }) - if err != nil { - t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err) - } - defer fd.DecRef(sys.Ctx) - - // Read/Write should fail for directory FDs. - if _, err := fd.Read(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.ReadOptions{}); !linuxerr.Equals(linuxerr.EISDIR, err) { - t.Fatalf("Read for directory FD failed with unexpected error: %v", err) - } - if _, err := fd.Write(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.WriteOptions{}); !linuxerr.Equals(linuxerr.EBADF, err) { - t.Fatalf("Write for directory FD failed with unexpected error: %v", err) - } -} - -func TestDirFDIterDirents(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newReadonlyDir(ctx, creds, 0755, map[string]kernfs.Inode{ - // Fill root with nodes backed by various inode implementations. - "dir1": fs.newReadonlyDir(ctx, creds, 0755, nil), - "dir2": fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "dir3": fs.newDir(ctx, creds, 0755, nil), - }), - "file1": fs.newFile(ctx, creds, staticFileContent), - }) - }) - defer sys.Destroy() - - pop := sys.PathOpAtRoot("/") - sys.AssertAllDirentTypes(sys.ListDirents(pop), map[string]testutil.DirentType{ - "dir1": linux.DT_DIR, - "dir2": linux.DT_DIR, - "file1": linux.DT_REG, - }) -} - -func TestDirWalkDentryTree(t *testing.T) { - sys := newTestSystem(t, func(ctx context.Context, creds *auth.Credentials, fs *filesystem) kernfs.Inode { - return fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "dir1": fs.newDir(ctx, creds, 0755, nil), - "dir2": fs.newDir(ctx, creds, 0755, map[string]kernfs.Inode{ - "file1": fs.newFile(ctx, creds, staticFileContent), - "dir3": fs.newDir(ctx, creds, 0755, nil), - }), - }) - }) - defer sys.Destroy() - - testWalk := func(from *kernfs.Dentry, getDentryPath, walkPath string, expectedErr error) { - var d *kernfs.Dentry - if getDentryPath != "" { - pop := sys.PathOpAtRoot(getDentryPath) - vd := sys.GetDentryOrDie(pop) - defer vd.DecRef(sys.Ctx) - d = vd.Dentry().Impl().(*kernfs.Dentry) - } - - match, err := from.WalkDentryTree(sys.Ctx, sys.VFS, fspath.Parse(walkPath)) - if err == nil { - defer match.DecRef(sys.Ctx) - } - - if err != expectedErr { - t.Fatalf("WalkDentryTree from %q to %q (with expected error: %v) unexpected error, want: %v, got: %v", from.FSLocalPath(), walkPath, expectedErr, expectedErr, err) - } - if expectedErr != nil { - return - } - - if d != match { - t.Fatalf("WalkDentryTree from %q to %q (with expected error: %v) found unexpected dentry; want: %v, got: %v", from.FSLocalPath(), walkPath, expectedErr, d, match) - } - } - - rootD := sys.Root.Dentry().Impl().(*kernfs.Dentry) - - testWalk(rootD, "dir1", "/dir1", nil) - testWalk(rootD, "", "/dir-non-existent", linuxerr.ENOENT) - testWalk(rootD, "", "/dir1/child-non-existent", linuxerr.ENOENT) - testWalk(rootD, "", "/dir2/inner-non-existent/dir3", linuxerr.ENOENT) - - testWalk(rootD, "dir2/dir3", "/dir2/../dir2/dir3", nil) - testWalk(rootD, "dir2/dir3", "/dir2/././dir3", nil) - testWalk(rootD, "dir2/dir3", "/dir2/././dir3/.././dir3", nil) - - pop := sys.PathOpAtRoot("dir2") - dir2VD := sys.GetDentryOrDie(pop) - defer dir2VD.DecRef(sys.Ctx) - dir2D := dir2VD.Dentry().Impl().(*kernfs.Dentry) - - testWalk(dir2D, "dir2/dir3", "/dir3", nil) - testWalk(dir2D, "dir2/dir3", "/../../../dir3", nil) - testWalk(dir2D, "dir2/file1", "/file1", nil) - testWalk(dir2D, "dir2/file1", "file1", nil) -} diff --git a/pkg/sentry/fsimpl/kernfs/slot_list.go b/pkg/sentry/fsimpl/kernfs/slot_list.go new file mode 100644 index 000000000..181fe7c8f --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/slot_list.go @@ -0,0 +1,221 @@ +package kernfs + +// 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 slotElementMapper struct{} + +// linkerFor maps an Element to a Linker. +// +// This default implementation should be inlined. +// +//go:nosplit +func (slotElementMapper) linkerFor(elem *slot) *slot { 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 slotList struct { + head *slot + tail *slot +} + +// Reset resets list l to the empty state. +func (l *slotList) Reset() { + l.head = nil + l.tail = nil +} + +// Empty returns true iff the list is empty. +// +//go:nosplit +func (l *slotList) Empty() bool { + return l.head == nil +} + +// Front returns the first element of list l or nil. +// +//go:nosplit +func (l *slotList) Front() *slot { + return l.head +} + +// Back returns the last element of list l or nil. +// +//go:nosplit +func (l *slotList) Back() *slot { + return l.tail +} + +// Len returns the number of elements in the list. +// +// NOTE: This is an O(n) operation. +// +//go:nosplit +func (l *slotList) Len() (count int) { + for e := l.Front(); e != nil; e = (slotElementMapper{}.linkerFor(e)).Next() { + count++ + } + return count +} + +// PushFront inserts the element e at the front of list l. +// +//go:nosplit +func (l *slotList) PushFront(e *slot) { + linker := slotElementMapper{}.linkerFor(e) + linker.SetNext(l.head) + linker.SetPrev(nil) + if l.head != nil { + slotElementMapper{}.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 *slotList) PushBack(e *slot) { + linker := slotElementMapper{}.linkerFor(e) + linker.SetNext(nil) + linker.SetPrev(l.tail) + if l.tail != nil { + slotElementMapper{}.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 *slotList) PushBackList(m *slotList) { + if l.head == nil { + l.head = m.head + l.tail = m.tail + } else if m.head != nil { + slotElementMapper{}.linkerFor(l.tail).SetNext(m.head) + slotElementMapper{}.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 *slotList) InsertAfter(b, e *slot) { + bLinker := slotElementMapper{}.linkerFor(b) + eLinker := slotElementMapper{}.linkerFor(e) + + a := bLinker.Next() + + eLinker.SetNext(a) + eLinker.SetPrev(b) + bLinker.SetNext(e) + + if a != nil { + slotElementMapper{}.linkerFor(a).SetPrev(e) + } else { + l.tail = e + } +} + +// InsertBefore inserts e before a. +// +//go:nosplit +func (l *slotList) InsertBefore(a, e *slot) { + aLinker := slotElementMapper{}.linkerFor(a) + eLinker := slotElementMapper{}.linkerFor(e) + + b := aLinker.Prev() + eLinker.SetNext(a) + eLinker.SetPrev(b) + aLinker.SetPrev(e) + + if b != nil { + slotElementMapper{}.linkerFor(b).SetNext(e) + } else { + l.head = e + } +} + +// Remove removes e from l. +// +//go:nosplit +func (l *slotList) Remove(e *slot) { + linker := slotElementMapper{}.linkerFor(e) + prev := linker.Prev() + next := linker.Next() + + if prev != nil { + slotElementMapper{}.linkerFor(prev).SetNext(next) + } else if l.head == e { + l.head = next + } + + if next != nil { + slotElementMapper{}.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 slotEntry struct { + next *slot + prev *slot +} + +// Next returns the entry that follows e in the list. +// +//go:nosplit +func (e *slotEntry) Next() *slot { + return e.next +} + +// Prev returns the entry that precedes e in the list. +// +//go:nosplit +func (e *slotEntry) Prev() *slot { + return e.prev +} + +// SetNext assigns 'entry' as the entry that follows e in the list. +// +//go:nosplit +func (e *slotEntry) SetNext(elem *slot) { + e.next = elem +} + +// SetPrev assigns 'entry' as the entry that precedes e in the list. +// +//go:nosplit +func (e *slotEntry) SetPrev(elem *slot) { + e.prev = elem +} diff --git a/pkg/sentry/fsimpl/kernfs/static_directory_refs.go b/pkg/sentry/fsimpl/kernfs/static_directory_refs.go new file mode 100644 index 000000000..69534a2d2 --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/static_directory_refs.go @@ -0,0 +1,140 @@ +package kernfs + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const StaticDirectoryenableLogging = false + +// obj 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 StaticDirectoryobj *StaticDirectory + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type StaticDirectoryRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *StaticDirectoryRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *StaticDirectoryRefs) RefType() string { + return fmt.Sprintf("%T", StaticDirectoryobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *StaticDirectoryRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *StaticDirectoryRefs) LogRefs() bool { + return StaticDirectoryenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *StaticDirectoryRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *StaticDirectoryRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if StaticDirectoryenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *StaticDirectoryRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if StaticDirectoryenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *StaticDirectoryRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if StaticDirectoryenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *StaticDirectoryRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go b/pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go new file mode 100644 index 000000000..3c5fdf15e --- /dev/null +++ b/pkg/sentry/fsimpl/kernfs/synthetic_directory_refs.go @@ -0,0 +1,140 @@ +package kernfs + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const syntheticDirectoryenableLogging = false + +// obj 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 syntheticDirectoryobj *syntheticDirectory + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type syntheticDirectoryRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *syntheticDirectoryRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *syntheticDirectoryRefs) RefType() string { + return fmt.Sprintf("%T", syntheticDirectoryobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *syntheticDirectoryRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *syntheticDirectoryRefs) LogRefs() bool { + return syntheticDirectoryenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *syntheticDirectoryRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *syntheticDirectoryRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if syntheticDirectoryenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *syntheticDirectoryRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if syntheticDirectoryenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *syntheticDirectoryRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if syntheticDirectoryenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *syntheticDirectoryRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/overlay/BUILD b/pkg/sentry/fsimpl/overlay/BUILD deleted file mode 100644 index d16dfef9b..000000000 --- a/pkg/sentry/fsimpl/overlay/BUILD +++ /dev/null @@ -1,48 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//tools/go_generics:defs.bzl", "go_template_instance") - -licenses(["notice"]) - -go_template_instance( - name = "fstree", - out = "fstree.go", - package = "overlay", - prefix = "generic", - template = "//pkg/sentry/vfs/genericfstree:generic_fstree", - types = { - "Dentry": "dentry", - }, -) - -go_library( - name = "overlay", - srcs = [ - "copy_up.go", - "directory.go", - "filesystem.go", - "fstree.go", - "overlay.go", - "regular_file.go", - "save_restore.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/hostarch", - "//pkg/log", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/sentry/arch", - "//pkg/sentry/fs/lock", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/memmap", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/usermem", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/fsimpl/overlay/fstree.go b/pkg/sentry/fsimpl/overlay/fstree.go new file mode 100644 index 000000000..c3eb062ed --- /dev/null +++ b/pkg/sentry/fsimpl/overlay/fstree.go @@ -0,0 +1,55 @@ +package overlay + +import ( + "gvisor.dev/gvisor/pkg/fspath" + "gvisor.dev/gvisor/pkg/sentry/vfs" +) + +// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is +// either d2's parent or an ancestor of d2's parent. +func genericIsAncestorDentry(d, d2 *dentry) bool { + for d2 != nil { + if d2.parent == d { + return true + } + if d2.parent == d2 { + return false + } + d2 = d2.parent + } + return false +} + +// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d. +func genericParentOrSelf(d *dentry) *dentry { + if d.parent != nil { + return d.parent + } + return d +} + +// PrependPath is a generic implementation of FilesystemImpl.PrependPath(). +func genericPrependPath(vfsroot vfs.VirtualDentry, mnt *vfs.Mount, d *dentry, b *fspath.Builder) error { + for { + if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() { + return vfs.PrependPathAtVFSRootError{} + } + if mnt != nil && &d.vfsd == mnt.Root() { + return nil + } + if d.parent == nil { + return vfs.PrependPathAtNonMountRootError{} + } + b.PrependComponent(d.name) + d = d.parent + } +} + +// DebugPathname returns a pathname to d relative to its filesystem root. +// DebugPathname does not correspond to any Linux function; it's used to +// generate dentry pathnames for debugging. +func genericDebugPathname(d *dentry) string { + var b fspath.Builder + _ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b) + return b.String() +} diff --git a/pkg/sentry/fsimpl/overlay/overlay_state_autogen.go b/pkg/sentry/fsimpl/overlay/overlay_state_autogen.go new file mode 100644 index 000000000..923a2e71a --- /dev/null +++ b/pkg/sentry/fsimpl/overlay/overlay_state_autogen.go @@ -0,0 +1,321 @@ +// automatically generated by stateify. + +package overlay + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (fd *directoryFD) StateTypeName() string { + return "pkg/sentry/fsimpl/overlay.directoryFD" +} + +func (fd *directoryFD) StateFields() []string { + return []string{ + "fileDescription", + "DirectoryFileDescriptionDefaultImpl", + "DentryMetadataFileDescriptionImpl", + "off", + "dirents", + } +} + +func (fd *directoryFD) beforeSave() {} + +// +checklocksignore +func (fd *directoryFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.fileDescription) + stateSinkObject.Save(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSinkObject.Save(2, &fd.DentryMetadataFileDescriptionImpl) + stateSinkObject.Save(3, &fd.off) + stateSinkObject.Save(4, &fd.dirents) +} + +func (fd *directoryFD) afterLoad() {} + +// +checklocksignore +func (fd *directoryFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.fileDescription) + stateSourceObject.Load(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSourceObject.Load(2, &fd.DentryMetadataFileDescriptionImpl) + stateSourceObject.Load(3, &fd.off) + stateSourceObject.Load(4, &fd.dirents) +} + +func (fstype *FilesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/overlay.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 (f *FilesystemOptions) StateTypeName() string { + return "pkg/sentry/fsimpl/overlay.FilesystemOptions" +} + +func (f *FilesystemOptions) StateFields() []string { + return []string{ + "UpperRoot", + "LowerRoots", + } +} + +func (f *FilesystemOptions) beforeSave() {} + +// +checklocksignore +func (f *FilesystemOptions) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.UpperRoot) + stateSinkObject.Save(1, &f.LowerRoots) +} + +func (f *FilesystemOptions) afterLoad() {} + +// +checklocksignore +func (f *FilesystemOptions) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.UpperRoot) + stateSourceObject.Load(1, &f.LowerRoots) +} + +func (fs *filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/overlay.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "vfsfs", + "opts", + "creds", + "privateDevMinors", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.vfsfs) + stateSinkObject.Save(1, &fs.opts) + stateSinkObject.Save(2, &fs.creds) + stateSinkObject.Save(3, &fs.privateDevMinors) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.vfsfs) + stateSourceObject.Load(1, &fs.opts) + stateSourceObject.Load(2, &fs.creds) + stateSourceObject.Load(3, &fs.privateDevMinors) +} + +func (l *layerDevNumber) StateTypeName() string { + return "pkg/sentry/fsimpl/overlay.layerDevNumber" +} + +func (l *layerDevNumber) StateFields() []string { + return []string{ + "major", + "minor", + } +} + +func (l *layerDevNumber) beforeSave() {} + +// +checklocksignore +func (l *layerDevNumber) StateSave(stateSinkObject state.Sink) { + l.beforeSave() + stateSinkObject.Save(0, &l.major) + stateSinkObject.Save(1, &l.minor) +} + +func (l *layerDevNumber) afterLoad() {} + +// +checklocksignore +func (l *layerDevNumber) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &l.major) + stateSourceObject.Load(1, &l.minor) +} + +func (d *dentry) StateTypeName() string { + return "pkg/sentry/fsimpl/overlay.dentry" +} + +func (d *dentry) StateFields() []string { + return []string{ + "vfsd", + "refs", + "fs", + "mode", + "uid", + "gid", + "copiedUp", + "parent", + "name", + "children", + "dirents", + "upperVD", + "lowerVDs", + "inlineLowerVDs", + "devMajor", + "devMinor", + "ino", + "lowerMappings", + "wrappedMappable", + "isMappable", + "locks", + "watches", + } +} + +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.copiedUp) + stateSinkObject.Save(7, &d.parent) + stateSinkObject.Save(8, &d.name) + stateSinkObject.Save(9, &d.children) + stateSinkObject.Save(10, &d.dirents) + stateSinkObject.Save(11, &d.upperVD) + stateSinkObject.Save(12, &d.lowerVDs) + stateSinkObject.Save(13, &d.inlineLowerVDs) + stateSinkObject.Save(14, &d.devMajor) + stateSinkObject.Save(15, &d.devMinor) + stateSinkObject.Save(16, &d.ino) + stateSinkObject.Save(17, &d.lowerMappings) + stateSinkObject.Save(18, &d.wrappedMappable) + stateSinkObject.Save(19, &d.isMappable) + stateSinkObject.Save(20, &d.locks) + stateSinkObject.Save(21, &d.watches) +} + +// +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.copiedUp) + stateSourceObject.Load(7, &d.parent) + stateSourceObject.Load(8, &d.name) + stateSourceObject.Load(9, &d.children) + stateSourceObject.Load(10, &d.dirents) + stateSourceObject.Load(11, &d.upperVD) + stateSourceObject.Load(12, &d.lowerVDs) + stateSourceObject.Load(13, &d.inlineLowerVDs) + stateSourceObject.Load(14, &d.devMajor) + stateSourceObject.Load(15, &d.devMinor) + stateSourceObject.Load(16, &d.ino) + stateSourceObject.Load(17, &d.lowerMappings) + stateSourceObject.Load(18, &d.wrappedMappable) + stateSourceObject.Load(19, &d.isMappable) + stateSourceObject.Load(20, &d.locks) + stateSourceObject.Load(21, &d.watches) + stateSourceObject.AfterLoad(d.afterLoad) +} + +func (fd *fileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/overlay.fileDescription" +} + +func (fd *fileDescription) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "LockFD", + } +} + +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.LockFD) +} + +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.LockFD) +} + +func (fd *regularFileFD) StateTypeName() string { + return "pkg/sentry/fsimpl/overlay.regularFileFD" +} + +func (fd *regularFileFD) StateFields() []string { + return []string{ + "fileDescription", + "copiedUp", + "cachedFD", + "cachedFlags", + "lowerWaiters", + } +} + +func (fd *regularFileFD) beforeSave() {} + +// +checklocksignore +func (fd *regularFileFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.fileDescription) + stateSinkObject.Save(1, &fd.copiedUp) + stateSinkObject.Save(2, &fd.cachedFD) + stateSinkObject.Save(3, &fd.cachedFlags) + stateSinkObject.Save(4, &fd.lowerWaiters) +} + +func (fd *regularFileFD) afterLoad() {} + +// +checklocksignore +func (fd *regularFileFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.fileDescription) + stateSourceObject.Load(1, &fd.copiedUp) + stateSourceObject.Load(2, &fd.cachedFD) + stateSourceObject.Load(3, &fd.cachedFlags) + stateSourceObject.Load(4, &fd.lowerWaiters) +} + +func init() { + state.Register((*directoryFD)(nil)) + state.Register((*FilesystemType)(nil)) + state.Register((*FilesystemOptions)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*layerDevNumber)(nil)) + state.Register((*dentry)(nil)) + state.Register((*fileDescription)(nil)) + state.Register((*regularFileFD)(nil)) +} diff --git a/pkg/sentry/fsimpl/pipefs/BUILD b/pkg/sentry/fsimpl/pipefs/BUILD deleted file mode 100644 index a50510031..000000000 --- a/pkg/sentry/fsimpl/pipefs/BUILD +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -licenses(["notice"]) - -go_library( - name = "pipefs", - srcs = ["pipefs.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/hostarch", - "//pkg/sentry/fsimpl/kernfs", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/pipe", - "//pkg/sentry/kernel/time", - "//pkg/sentry/vfs", - ], -) diff --git a/pkg/sentry/fsimpl/pipefs/pipefs_state_autogen.go b/pkg/sentry/fsimpl/pipefs/pipefs_state_autogen.go new file mode 100644 index 000000000..5f9e117c3 --- /dev/null +++ b/pkg/sentry/fsimpl/pipefs/pipefs_state_autogen.go @@ -0,0 +1,111 @@ +// automatically generated by stateify. + +package pipefs + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (f *filesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/pipefs.filesystemType" +} + +func (f *filesystemType) StateFields() []string { + return []string{} +} + +func (f *filesystemType) beforeSave() {} + +// +checklocksignore +func (f *filesystemType) StateSave(stateSinkObject state.Sink) { + f.beforeSave() +} + +func (f *filesystemType) afterLoad() {} + +// +checklocksignore +func (f *filesystemType) StateLoad(stateSourceObject state.Source) { +} + +func (fs *filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/pipefs.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "Filesystem", + "devMinor", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Filesystem) + stateSinkObject.Save(1, &fs.devMinor) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Filesystem) + stateSourceObject.Load(1, &fs.devMinor) +} + +func (i *inode) StateTypeName() string { + return "pkg/sentry/fsimpl/pipefs.inode" +} + +func (i *inode) StateFields() []string { + return []string{ + "InodeNotDirectory", + "InodeNotSymlink", + "InodeNoopRefCount", + "locks", + "pipe", + "ino", + "uid", + "gid", + "ctime", + } +} + +func (i *inode) beforeSave() {} + +// +checklocksignore +func (i *inode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.InodeNotDirectory) + stateSinkObject.Save(1, &i.InodeNotSymlink) + stateSinkObject.Save(2, &i.InodeNoopRefCount) + stateSinkObject.Save(3, &i.locks) + stateSinkObject.Save(4, &i.pipe) + stateSinkObject.Save(5, &i.ino) + stateSinkObject.Save(6, &i.uid) + stateSinkObject.Save(7, &i.gid) + stateSinkObject.Save(8, &i.ctime) +} + +func (i *inode) afterLoad() {} + +// +checklocksignore +func (i *inode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeNotDirectory) + stateSourceObject.Load(1, &i.InodeNotSymlink) + stateSourceObject.Load(2, &i.InodeNoopRefCount) + stateSourceObject.Load(3, &i.locks) + stateSourceObject.Load(4, &i.pipe) + stateSourceObject.Load(5, &i.ino) + stateSourceObject.Load(6, &i.uid) + stateSourceObject.Load(7, &i.gid) + stateSourceObject.Load(8, &i.ctime) +} + +func init() { + state.Register((*filesystemType)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*inode)(nil)) +} diff --git a/pkg/sentry/fsimpl/proc/BUILD b/pkg/sentry/fsimpl/proc/BUILD deleted file mode 100644 index 95cfbdc42..000000000 --- a/pkg/sentry/fsimpl/proc/BUILD +++ /dev/null @@ -1,133 +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 = "fd_dir_inode_refs", - out = "fd_dir_inode_refs.go", - package = "proc", - prefix = "fdDirInode", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "fdDirInode", - }, -) - -go_template_instance( - name = "fd_info_dir_inode_refs", - out = "fd_info_dir_inode_refs.go", - package = "proc", - prefix = "fdInfoDirInode", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "fdInfoDirInode", - }, -) - -go_template_instance( - name = "subtasks_inode_refs", - out = "subtasks_inode_refs.go", - package = "proc", - prefix = "subtasksInode", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "subtasksInode", - }, -) - -go_template_instance( - name = "task_inode_refs", - out = "task_inode_refs.go", - package = "proc", - prefix = "taskInode", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "taskInode", - }, -) - -go_template_instance( - name = "tasks_inode_refs", - out = "tasks_inode_refs.go", - package = "proc", - prefix = "tasksInode", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "tasksInode", - }, -) - -go_library( - name = "proc", - srcs = [ - "fd_dir_inode_refs.go", - "fd_info_dir_inode_refs.go", - "filesystem.go", - "subtasks.go", - "subtasks_inode_refs.go", - "task.go", - "task_fds.go", - "task_files.go", - "task_inode_refs.go", - "task_net.go", - "tasks.go", - "tasks_files.go", - "tasks_inode_refs.go", - "tasks_sys.go", - "yama.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/log", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/safemem", - "//pkg/sentry/fs/lock", - "//pkg/sentry/fsbridge", - "//pkg/sentry/fsimpl/kernfs", - "//pkg/sentry/inet", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/time", - "//pkg/sentry/limits", - "//pkg/sentry/mm", - "//pkg/sentry/socket", - "//pkg/sentry/socket/unix", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/usage", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/tcpip/header", - "//pkg/tcpip/network/ipv4", - "//pkg/usermem", - ], -) - -go_test( - name = "proc_test", - size = "small", - srcs = [ - "tasks_sys_test.go", - "tasks_test.go", - ], - library = ":proc", - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/sentry/contexttest", - "//pkg/sentry/fsimpl/testutil", - "//pkg/sentry/fsimpl/tmpfs", - "//pkg/sentry/inet", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - "//pkg/usermem", - ], -) diff --git a/pkg/sentry/fsimpl/proc/fd_dir_inode_refs.go b/pkg/sentry/fsimpl/proc/fd_dir_inode_refs.go new file mode 100644 index 000000000..61138f055 --- /dev/null +++ b/pkg/sentry/fsimpl/proc/fd_dir_inode_refs.go @@ -0,0 +1,140 @@ +package proc + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const fdDirInodeenableLogging = false + +// obj 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 fdDirInodeobj *fdDirInode + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type fdDirInodeRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *fdDirInodeRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *fdDirInodeRefs) RefType() string { + return fmt.Sprintf("%T", fdDirInodeobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *fdDirInodeRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *fdDirInodeRefs) LogRefs() bool { + return fdDirInodeenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *fdDirInodeRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *fdDirInodeRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if fdDirInodeenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *fdDirInodeRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if fdDirInodeenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *fdDirInodeRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if fdDirInodeenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *fdDirInodeRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/proc/fd_info_dir_inode_refs.go b/pkg/sentry/fsimpl/proc/fd_info_dir_inode_refs.go new file mode 100644 index 000000000..53fb0910a --- /dev/null +++ b/pkg/sentry/fsimpl/proc/fd_info_dir_inode_refs.go @@ -0,0 +1,140 @@ +package proc + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const fdInfoDirInodeenableLogging = false + +// obj 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 fdInfoDirInodeobj *fdInfoDirInode + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type fdInfoDirInodeRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *fdInfoDirInodeRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *fdInfoDirInodeRefs) RefType() string { + return fmt.Sprintf("%T", fdInfoDirInodeobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *fdInfoDirInodeRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *fdInfoDirInodeRefs) LogRefs() bool { + return fdInfoDirInodeenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *fdInfoDirInodeRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *fdInfoDirInodeRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if fdInfoDirInodeenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *fdInfoDirInodeRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if fdInfoDirInodeenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *fdInfoDirInodeRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if fdInfoDirInodeenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *fdInfoDirInodeRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/proc/proc_state_autogen.go b/pkg/sentry/fsimpl/proc/proc_state_autogen.go new file mode 100644 index 000000000..e32e7671c --- /dev/null +++ b/pkg/sentry/fsimpl/proc/proc_state_autogen.go @@ -0,0 +1,2454 @@ +// automatically generated by stateify. + +package proc + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (r *fdDirInodeRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.fdDirInodeRefs" +} + +func (r *fdDirInodeRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *fdDirInodeRefs) beforeSave() {} + +// +checklocksignore +func (r *fdDirInodeRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *fdDirInodeRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (r *fdInfoDirInodeRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.fdInfoDirInodeRefs" +} + +func (r *fdInfoDirInodeRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *fdInfoDirInodeRefs) beforeSave() {} + +// +checklocksignore +func (r *fdInfoDirInodeRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *fdInfoDirInodeRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (ft *FilesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.FilesystemType" +} + +func (ft *FilesystemType) StateFields() []string { + return []string{} +} + +func (ft *FilesystemType) beforeSave() {} + +// +checklocksignore +func (ft *FilesystemType) StateSave(stateSinkObject state.Sink) { + ft.beforeSave() +} + +func (ft *FilesystemType) afterLoad() {} + +// +checklocksignore +func (ft *FilesystemType) StateLoad(stateSourceObject state.Source) { +} + +func (fs *filesystem) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "Filesystem", + "devMinor", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Filesystem) + stateSinkObject.Save(1, &fs.devMinor) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Filesystem) + stateSourceObject.Load(1, &fs.devMinor) +} + +func (s *staticFile) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.staticFile" +} + +func (s *staticFile) StateFields() []string { + return []string{ + "DynamicBytesFile", + "StaticData", + } +} + +func (s *staticFile) beforeSave() {} + +// +checklocksignore +func (s *staticFile) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.DynamicBytesFile) + stateSinkObject.Save(1, &s.StaticData) +} + +func (s *staticFile) afterLoad() {} + +// +checklocksignore +func (s *staticFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.DynamicBytesFile) + stateSourceObject.Load(1, &s.StaticData) +} + +func (i *InternalData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.InternalData" +} + +func (i *InternalData) StateFields() []string { + return []string{ + "Cgroups", + } +} + +func (i *InternalData) beforeSave() {} + +// +checklocksignore +func (i *InternalData) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.Cgroups) +} + +func (i *InternalData) afterLoad() {} + +// +checklocksignore +func (i *InternalData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.Cgroups) +} + +func (i *implStatFS) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.implStatFS" +} + +func (i *implStatFS) StateFields() []string { + return []string{} +} + +func (i *implStatFS) beforeSave() {} + +// +checklocksignore +func (i *implStatFS) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *implStatFS) afterLoad() {} + +// +checklocksignore +func (i *implStatFS) StateLoad(stateSourceObject state.Source) { +} + +func (i *subtasksInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.subtasksInode" +} + +func (i *subtasksInode) StateFields() []string { + return []string{ + "implStatFS", + "InodeAlwaysValid", + "InodeAttrs", + "InodeDirectoryNoNewChildren", + "InodeNotSymlink", + "InodeTemporary", + "OrderedChildren", + "subtasksInodeRefs", + "locks", + "fs", + "task", + "pidns", + "cgroupControllers", + } +} + +func (i *subtasksInode) beforeSave() {} + +// +checklocksignore +func (i *subtasksInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.implStatFS) + stateSinkObject.Save(1, &i.InodeAlwaysValid) + stateSinkObject.Save(2, &i.InodeAttrs) + stateSinkObject.Save(3, &i.InodeDirectoryNoNewChildren) + stateSinkObject.Save(4, &i.InodeNotSymlink) + stateSinkObject.Save(5, &i.InodeTemporary) + stateSinkObject.Save(6, &i.OrderedChildren) + stateSinkObject.Save(7, &i.subtasksInodeRefs) + stateSinkObject.Save(8, &i.locks) + stateSinkObject.Save(9, &i.fs) + stateSinkObject.Save(10, &i.task) + stateSinkObject.Save(11, &i.pidns) + stateSinkObject.Save(12, &i.cgroupControllers) +} + +func (i *subtasksInode) afterLoad() {} + +// +checklocksignore +func (i *subtasksInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.implStatFS) + stateSourceObject.Load(1, &i.InodeAlwaysValid) + stateSourceObject.Load(2, &i.InodeAttrs) + stateSourceObject.Load(3, &i.InodeDirectoryNoNewChildren) + stateSourceObject.Load(4, &i.InodeNotSymlink) + stateSourceObject.Load(5, &i.InodeTemporary) + stateSourceObject.Load(6, &i.OrderedChildren) + stateSourceObject.Load(7, &i.subtasksInodeRefs) + stateSourceObject.Load(8, &i.locks) + stateSourceObject.Load(9, &i.fs) + stateSourceObject.Load(10, &i.task) + stateSourceObject.Load(11, &i.pidns) + stateSourceObject.Load(12, &i.cgroupControllers) +} + +func (fd *subtasksFD) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.subtasksFD" +} + +func (fd *subtasksFD) StateFields() []string { + return []string{ + "GenericDirectoryFD", + "task", + } +} + +func (fd *subtasksFD) beforeSave() {} + +// +checklocksignore +func (fd *subtasksFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.GenericDirectoryFD) + stateSinkObject.Save(1, &fd.task) +} + +func (fd *subtasksFD) afterLoad() {} + +// +checklocksignore +func (fd *subtasksFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.GenericDirectoryFD) + stateSourceObject.Load(1, &fd.task) +} + +func (r *subtasksInodeRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.subtasksInodeRefs" +} + +func (r *subtasksInodeRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *subtasksInodeRefs) beforeSave() {} + +// +checklocksignore +func (r *subtasksInodeRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *subtasksInodeRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (i *taskInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.taskInode" +} + +func (i *taskInode) StateFields() []string { + return []string{ + "implStatFS", + "InodeAttrs", + "InodeDirectoryNoNewChildren", + "InodeNotSymlink", + "InodeTemporary", + "OrderedChildren", + "taskInodeRefs", + "locks", + "task", + } +} + +func (i *taskInode) beforeSave() {} + +// +checklocksignore +func (i *taskInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.implStatFS) + stateSinkObject.Save(1, &i.InodeAttrs) + stateSinkObject.Save(2, &i.InodeDirectoryNoNewChildren) + stateSinkObject.Save(3, &i.InodeNotSymlink) + stateSinkObject.Save(4, &i.InodeTemporary) + stateSinkObject.Save(5, &i.OrderedChildren) + stateSinkObject.Save(6, &i.taskInodeRefs) + stateSinkObject.Save(7, &i.locks) + stateSinkObject.Save(8, &i.task) +} + +func (i *taskInode) afterLoad() {} + +// +checklocksignore +func (i *taskInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.implStatFS) + stateSourceObject.Load(1, &i.InodeAttrs) + stateSourceObject.Load(2, &i.InodeDirectoryNoNewChildren) + stateSourceObject.Load(3, &i.InodeNotSymlink) + stateSourceObject.Load(4, &i.InodeTemporary) + stateSourceObject.Load(5, &i.OrderedChildren) + stateSourceObject.Load(6, &i.taskInodeRefs) + stateSourceObject.Load(7, &i.locks) + stateSourceObject.Load(8, &i.task) +} + +func (i *taskOwnedInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.taskOwnedInode" +} + +func (i *taskOwnedInode) StateFields() []string { + return []string{ + "Inode", + "owner", + } +} + +func (i *taskOwnedInode) beforeSave() {} + +// +checklocksignore +func (i *taskOwnedInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.Inode) + stateSinkObject.Save(1, &i.owner) +} + +func (i *taskOwnedInode) afterLoad() {} + +// +checklocksignore +func (i *taskOwnedInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.Inode) + stateSourceObject.Load(1, &i.owner) +} + +func (i *fdDir) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.fdDir" +} + +func (i *fdDir) StateFields() []string { + return []string{ + "locks", + "fs", + "task", + "produceSymlink", + } +} + +func (i *fdDir) beforeSave() {} + +// +checklocksignore +func (i *fdDir) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.locks) + stateSinkObject.Save(1, &i.fs) + stateSinkObject.Save(2, &i.task) + stateSinkObject.Save(3, &i.produceSymlink) +} + +func (i *fdDir) afterLoad() {} + +// +checklocksignore +func (i *fdDir) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.locks) + stateSourceObject.Load(1, &i.fs) + stateSourceObject.Load(2, &i.task) + stateSourceObject.Load(3, &i.produceSymlink) +} + +func (i *fdDirInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.fdDirInode" +} + +func (i *fdDirInode) StateFields() []string { + return []string{ + "fdDir", + "fdDirInodeRefs", + "implStatFS", + "InodeAlwaysValid", + "InodeAttrs", + "InodeDirectoryNoNewChildren", + "InodeNotSymlink", + "InodeTemporary", + "OrderedChildren", + } +} + +func (i *fdDirInode) beforeSave() {} + +// +checklocksignore +func (i *fdDirInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.fdDir) + stateSinkObject.Save(1, &i.fdDirInodeRefs) + stateSinkObject.Save(2, &i.implStatFS) + stateSinkObject.Save(3, &i.InodeAlwaysValid) + stateSinkObject.Save(4, &i.InodeAttrs) + stateSinkObject.Save(5, &i.InodeDirectoryNoNewChildren) + stateSinkObject.Save(6, &i.InodeNotSymlink) + stateSinkObject.Save(7, &i.InodeTemporary) + stateSinkObject.Save(8, &i.OrderedChildren) +} + +func (i *fdDirInode) afterLoad() {} + +// +checklocksignore +func (i *fdDirInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.fdDir) + stateSourceObject.Load(1, &i.fdDirInodeRefs) + stateSourceObject.Load(2, &i.implStatFS) + stateSourceObject.Load(3, &i.InodeAlwaysValid) + stateSourceObject.Load(4, &i.InodeAttrs) + stateSourceObject.Load(5, &i.InodeDirectoryNoNewChildren) + stateSourceObject.Load(6, &i.InodeNotSymlink) + stateSourceObject.Load(7, &i.InodeTemporary) + stateSourceObject.Load(8, &i.OrderedChildren) +} + +func (s *fdSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.fdSymlink" +} + +func (s *fdSymlink) StateFields() []string { + return []string{ + "implStatFS", + "InodeAttrs", + "InodeNoopRefCount", + "InodeSymlink", + "fs", + "task", + "fd", + } +} + +func (s *fdSymlink) beforeSave() {} + +// +checklocksignore +func (s *fdSymlink) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.implStatFS) + stateSinkObject.Save(1, &s.InodeAttrs) + stateSinkObject.Save(2, &s.InodeNoopRefCount) + stateSinkObject.Save(3, &s.InodeSymlink) + stateSinkObject.Save(4, &s.fs) + stateSinkObject.Save(5, &s.task) + stateSinkObject.Save(6, &s.fd) +} + +func (s *fdSymlink) afterLoad() {} + +// +checklocksignore +func (s *fdSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.implStatFS) + stateSourceObject.Load(1, &s.InodeAttrs) + stateSourceObject.Load(2, &s.InodeNoopRefCount) + stateSourceObject.Load(3, &s.InodeSymlink) + stateSourceObject.Load(4, &s.fs) + stateSourceObject.Load(5, &s.task) + stateSourceObject.Load(6, &s.fd) +} + +func (i *fdInfoDirInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.fdInfoDirInode" +} + +func (i *fdInfoDirInode) StateFields() []string { + return []string{ + "fdDir", + "fdInfoDirInodeRefs", + "implStatFS", + "InodeAlwaysValid", + "InodeAttrs", + "InodeDirectoryNoNewChildren", + "InodeNotSymlink", + "InodeTemporary", + "OrderedChildren", + } +} + +func (i *fdInfoDirInode) beforeSave() {} + +// +checklocksignore +func (i *fdInfoDirInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.fdDir) + stateSinkObject.Save(1, &i.fdInfoDirInodeRefs) + stateSinkObject.Save(2, &i.implStatFS) + stateSinkObject.Save(3, &i.InodeAlwaysValid) + stateSinkObject.Save(4, &i.InodeAttrs) + stateSinkObject.Save(5, &i.InodeDirectoryNoNewChildren) + stateSinkObject.Save(6, &i.InodeNotSymlink) + stateSinkObject.Save(7, &i.InodeTemporary) + stateSinkObject.Save(8, &i.OrderedChildren) +} + +func (i *fdInfoDirInode) afterLoad() {} + +// +checklocksignore +func (i *fdInfoDirInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.fdDir) + stateSourceObject.Load(1, &i.fdInfoDirInodeRefs) + stateSourceObject.Load(2, &i.implStatFS) + stateSourceObject.Load(3, &i.InodeAlwaysValid) + stateSourceObject.Load(4, &i.InodeAttrs) + stateSourceObject.Load(5, &i.InodeDirectoryNoNewChildren) + stateSourceObject.Load(6, &i.InodeNotSymlink) + stateSourceObject.Load(7, &i.InodeTemporary) + stateSourceObject.Load(8, &i.OrderedChildren) +} + +func (d *fdInfoData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.fdInfoData" +} + +func (d *fdInfoData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "fs", + "task", + "fd", + } +} + +func (d *fdInfoData) beforeSave() {} + +// +checklocksignore +func (d *fdInfoData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.fs) + stateSinkObject.Save(2, &d.task) + stateSinkObject.Save(3, &d.fd) +} + +func (d *fdInfoData) afterLoad() {} + +// +checklocksignore +func (d *fdInfoData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.fs) + stateSourceObject.Load(2, &d.task) + stateSourceObject.Load(3, &d.fd) +} + +func (d *auxvData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.auxvData" +} + +func (d *auxvData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + } +} + +func (d *auxvData) beforeSave() {} + +// +checklocksignore +func (d *auxvData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.task) +} + +func (d *auxvData) afterLoad() {} + +// +checklocksignore +func (d *auxvData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.task) +} + +func (d *cmdlineData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.cmdlineData" +} + +func (d *cmdlineData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + "arg", + } +} + +func (d *cmdlineData) beforeSave() {} + +// +checklocksignore +func (d *cmdlineData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.task) + stateSinkObject.Save(2, &d.arg) +} + +func (d *cmdlineData) afterLoad() {} + +// +checklocksignore +func (d *cmdlineData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.task) + stateSourceObject.Load(2, &d.arg) +} + +func (i *commInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.commInode" +} + +func (i *commInode) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + } +} + +func (i *commInode) beforeSave() {} + +// +checklocksignore +func (i *commInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.DynamicBytesFile) + stateSinkObject.Save(1, &i.task) +} + +func (i *commInode) afterLoad() {} + +// +checklocksignore +func (i *commInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.DynamicBytesFile) + stateSourceObject.Load(1, &i.task) +} + +func (d *commData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.commData" +} + +func (d *commData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + } +} + +func (d *commData) beforeSave() {} + +// +checklocksignore +func (d *commData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.task) +} + +func (d *commData) afterLoad() {} + +// +checklocksignore +func (d *commData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.task) +} + +func (d *idMapData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.idMapData" +} + +func (d *idMapData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + "gids", + } +} + +func (d *idMapData) beforeSave() {} + +// +checklocksignore +func (d *idMapData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.task) + stateSinkObject.Save(2, &d.gids) +} + +func (d *idMapData) afterLoad() {} + +// +checklocksignore +func (d *idMapData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.task) + stateSourceObject.Load(2, &d.gids) +} + +func (f *memInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.memInode" +} + +func (f *memInode) StateFields() []string { + return []string{ + "InodeAttrs", + "InodeNoStatFS", + "InodeNoopRefCount", + "InodeNotDirectory", + "InodeNotSymlink", + "task", + "locks", + } +} + +func (f *memInode) beforeSave() {} + +// +checklocksignore +func (f *memInode) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.InodeAttrs) + stateSinkObject.Save(1, &f.InodeNoStatFS) + stateSinkObject.Save(2, &f.InodeNoopRefCount) + stateSinkObject.Save(3, &f.InodeNotDirectory) + stateSinkObject.Save(4, &f.InodeNotSymlink) + stateSinkObject.Save(5, &f.task) + stateSinkObject.Save(6, &f.locks) +} + +func (f *memInode) afterLoad() {} + +// +checklocksignore +func (f *memInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.InodeAttrs) + stateSourceObject.Load(1, &f.InodeNoStatFS) + stateSourceObject.Load(2, &f.InodeNoopRefCount) + stateSourceObject.Load(3, &f.InodeNotDirectory) + stateSourceObject.Load(4, &f.InodeNotSymlink) + stateSourceObject.Load(5, &f.task) + stateSourceObject.Load(6, &f.locks) +} + +func (fd *memFD) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.memFD" +} + +func (fd *memFD) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "LockFD", + "inode", + "offset", + } +} + +func (fd *memFD) beforeSave() {} + +// +checklocksignore +func (fd *memFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.vfsfd) + stateSinkObject.Save(1, &fd.FileDescriptionDefaultImpl) + stateSinkObject.Save(2, &fd.LockFD) + stateSinkObject.Save(3, &fd.inode) + stateSinkObject.Save(4, &fd.offset) +} + +func (fd *memFD) afterLoad() {} + +// +checklocksignore +func (fd *memFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.vfsfd) + stateSourceObject.Load(1, &fd.FileDescriptionDefaultImpl) + stateSourceObject.Load(2, &fd.LockFD) + stateSourceObject.Load(3, &fd.inode) + stateSourceObject.Load(4, &fd.offset) +} + +func (d *mapsData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.mapsData" +} + +func (d *mapsData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + } +} + +func (d *mapsData) beforeSave() {} + +// +checklocksignore +func (d *mapsData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.task) +} + +func (d *mapsData) afterLoad() {} + +// +checklocksignore +func (d *mapsData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.task) +} + +func (d *smapsData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.smapsData" +} + +func (d *smapsData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + } +} + +func (d *smapsData) beforeSave() {} + +// +checklocksignore +func (d *smapsData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.task) +} + +func (d *smapsData) afterLoad() {} + +// +checklocksignore +func (d *smapsData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.task) +} + +func (s *taskStatData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.taskStatData" +} + +func (s *taskStatData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + "tgstats", + "pidns", + } +} + +func (s *taskStatData) beforeSave() {} + +// +checklocksignore +func (s *taskStatData) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.DynamicBytesFile) + stateSinkObject.Save(1, &s.task) + stateSinkObject.Save(2, &s.tgstats) + stateSinkObject.Save(3, &s.pidns) +} + +func (s *taskStatData) afterLoad() {} + +// +checklocksignore +func (s *taskStatData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.DynamicBytesFile) + stateSourceObject.Load(1, &s.task) + stateSourceObject.Load(2, &s.tgstats) + stateSourceObject.Load(3, &s.pidns) +} + +func (s *statmData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.statmData" +} + +func (s *statmData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + } +} + +func (s *statmData) beforeSave() {} + +// +checklocksignore +func (s *statmData) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.DynamicBytesFile) + stateSinkObject.Save(1, &s.task) +} + +func (s *statmData) afterLoad() {} + +// +checklocksignore +func (s *statmData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.DynamicBytesFile) + stateSourceObject.Load(1, &s.task) +} + +func (s *statusInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.statusInode" +} + +func (s *statusInode) StateFields() []string { + return []string{ + "InodeAttrs", + "InodeNoStatFS", + "InodeNoopRefCount", + "InodeNotDirectory", + "InodeNotSymlink", + "task", + "pidns", + "locks", + } +} + +func (s *statusInode) beforeSave() {} + +// +checklocksignore +func (s *statusInode) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.InodeAttrs) + stateSinkObject.Save(1, &s.InodeNoStatFS) + stateSinkObject.Save(2, &s.InodeNoopRefCount) + stateSinkObject.Save(3, &s.InodeNotDirectory) + stateSinkObject.Save(4, &s.InodeNotSymlink) + stateSinkObject.Save(5, &s.task) + stateSinkObject.Save(6, &s.pidns) + stateSinkObject.Save(7, &s.locks) +} + +func (s *statusInode) afterLoad() {} + +// +checklocksignore +func (s *statusInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.InodeAttrs) + stateSourceObject.Load(1, &s.InodeNoStatFS) + stateSourceObject.Load(2, &s.InodeNoopRefCount) + stateSourceObject.Load(3, &s.InodeNotDirectory) + stateSourceObject.Load(4, &s.InodeNotSymlink) + stateSourceObject.Load(5, &s.task) + stateSourceObject.Load(6, &s.pidns) + stateSourceObject.Load(7, &s.locks) +} + +func (s *statusFD) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.statusFD" +} + +func (s *statusFD) StateFields() []string { + return []string{ + "statusFDLowerBase", + "DynamicBytesFileDescriptionImpl", + "LockFD", + "vfsfd", + "inode", + "task", + "pidns", + "userns", + } +} + +func (s *statusFD) beforeSave() {} + +// +checklocksignore +func (s *statusFD) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.statusFDLowerBase) + stateSinkObject.Save(1, &s.DynamicBytesFileDescriptionImpl) + stateSinkObject.Save(2, &s.LockFD) + stateSinkObject.Save(3, &s.vfsfd) + stateSinkObject.Save(4, &s.inode) + stateSinkObject.Save(5, &s.task) + stateSinkObject.Save(6, &s.pidns) + stateSinkObject.Save(7, &s.userns) +} + +func (s *statusFD) afterLoad() {} + +// +checklocksignore +func (s *statusFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.statusFDLowerBase) + stateSourceObject.Load(1, &s.DynamicBytesFileDescriptionImpl) + stateSourceObject.Load(2, &s.LockFD) + stateSourceObject.Load(3, &s.vfsfd) + stateSourceObject.Load(4, &s.inode) + stateSourceObject.Load(5, &s.task) + stateSourceObject.Load(6, &s.pidns) + stateSourceObject.Load(7, &s.userns) +} + +func (s *statusFDLowerBase) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.statusFDLowerBase" +} + +func (s *statusFDLowerBase) StateFields() []string { + return []string{ + "FileDescriptionDefaultImpl", + } +} + +func (s *statusFDLowerBase) beforeSave() {} + +// +checklocksignore +func (s *statusFDLowerBase) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.FileDescriptionDefaultImpl) +} + +func (s *statusFDLowerBase) afterLoad() {} + +// +checklocksignore +func (s *statusFDLowerBase) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.FileDescriptionDefaultImpl) +} + +func (i *ioData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.ioData" +} + +func (i *ioData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "ioUsage", + } +} + +func (i *ioData) beforeSave() {} + +// +checklocksignore +func (i *ioData) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.DynamicBytesFile) + stateSinkObject.Save(1, &i.ioUsage) +} + +func (i *ioData) afterLoad() {} + +// +checklocksignore +func (i *ioData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.DynamicBytesFile) + stateSourceObject.Load(1, &i.ioUsage) +} + +func (o *oomScoreAdj) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.oomScoreAdj" +} + +func (o *oomScoreAdj) StateFields() []string { + return []string{ + "DynamicBytesFile", + "task", + } +} + +func (o *oomScoreAdj) beforeSave() {} + +// +checklocksignore +func (o *oomScoreAdj) StateSave(stateSinkObject state.Sink) { + o.beforeSave() + stateSinkObject.Save(0, &o.DynamicBytesFile) + stateSinkObject.Save(1, &o.task) +} + +func (o *oomScoreAdj) afterLoad() {} + +// +checklocksignore +func (o *oomScoreAdj) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &o.DynamicBytesFile) + stateSourceObject.Load(1, &o.task) +} + +func (s *exeSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.exeSymlink" +} + +func (s *exeSymlink) StateFields() []string { + return []string{ + "implStatFS", + "InodeAttrs", + "InodeNoopRefCount", + "InodeSymlink", + "fs", + "task", + } +} + +func (s *exeSymlink) beforeSave() {} + +// +checklocksignore +func (s *exeSymlink) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.implStatFS) + stateSinkObject.Save(1, &s.InodeAttrs) + stateSinkObject.Save(2, &s.InodeNoopRefCount) + stateSinkObject.Save(3, &s.InodeSymlink) + stateSinkObject.Save(4, &s.fs) + stateSinkObject.Save(5, &s.task) +} + +func (s *exeSymlink) afterLoad() {} + +// +checklocksignore +func (s *exeSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.implStatFS) + stateSourceObject.Load(1, &s.InodeAttrs) + stateSourceObject.Load(2, &s.InodeNoopRefCount) + stateSourceObject.Load(3, &s.InodeSymlink) + stateSourceObject.Load(4, &s.fs) + stateSourceObject.Load(5, &s.task) +} + +func (s *cwdSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.cwdSymlink" +} + +func (s *cwdSymlink) StateFields() []string { + return []string{ + "implStatFS", + "InodeAttrs", + "InodeNoopRefCount", + "InodeSymlink", + "fs", + "task", + } +} + +func (s *cwdSymlink) beforeSave() {} + +// +checklocksignore +func (s *cwdSymlink) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.implStatFS) + stateSinkObject.Save(1, &s.InodeAttrs) + stateSinkObject.Save(2, &s.InodeNoopRefCount) + stateSinkObject.Save(3, &s.InodeSymlink) + stateSinkObject.Save(4, &s.fs) + stateSinkObject.Save(5, &s.task) +} + +func (s *cwdSymlink) afterLoad() {} + +// +checklocksignore +func (s *cwdSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.implStatFS) + stateSourceObject.Load(1, &s.InodeAttrs) + stateSourceObject.Load(2, &s.InodeNoopRefCount) + stateSourceObject.Load(3, &s.InodeSymlink) + stateSourceObject.Load(4, &s.fs) + stateSourceObject.Load(5, &s.task) +} + +func (i *mountInfoData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.mountInfoData" +} + +func (i *mountInfoData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "fs", + "task", + } +} + +func (i *mountInfoData) beforeSave() {} + +// +checklocksignore +func (i *mountInfoData) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.DynamicBytesFile) + stateSinkObject.Save(1, &i.fs) + stateSinkObject.Save(2, &i.task) +} + +func (i *mountInfoData) afterLoad() {} + +// +checklocksignore +func (i *mountInfoData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.DynamicBytesFile) + stateSourceObject.Load(1, &i.fs) + stateSourceObject.Load(2, &i.task) +} + +func (i *mountsData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.mountsData" +} + +func (i *mountsData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "fs", + "task", + } +} + +func (i *mountsData) beforeSave() {} + +// +checklocksignore +func (i *mountsData) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.DynamicBytesFile) + stateSinkObject.Save(1, &i.fs) + stateSinkObject.Save(2, &i.task) +} + +func (i *mountsData) afterLoad() {} + +// +checklocksignore +func (i *mountsData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.DynamicBytesFile) + stateSourceObject.Load(1, &i.fs) + stateSourceObject.Load(2, &i.task) +} + +func (s *namespaceSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.namespaceSymlink" +} + +func (s *namespaceSymlink) StateFields() []string { + return []string{ + "StaticSymlink", + "task", + } +} + +func (s *namespaceSymlink) beforeSave() {} + +// +checklocksignore +func (s *namespaceSymlink) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.StaticSymlink) + stateSinkObject.Save(1, &s.task) +} + +func (s *namespaceSymlink) afterLoad() {} + +// +checklocksignore +func (s *namespaceSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.StaticSymlink) + stateSourceObject.Load(1, &s.task) +} + +func (i *namespaceInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.namespaceInode" +} + +func (i *namespaceInode) StateFields() []string { + return []string{ + "implStatFS", + "InodeAttrs", + "InodeNoopRefCount", + "InodeNotDirectory", + "InodeNotSymlink", + "locks", + } +} + +func (i *namespaceInode) beforeSave() {} + +// +checklocksignore +func (i *namespaceInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.implStatFS) + stateSinkObject.Save(1, &i.InodeAttrs) + stateSinkObject.Save(2, &i.InodeNoopRefCount) + stateSinkObject.Save(3, &i.InodeNotDirectory) + stateSinkObject.Save(4, &i.InodeNotSymlink) + stateSinkObject.Save(5, &i.locks) +} + +func (i *namespaceInode) afterLoad() {} + +// +checklocksignore +func (i *namespaceInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.implStatFS) + stateSourceObject.Load(1, &i.InodeAttrs) + stateSourceObject.Load(2, &i.InodeNoopRefCount) + stateSourceObject.Load(3, &i.InodeNotDirectory) + stateSourceObject.Load(4, &i.InodeNotSymlink) + stateSourceObject.Load(5, &i.locks) +} + +func (fd *namespaceFD) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.namespaceFD" +} + +func (fd *namespaceFD) StateFields() []string { + return []string{ + "FileDescriptionDefaultImpl", + "LockFD", + "vfsfd", + "inode", + } +} + +func (fd *namespaceFD) beforeSave() {} + +// +checklocksignore +func (fd *namespaceFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.FileDescriptionDefaultImpl) + stateSinkObject.Save(1, &fd.LockFD) + stateSinkObject.Save(2, &fd.vfsfd) + stateSinkObject.Save(3, &fd.inode) +} + +func (fd *namespaceFD) afterLoad() {} + +// +checklocksignore +func (fd *namespaceFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.FileDescriptionDefaultImpl) + stateSourceObject.Load(1, &fd.LockFD) + stateSourceObject.Load(2, &fd.vfsfd) + stateSourceObject.Load(3, &fd.inode) +} + +func (d *taskCgroupData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.taskCgroupData" +} + +func (d *taskCgroupData) StateFields() []string { + return []string{ + "dynamicBytesFileSetAttr", + "task", + } +} + +func (d *taskCgroupData) beforeSave() {} + +// +checklocksignore +func (d *taskCgroupData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.dynamicBytesFileSetAttr) + stateSinkObject.Save(1, &d.task) +} + +func (d *taskCgroupData) afterLoad() {} + +// +checklocksignore +func (d *taskCgroupData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.dynamicBytesFileSetAttr) + stateSourceObject.Load(1, &d.task) +} + +func (r *taskInodeRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.taskInodeRefs" +} + +func (r *taskInodeRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *taskInodeRefs) beforeSave() {} + +// +checklocksignore +func (r *taskInodeRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *taskInodeRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (n *ifinet6) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.ifinet6" +} + +func (n *ifinet6) StateFields() []string { + return []string{ + "DynamicBytesFile", + "stack", + } +} + +func (n *ifinet6) beforeSave() {} + +// +checklocksignore +func (n *ifinet6) StateSave(stateSinkObject state.Sink) { + n.beforeSave() + stateSinkObject.Save(0, &n.DynamicBytesFile) + stateSinkObject.Save(1, &n.stack) +} + +func (n *ifinet6) afterLoad() {} + +// +checklocksignore +func (n *ifinet6) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &n.DynamicBytesFile) + stateSourceObject.Load(1, &n.stack) +} + +func (n *netDevData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.netDevData" +} + +func (n *netDevData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "stack", + } +} + +func (n *netDevData) beforeSave() {} + +// +checklocksignore +func (n *netDevData) StateSave(stateSinkObject state.Sink) { + n.beforeSave() + stateSinkObject.Save(0, &n.DynamicBytesFile) + stateSinkObject.Save(1, &n.stack) +} + +func (n *netDevData) afterLoad() {} + +// +checklocksignore +func (n *netDevData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &n.DynamicBytesFile) + stateSourceObject.Load(1, &n.stack) +} + +func (n *netUnixData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.netUnixData" +} + +func (n *netUnixData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "kernel", + } +} + +func (n *netUnixData) beforeSave() {} + +// +checklocksignore +func (n *netUnixData) StateSave(stateSinkObject state.Sink) { + n.beforeSave() + stateSinkObject.Save(0, &n.DynamicBytesFile) + stateSinkObject.Save(1, &n.kernel) +} + +func (n *netUnixData) afterLoad() {} + +// +checklocksignore +func (n *netUnixData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &n.DynamicBytesFile) + stateSourceObject.Load(1, &n.kernel) +} + +func (d *netTCPData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.netTCPData" +} + +func (d *netTCPData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "kernel", + } +} + +func (d *netTCPData) beforeSave() {} + +// +checklocksignore +func (d *netTCPData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.kernel) +} + +func (d *netTCPData) afterLoad() {} + +// +checklocksignore +func (d *netTCPData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.kernel) +} + +func (d *netTCP6Data) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.netTCP6Data" +} + +func (d *netTCP6Data) StateFields() []string { + return []string{ + "DynamicBytesFile", + "kernel", + } +} + +func (d *netTCP6Data) beforeSave() {} + +// +checklocksignore +func (d *netTCP6Data) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.kernel) +} + +func (d *netTCP6Data) afterLoad() {} + +// +checklocksignore +func (d *netTCP6Data) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.kernel) +} + +func (d *netUDPData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.netUDPData" +} + +func (d *netUDPData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "kernel", + } +} + +func (d *netUDPData) beforeSave() {} + +// +checklocksignore +func (d *netUDPData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.kernel) +} + +func (d *netUDPData) afterLoad() {} + +// +checklocksignore +func (d *netUDPData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.kernel) +} + +func (d *netSnmpData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.netSnmpData" +} + +func (d *netSnmpData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "stack", + } +} + +func (d *netSnmpData) beforeSave() {} + +// +checklocksignore +func (d *netSnmpData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.stack) +} + +func (d *netSnmpData) afterLoad() {} + +// +checklocksignore +func (d *netSnmpData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.stack) +} + +func (s *snmpLine) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.snmpLine" +} + +func (s *snmpLine) StateFields() []string { + return []string{ + "prefix", + "header", + } +} + +func (s *snmpLine) beforeSave() {} + +// +checklocksignore +func (s *snmpLine) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.prefix) + stateSinkObject.Save(1, &s.header) +} + +func (s *snmpLine) afterLoad() {} + +// +checklocksignore +func (s *snmpLine) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.prefix) + stateSourceObject.Load(1, &s.header) +} + +func (d *netRouteData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.netRouteData" +} + +func (d *netRouteData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "stack", + } +} + +func (d *netRouteData) beforeSave() {} + +// +checklocksignore +func (d *netRouteData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.stack) +} + +func (d *netRouteData) afterLoad() {} + +// +checklocksignore +func (d *netRouteData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.stack) +} + +func (d *netStatData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.netStatData" +} + +func (d *netStatData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "stack", + } +} + +func (d *netStatData) beforeSave() {} + +// +checklocksignore +func (d *netStatData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.stack) +} + +func (d *netStatData) afterLoad() {} + +// +checklocksignore +func (d *netStatData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.stack) +} + +func (i *tasksInode) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.tasksInode" +} + +func (i *tasksInode) StateFields() []string { + return []string{ + "implStatFS", + "InodeAlwaysValid", + "InodeAttrs", + "InodeDirectoryNoNewChildren", + "InodeNotSymlink", + "InodeTemporary", + "OrderedChildren", + "tasksInodeRefs", + "locks", + "fs", + "pidns", + "fakeCgroupControllers", + } +} + +func (i *tasksInode) beforeSave() {} + +// +checklocksignore +func (i *tasksInode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.implStatFS) + stateSinkObject.Save(1, &i.InodeAlwaysValid) + stateSinkObject.Save(2, &i.InodeAttrs) + stateSinkObject.Save(3, &i.InodeDirectoryNoNewChildren) + stateSinkObject.Save(4, &i.InodeNotSymlink) + stateSinkObject.Save(5, &i.InodeTemporary) + stateSinkObject.Save(6, &i.OrderedChildren) + stateSinkObject.Save(7, &i.tasksInodeRefs) + stateSinkObject.Save(8, &i.locks) + stateSinkObject.Save(9, &i.fs) + stateSinkObject.Save(10, &i.pidns) + stateSinkObject.Save(11, &i.fakeCgroupControllers) +} + +func (i *tasksInode) afterLoad() {} + +// +checklocksignore +func (i *tasksInode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.implStatFS) + stateSourceObject.Load(1, &i.InodeAlwaysValid) + stateSourceObject.Load(2, &i.InodeAttrs) + stateSourceObject.Load(3, &i.InodeDirectoryNoNewChildren) + stateSourceObject.Load(4, &i.InodeNotSymlink) + stateSourceObject.Load(5, &i.InodeTemporary) + stateSourceObject.Load(6, &i.OrderedChildren) + stateSourceObject.Load(7, &i.tasksInodeRefs) + stateSourceObject.Load(8, &i.locks) + stateSourceObject.Load(9, &i.fs) + stateSourceObject.Load(10, &i.pidns) + stateSourceObject.Load(11, &i.fakeCgroupControllers) +} + +func (s *staticFileSetStat) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.staticFileSetStat" +} + +func (s *staticFileSetStat) StateFields() []string { + return []string{ + "dynamicBytesFileSetAttr", + "StaticData", + } +} + +func (s *staticFileSetStat) beforeSave() {} + +// +checklocksignore +func (s *staticFileSetStat) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.dynamicBytesFileSetAttr) + stateSinkObject.Save(1, &s.StaticData) +} + +func (s *staticFileSetStat) afterLoad() {} + +// +checklocksignore +func (s *staticFileSetStat) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.dynamicBytesFileSetAttr) + stateSourceObject.Load(1, &s.StaticData) +} + +func (s *selfSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.selfSymlink" +} + +func (s *selfSymlink) StateFields() []string { + return []string{ + "implStatFS", + "InodeAttrs", + "InodeNoopRefCount", + "InodeSymlink", + "pidns", + } +} + +func (s *selfSymlink) beforeSave() {} + +// +checklocksignore +func (s *selfSymlink) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.implStatFS) + stateSinkObject.Save(1, &s.InodeAttrs) + stateSinkObject.Save(2, &s.InodeNoopRefCount) + stateSinkObject.Save(3, &s.InodeSymlink) + stateSinkObject.Save(4, &s.pidns) +} + +func (s *selfSymlink) afterLoad() {} + +// +checklocksignore +func (s *selfSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.implStatFS) + stateSourceObject.Load(1, &s.InodeAttrs) + stateSourceObject.Load(2, &s.InodeNoopRefCount) + stateSourceObject.Load(3, &s.InodeSymlink) + stateSourceObject.Load(4, &s.pidns) +} + +func (s *threadSelfSymlink) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.threadSelfSymlink" +} + +func (s *threadSelfSymlink) StateFields() []string { + return []string{ + "implStatFS", + "InodeAttrs", + "InodeNoopRefCount", + "InodeSymlink", + "pidns", + } +} + +func (s *threadSelfSymlink) beforeSave() {} + +// +checklocksignore +func (s *threadSelfSymlink) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.implStatFS) + stateSinkObject.Save(1, &s.InodeAttrs) + stateSinkObject.Save(2, &s.InodeNoopRefCount) + stateSinkObject.Save(3, &s.InodeSymlink) + stateSinkObject.Save(4, &s.pidns) +} + +func (s *threadSelfSymlink) afterLoad() {} + +// +checklocksignore +func (s *threadSelfSymlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.implStatFS) + stateSourceObject.Load(1, &s.InodeAttrs) + stateSourceObject.Load(2, &s.InodeNoopRefCount) + stateSourceObject.Load(3, &s.InodeSymlink) + stateSourceObject.Load(4, &s.pidns) +} + +func (d *dynamicBytesFileSetAttr) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.dynamicBytesFileSetAttr" +} + +func (d *dynamicBytesFileSetAttr) StateFields() []string { + return []string{ + "DynamicBytesFile", + } +} + +func (d *dynamicBytesFileSetAttr) beforeSave() {} + +// +checklocksignore +func (d *dynamicBytesFileSetAttr) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) +} + +func (d *dynamicBytesFileSetAttr) afterLoad() {} + +// +checklocksignore +func (d *dynamicBytesFileSetAttr) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) +} + +func (c *cpuStats) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.cpuStats" +} + +func (c *cpuStats) StateFields() []string { + return []string{ + "user", + "nice", + "system", + "idle", + "ioWait", + "irq", + "softirq", + "steal", + "guest", + "guestNice", + } +} + +func (c *cpuStats) beforeSave() {} + +// +checklocksignore +func (c *cpuStats) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.user) + stateSinkObject.Save(1, &c.nice) + stateSinkObject.Save(2, &c.system) + stateSinkObject.Save(3, &c.idle) + stateSinkObject.Save(4, &c.ioWait) + stateSinkObject.Save(5, &c.irq) + stateSinkObject.Save(6, &c.softirq) + stateSinkObject.Save(7, &c.steal) + stateSinkObject.Save(8, &c.guest) + stateSinkObject.Save(9, &c.guestNice) +} + +func (c *cpuStats) afterLoad() {} + +// +checklocksignore +func (c *cpuStats) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.user) + stateSourceObject.Load(1, &c.nice) + stateSourceObject.Load(2, &c.system) + stateSourceObject.Load(3, &c.idle) + stateSourceObject.Load(4, &c.ioWait) + stateSourceObject.Load(5, &c.irq) + stateSourceObject.Load(6, &c.softirq) + stateSourceObject.Load(7, &c.steal) + stateSourceObject.Load(8, &c.guest) + stateSourceObject.Load(9, &c.guestNice) +} + +func (s *statData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.statData" +} + +func (s *statData) StateFields() []string { + return []string{ + "dynamicBytesFileSetAttr", + } +} + +func (s *statData) beforeSave() {} + +// +checklocksignore +func (s *statData) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.dynamicBytesFileSetAttr) +} + +func (s *statData) afterLoad() {} + +// +checklocksignore +func (s *statData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.dynamicBytesFileSetAttr) +} + +func (l *loadavgData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.loadavgData" +} + +func (l *loadavgData) StateFields() []string { + return []string{ + "dynamicBytesFileSetAttr", + } +} + +func (l *loadavgData) beforeSave() {} + +// +checklocksignore +func (l *loadavgData) StateSave(stateSinkObject state.Sink) { + l.beforeSave() + stateSinkObject.Save(0, &l.dynamicBytesFileSetAttr) +} + +func (l *loadavgData) afterLoad() {} + +// +checklocksignore +func (l *loadavgData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &l.dynamicBytesFileSetAttr) +} + +func (m *meminfoData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.meminfoData" +} + +func (m *meminfoData) StateFields() []string { + return []string{ + "dynamicBytesFileSetAttr", + } +} + +func (m *meminfoData) beforeSave() {} + +// +checklocksignore +func (m *meminfoData) StateSave(stateSinkObject state.Sink) { + m.beforeSave() + stateSinkObject.Save(0, &m.dynamicBytesFileSetAttr) +} + +func (m *meminfoData) afterLoad() {} + +// +checklocksignore +func (m *meminfoData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &m.dynamicBytesFileSetAttr) +} + +func (u *uptimeData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.uptimeData" +} + +func (u *uptimeData) StateFields() []string { + return []string{ + "dynamicBytesFileSetAttr", + } +} + +func (u *uptimeData) beforeSave() {} + +// +checklocksignore +func (u *uptimeData) StateSave(stateSinkObject state.Sink) { + u.beforeSave() + stateSinkObject.Save(0, &u.dynamicBytesFileSetAttr) +} + +func (u *uptimeData) afterLoad() {} + +// +checklocksignore +func (u *uptimeData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &u.dynamicBytesFileSetAttr) +} + +func (v *versionData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.versionData" +} + +func (v *versionData) StateFields() []string { + return []string{ + "dynamicBytesFileSetAttr", + } +} + +func (v *versionData) beforeSave() {} + +// +checklocksignore +func (v *versionData) StateSave(stateSinkObject state.Sink) { + v.beforeSave() + stateSinkObject.Save(0, &v.dynamicBytesFileSetAttr) +} + +func (v *versionData) afterLoad() {} + +// +checklocksignore +func (v *versionData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &v.dynamicBytesFileSetAttr) +} + +func (d *filesystemsData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.filesystemsData" +} + +func (d *filesystemsData) StateFields() []string { + return []string{ + "DynamicBytesFile", + } +} + +func (d *filesystemsData) beforeSave() {} + +// +checklocksignore +func (d *filesystemsData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) +} + +func (d *filesystemsData) afterLoad() {} + +// +checklocksignore +func (d *filesystemsData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) +} + +func (c *cgroupsData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.cgroupsData" +} + +func (c *cgroupsData) StateFields() []string { + return []string{ + "dynamicBytesFileSetAttr", + } +} + +func (c *cgroupsData) beforeSave() {} + +// +checklocksignore +func (c *cgroupsData) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.dynamicBytesFileSetAttr) +} + +func (c *cgroupsData) afterLoad() {} + +// +checklocksignore +func (c *cgroupsData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.dynamicBytesFileSetAttr) +} + +func (c *cmdLineData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.cmdLineData" +} + +func (c *cmdLineData) StateFields() []string { + return []string{ + "dynamicBytesFileSetAttr", + } +} + +func (c *cmdLineData) beforeSave() {} + +// +checklocksignore +func (c *cmdLineData) StateSave(stateSinkObject state.Sink) { + c.beforeSave() + stateSinkObject.Save(0, &c.dynamicBytesFileSetAttr) +} + +func (c *cmdLineData) afterLoad() {} + +// +checklocksignore +func (c *cmdLineData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &c.dynamicBytesFileSetAttr) +} + +func (r *tasksInodeRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.tasksInodeRefs" +} + +func (r *tasksInodeRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *tasksInodeRefs) beforeSave() {} + +// +checklocksignore +func (r *tasksInodeRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *tasksInodeRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (t *tcpMemDir) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.tcpMemDir" +} + +func (t *tcpMemDir) StateFields() []string { + return nil +} + +func (d *mmapMinAddrData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.mmapMinAddrData" +} + +func (d *mmapMinAddrData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "k", + } +} + +func (d *mmapMinAddrData) beforeSave() {} + +// +checklocksignore +func (d *mmapMinAddrData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.k) +} + +func (d *mmapMinAddrData) afterLoad() {} + +// +checklocksignore +func (d *mmapMinAddrData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.k) +} + +func (h *hostnameData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.hostnameData" +} + +func (h *hostnameData) StateFields() []string { + return []string{ + "DynamicBytesFile", + } +} + +func (h *hostnameData) beforeSave() {} + +// +checklocksignore +func (h *hostnameData) StateSave(stateSinkObject state.Sink) { + h.beforeSave() + stateSinkObject.Save(0, &h.DynamicBytesFile) +} + +func (h *hostnameData) afterLoad() {} + +// +checklocksignore +func (h *hostnameData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &h.DynamicBytesFile) +} + +func (d *tcpSackData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.tcpSackData" +} + +func (d *tcpSackData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "stack", + "enabled", + } +} + +func (d *tcpSackData) beforeSave() {} + +// +checklocksignore +func (d *tcpSackData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.stack) + stateSinkObject.Save(2, &d.enabled) +} + +func (d *tcpSackData) afterLoad() {} + +// +checklocksignore +func (d *tcpSackData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.LoadWait(1, &d.stack) + stateSourceObject.Load(2, &d.enabled) +} + +func (d *tcpRecoveryData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.tcpRecoveryData" +} + +func (d *tcpRecoveryData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "stack", + } +} + +func (d *tcpRecoveryData) beforeSave() {} + +// +checklocksignore +func (d *tcpRecoveryData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.stack) +} + +func (d *tcpRecoveryData) afterLoad() {} + +// +checklocksignore +func (d *tcpRecoveryData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.LoadWait(1, &d.stack) +} + +func (d *tcpMemData) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.tcpMemData" +} + +func (d *tcpMemData) StateFields() []string { + return []string{ + "DynamicBytesFile", + "dir", + "stack", + } +} + +func (d *tcpMemData) beforeSave() {} + +// +checklocksignore +func (d *tcpMemData) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.DynamicBytesFile) + stateSinkObject.Save(1, &d.dir) + stateSinkObject.Save(2, &d.stack) +} + +func (d *tcpMemData) afterLoad() {} + +// +checklocksignore +func (d *tcpMemData) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.DynamicBytesFile) + stateSourceObject.Load(1, &d.dir) + stateSourceObject.LoadWait(2, &d.stack) +} + +func (ipf *ipForwarding) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.ipForwarding" +} + +func (ipf *ipForwarding) StateFields() []string { + return []string{ + "DynamicBytesFile", + "stack", + "enabled", + } +} + +func (ipf *ipForwarding) beforeSave() {} + +// +checklocksignore +func (ipf *ipForwarding) StateSave(stateSinkObject state.Sink) { + ipf.beforeSave() + stateSinkObject.Save(0, &ipf.DynamicBytesFile) + stateSinkObject.Save(1, &ipf.stack) + stateSinkObject.Save(2, &ipf.enabled) +} + +func (ipf *ipForwarding) afterLoad() {} + +// +checklocksignore +func (ipf *ipForwarding) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &ipf.DynamicBytesFile) + stateSourceObject.LoadWait(1, &ipf.stack) + stateSourceObject.Load(2, &ipf.enabled) +} + +func (pr *portRange) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.portRange" +} + +func (pr *portRange) StateFields() []string { + return []string{ + "DynamicBytesFile", + "stack", + "start", + "end", + } +} + +func (pr *portRange) beforeSave() {} + +// +checklocksignore +func (pr *portRange) StateSave(stateSinkObject state.Sink) { + pr.beforeSave() + stateSinkObject.Save(0, &pr.DynamicBytesFile) + stateSinkObject.Save(1, &pr.stack) + stateSinkObject.Save(2, &pr.start) + stateSinkObject.Save(3, &pr.end) +} + +func (pr *portRange) afterLoad() {} + +// +checklocksignore +func (pr *portRange) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &pr.DynamicBytesFile) + stateSourceObject.LoadWait(1, &pr.stack) + stateSourceObject.Load(2, &pr.start) + stateSourceObject.Load(3, &pr.end) +} + +func (s *yamaPtraceScope) StateTypeName() string { + return "pkg/sentry/fsimpl/proc.yamaPtraceScope" +} + +func (s *yamaPtraceScope) StateFields() []string { + return []string{ + "DynamicBytesFile", + "level", + } +} + +func (s *yamaPtraceScope) beforeSave() {} + +// +checklocksignore +func (s *yamaPtraceScope) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.DynamicBytesFile) + stateSinkObject.Save(1, &s.level) +} + +func (s *yamaPtraceScope) afterLoad() {} + +// +checklocksignore +func (s *yamaPtraceScope) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.DynamicBytesFile) + stateSourceObject.Load(1, &s.level) +} + +func init() { + state.Register((*fdDirInodeRefs)(nil)) + state.Register((*fdInfoDirInodeRefs)(nil)) + state.Register((*FilesystemType)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*staticFile)(nil)) + state.Register((*InternalData)(nil)) + state.Register((*implStatFS)(nil)) + state.Register((*subtasksInode)(nil)) + state.Register((*subtasksFD)(nil)) + state.Register((*subtasksInodeRefs)(nil)) + state.Register((*taskInode)(nil)) + state.Register((*taskOwnedInode)(nil)) + state.Register((*fdDir)(nil)) + state.Register((*fdDirInode)(nil)) + state.Register((*fdSymlink)(nil)) + state.Register((*fdInfoDirInode)(nil)) + state.Register((*fdInfoData)(nil)) + state.Register((*auxvData)(nil)) + state.Register((*cmdlineData)(nil)) + state.Register((*commInode)(nil)) + state.Register((*commData)(nil)) + state.Register((*idMapData)(nil)) + state.Register((*memInode)(nil)) + state.Register((*memFD)(nil)) + state.Register((*mapsData)(nil)) + state.Register((*smapsData)(nil)) + state.Register((*taskStatData)(nil)) + state.Register((*statmData)(nil)) + state.Register((*statusInode)(nil)) + state.Register((*statusFD)(nil)) + state.Register((*statusFDLowerBase)(nil)) + state.Register((*ioData)(nil)) + state.Register((*oomScoreAdj)(nil)) + state.Register((*exeSymlink)(nil)) + state.Register((*cwdSymlink)(nil)) + state.Register((*mountInfoData)(nil)) + state.Register((*mountsData)(nil)) + state.Register((*namespaceSymlink)(nil)) + state.Register((*namespaceInode)(nil)) + state.Register((*namespaceFD)(nil)) + state.Register((*taskCgroupData)(nil)) + state.Register((*taskInodeRefs)(nil)) + state.Register((*ifinet6)(nil)) + state.Register((*netDevData)(nil)) + state.Register((*netUnixData)(nil)) + state.Register((*netTCPData)(nil)) + state.Register((*netTCP6Data)(nil)) + state.Register((*netUDPData)(nil)) + state.Register((*netSnmpData)(nil)) + state.Register((*snmpLine)(nil)) + state.Register((*netRouteData)(nil)) + state.Register((*netStatData)(nil)) + state.Register((*tasksInode)(nil)) + state.Register((*staticFileSetStat)(nil)) + state.Register((*selfSymlink)(nil)) + state.Register((*threadSelfSymlink)(nil)) + state.Register((*dynamicBytesFileSetAttr)(nil)) + state.Register((*cpuStats)(nil)) + state.Register((*statData)(nil)) + state.Register((*loadavgData)(nil)) + state.Register((*meminfoData)(nil)) + state.Register((*uptimeData)(nil)) + state.Register((*versionData)(nil)) + state.Register((*filesystemsData)(nil)) + state.Register((*cgroupsData)(nil)) + state.Register((*cmdLineData)(nil)) + state.Register((*tasksInodeRefs)(nil)) + state.Register((*tcpMemDir)(nil)) + state.Register((*mmapMinAddrData)(nil)) + state.Register((*hostnameData)(nil)) + state.Register((*tcpSackData)(nil)) + state.Register((*tcpRecoveryData)(nil)) + state.Register((*tcpMemData)(nil)) + state.Register((*ipForwarding)(nil)) + state.Register((*portRange)(nil)) + state.Register((*yamaPtraceScope)(nil)) +} diff --git a/pkg/sentry/fsimpl/proc/subtasks_inode_refs.go b/pkg/sentry/fsimpl/proc/subtasks_inode_refs.go new file mode 100644 index 000000000..bd4998cbc --- /dev/null +++ b/pkg/sentry/fsimpl/proc/subtasks_inode_refs.go @@ -0,0 +1,140 @@ +package proc + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const subtasksInodeenableLogging = false + +// obj 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 subtasksInodeobj *subtasksInode + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type subtasksInodeRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *subtasksInodeRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *subtasksInodeRefs) RefType() string { + return fmt.Sprintf("%T", subtasksInodeobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *subtasksInodeRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *subtasksInodeRefs) LogRefs() bool { + return subtasksInodeenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *subtasksInodeRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *subtasksInodeRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if subtasksInodeenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *subtasksInodeRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if subtasksInodeenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *subtasksInodeRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if subtasksInodeenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *subtasksInodeRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/proc/task_inode_refs.go b/pkg/sentry/fsimpl/proc/task_inode_refs.go new file mode 100644 index 000000000..82c63213a --- /dev/null +++ b/pkg/sentry/fsimpl/proc/task_inode_refs.go @@ -0,0 +1,140 @@ +package proc + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const taskInodeenableLogging = false + +// obj 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 taskInodeobj *taskInode + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type taskInodeRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *taskInodeRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *taskInodeRefs) RefType() string { + return fmt.Sprintf("%T", taskInodeobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *taskInodeRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *taskInodeRefs) LogRefs() bool { + return taskInodeenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *taskInodeRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *taskInodeRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if taskInodeenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *taskInodeRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if taskInodeenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *taskInodeRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if taskInodeenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *taskInodeRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/proc/tasks_inode_refs.go b/pkg/sentry/fsimpl/proc/tasks_inode_refs.go new file mode 100644 index 000000000..73adc5278 --- /dev/null +++ b/pkg/sentry/fsimpl/proc/tasks_inode_refs.go @@ -0,0 +1,140 @@ +package proc + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const tasksInodeenableLogging = false + +// obj 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 tasksInodeobj *tasksInode + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type tasksInodeRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *tasksInodeRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *tasksInodeRefs) RefType() string { + return fmt.Sprintf("%T", tasksInodeobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *tasksInodeRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *tasksInodeRefs) LogRefs() bool { + return tasksInodeenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *tasksInodeRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *tasksInodeRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if tasksInodeenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *tasksInodeRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if tasksInodeenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *tasksInodeRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if tasksInodeenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *tasksInodeRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/proc/tasks_sys_test.go b/pkg/sentry/fsimpl/proc/tasks_sys_test.go deleted file mode 100644 index 19b012f7d..000000000 --- a/pkg/sentry/fsimpl/proc/tasks_sys_test.go +++ /dev/null @@ -1,149 +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 proc - -import ( - "bytes" - "reflect" - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/inet" - "gvisor.dev/gvisor/pkg/usermem" -) - -func newIPv6TestStack() *inet.TestStack { - s := inet.NewTestStack() - s.SupportsIPv6Flag = true - return s -} - -func TestIfinet6NoAddresses(t *testing.T) { - n := &ifinet6{stack: newIPv6TestStack()} - var buf bytes.Buffer - n.Generate(contexttest.Context(t), &buf) - if buf.Len() > 0 { - t.Errorf("n.Generate() generated = %v, want = %v", buf.Bytes(), []byte{}) - } -} - -func TestIfinet6(t *testing.T) { - s := newIPv6TestStack() - s.InterfacesMap[1] = inet.Interface{Name: "eth0"} - s.InterfaceAddrsMap[1] = []inet.InterfaceAddr{ - { - Family: linux.AF_INET6, - PrefixLen: 128, - Addr: []byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"), - }, - } - s.InterfacesMap[2] = inet.Interface{Name: "eth1"} - s.InterfaceAddrsMap[2] = []inet.InterfaceAddr{ - { - Family: linux.AF_INET6, - PrefixLen: 128, - Addr: []byte("\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"), - }, - } - want := map[string]struct{}{ - "000102030405060708090a0b0c0d0e0f 01 80 00 00 eth0\n": {}, - "101112131415161718191a1b1c1d1e1f 02 80 00 00 eth1\n": {}, - } - - n := &ifinet6{stack: s} - contents := n.contents() - if len(contents) != len(want) { - t.Errorf("Got len(n.contents()) = %d, want = %d", len(contents), len(want)) - } - got := map[string]struct{}{} - for _, l := range contents { - got[l] = struct{}{} - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Got n.contents() = %v, want = %v", got, want) - } -} - -// TestIPForwarding tests the implementation of -// /proc/sys/net/ipv4/ip_forwarding -func TestConfigureIPForwarding(t *testing.T) { - ctx := context.Background() - s := inet.NewTestStack() - - var cases = []struct { - comment string - initial bool - str string - final bool - }{ - { - comment: `Forwarding is disabled; write 1 and enable forwarding`, - initial: false, - str: "1", - final: true, - }, - { - comment: `Forwarding is disabled; write 0 and disable forwarding`, - initial: false, - str: "0", - final: false, - }, - { - comment: `Forwarding is enabled; write 1 and enable forwarding`, - initial: true, - str: "1", - final: true, - }, - { - comment: `Forwarding is enabled; write 0 and disable forwarding`, - initial: true, - str: "0", - final: false, - }, - { - comment: `Forwarding is disabled; write 2404 and enable forwarding`, - initial: false, - str: "2404", - final: true, - }, - { - comment: `Forwarding is enabled; write 2404 and enable forwarding`, - initial: true, - str: "2404", - final: true, - }, - } - for _, c := range cases { - t.Run(c.comment, func(t *testing.T) { - s.IPForwarding = c.initial - - file := &ipForwarding{stack: s, enabled: c.initial} - - // Write the values. - src := usermem.BytesIOSequence([]byte(c.str)) - if n, err := file.Write(ctx, src, 0); n != int64(len(c.str)) || err != nil { - t.Errorf("file.Write(ctx, nil, %q, 0) = (%d, %v); want (%d, nil)", c.str, n, err, len(c.str)) - } - - // Read the values from the stack and check them. - if got, want := s.IPForwarding, c.final; got != want { - t.Errorf("s.IPForwarding incorrect; got: %v, want: %v", got, want) - } - }) - } -} diff --git a/pkg/sentry/fsimpl/proc/tasks_test.go b/pkg/sentry/fsimpl/proc/tasks_test.go deleted file mode 100644 index 14f806c3c..000000000 --- a/pkg/sentry/fsimpl/proc/tasks_test.go +++ /dev/null @@ -1,511 +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 proc - -import ( - "fmt" - "math" - "path" - "strconv" - "testing" - - "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/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" -) - -var ( - // Next offset 256 by convention. Adds 1 for the next offset. - selfLink = vfs.Dirent{Type: linux.DT_LNK, NextOff: 256 + 0 + 1} - threadSelfLink = vfs.Dirent{Type: linux.DT_LNK, NextOff: 256 + 1 + 1} - - // /proc/[pid] next offset starts at 256+2 (files above), then adds the - // PID, and adds 1 for the next offset. - proc1 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 1 + 1} - proc2 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 2 + 1} - proc3 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 3 + 1} -) - -var ( - tasksStaticFiles = map[string]testutil.DirentType{ - "cmdline": linux.DT_REG, - "cpuinfo": linux.DT_REG, - "filesystems": linux.DT_REG, - "loadavg": linux.DT_REG, - "meminfo": linux.DT_REG, - "mounts": linux.DT_LNK, - "net": linux.DT_LNK, - "self": linux.DT_LNK, - "stat": linux.DT_REG, - "sys": linux.DT_DIR, - "thread-self": linux.DT_LNK, - "uptime": linux.DT_REG, - "version": linux.DT_REG, - } - tasksStaticFilesNextOffs = map[string]int64{ - "self": selfLink.NextOff, - "thread-self": threadSelfLink.NextOff, - } - taskStaticFiles = map[string]testutil.DirentType{ - "auxv": linux.DT_REG, - "cgroup": linux.DT_REG, - "cwd": linux.DT_LNK, - "cmdline": linux.DT_REG, - "comm": linux.DT_REG, - "environ": linux.DT_REG, - "exe": linux.DT_LNK, - "fd": linux.DT_DIR, - "fdinfo": linux.DT_DIR, - "gid_map": linux.DT_REG, - "io": linux.DT_REG, - "maps": linux.DT_REG, - "mem": linux.DT_REG, - "mountinfo": linux.DT_REG, - "mounts": linux.DT_REG, - "net": linux.DT_DIR, - "ns": linux.DT_DIR, - "oom_score": linux.DT_REG, - "oom_score_adj": linux.DT_REG, - "smaps": linux.DT_REG, - "stat": linux.DT_REG, - "statm": linux.DT_REG, - "status": linux.DT_REG, - "task": linux.DT_DIR, - "uid_map": linux.DT_REG, - } -) - -func setup(t *testing.T) *testutil.System { - k, err := testutil.Boot() - if err != nil { - t.Fatalf("Error creating kernel: %v", err) - } - - ctx := k.SupervisorContext() - creds := auth.CredentialsFromContext(ctx) - - k.VFS().MustRegisterFilesystemType(Name, &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - - mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", tmpfs.Name, &vfs.MountOptions{}) - if err != nil { - t.Fatalf("NewMountNamespace(): %v", err) - } - root := mntns.Root() - root.IncRef() - defer root.DecRef(ctx) - pop := &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse("/proc"), - } - if err := k.VFS().MkdirAt(ctx, creds, pop, &vfs.MkdirOptions{Mode: 0777}); err != nil { - t.Fatalf("MkDir(/proc): %v", err) - } - - pop = &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse("/proc"), - } - mntOpts := &vfs.MountOptions{ - GetFilesystemOptions: vfs.GetFilesystemOptions{ - InternalData: &InternalData{ - Cgroups: map[string]string{ - "cpuset": "/foo/cpuset", - "memory": "/foo/memory", - }, - }, - }, - } - if _, err := k.VFS().MountAt(ctx, creds, "", pop, Name, mntOpts); err != nil { - t.Fatalf("MountAt(/proc): %v", err) - } - return testutil.NewSystem(ctx, t, k.VFS(), mntns) -} - -func TestTasksEmpty(t *testing.T) { - s := setup(t) - defer s.Destroy() - - collector := s.ListDirents(s.PathOpAtRoot("/proc")) - s.AssertAllDirentTypes(collector, tasksStaticFiles) - s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs) -} - -func TestTasks(t *testing.T) { - s := setup(t) - defer s.Destroy() - - expectedDirents := make(map[string]testutil.DirentType) - for n, d := range tasksStaticFiles { - expectedDirents[n] = d - } - - k := kernel.KernelFromContext(s.Ctx) - var tasks []*kernel.Task - for i := 0; i < 5; i++ { - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) - task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root) - if err != nil { - t.Fatalf("CreateTask(): %v", err) - } - tasks = append(tasks, task) - expectedDirents[fmt.Sprintf("%d", i+1)] = linux.DT_DIR - } - - collector := s.ListDirents(s.PathOpAtRoot("/proc")) - s.AssertAllDirentTypes(collector, expectedDirents) - s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs) - - lastPid := 0 - dirents := collector.OrderedDirents() - doneSkippingNonTaskDirs := false - for _, d := range dirents { - pid, err := strconv.Atoi(d.Name) - if err != nil { - if !doneSkippingNonTaskDirs { - // We haven't gotten to the task dirs yet. - continue - } - t.Fatalf("Invalid process directory %q", d.Name) - } - doneSkippingNonTaskDirs = true - if lastPid > pid { - t.Errorf("pids not in order: %v", dirents) - } - found := false - for _, t := range tasks { - if k.TaskSet().Root.IDOfTask(t) == kernel.ThreadID(pid) { - found = true - } - } - if !found { - t.Errorf("Additional task ID %d listed: %v", pid, tasks) - } - // Next offset starts at 256+2 ('self' and 'thread-self'), then adds the - // PID, and adds 1 for the next offset. - if want := int64(256 + 2 + pid + 1); d.NextOff != want { - t.Errorf("Wrong dirent offset want: %d got: %d: %+v", want, d.NextOff, d) - } - } - if !doneSkippingNonTaskDirs { - t.Fatalf("Never found any process directories.") - } - - // Test lookup. - for _, path := range []string{"/proc/1", "/proc/2"} { - fd, err := s.VFS.OpenAt( - s.Ctx, - s.Creds, - s.PathOpAtRoot(path), - &vfs.OpenOptions{}, - ) - if err != nil { - t.Fatalf("vfsfs.OpenAt(%q) failed: %v", path, err) - } - defer fd.DecRef(s.Ctx) - buf := make([]byte, 1) - bufIOSeq := usermem.BytesIOSequence(buf) - if _, err := fd.Read(s.Ctx, bufIOSeq, vfs.ReadOptions{}); !linuxerr.Equals(linuxerr.EISDIR, err) { - t.Errorf("wrong error reading directory: %v", err) - } - } - - if _, err := s.VFS.OpenAt( - s.Ctx, - s.Creds, - s.PathOpAtRoot("/proc/9999"), - &vfs.OpenOptions{}, - ); !linuxerr.Equals(linuxerr.ENOENT, err) { - t.Fatalf("wrong error from vfsfs.OpenAt(/proc/9999): %v", err) - } -} - -func TestTasksOffset(t *testing.T) { - s := setup(t) - defer s.Destroy() - - k := kernel.KernelFromContext(s.Ctx) - for i := 0; i < 3; i++ { - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) - if _, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root); err != nil { - t.Fatalf("CreateTask(): %v", err) - } - } - - for _, tc := range []struct { - name string - offset int64 - wants map[string]vfs.Dirent - }{ - { - name: "small offset", - offset: 100, - wants: map[string]vfs.Dirent{ - "self": selfLink, - "thread-self": threadSelfLink, - "1": proc1, - "2": proc2, - "3": proc3, - }, - }, - { - name: "offset at start", - offset: 256, - wants: map[string]vfs.Dirent{ - "self": selfLink, - "thread-self": threadSelfLink, - "1": proc1, - "2": proc2, - "3": proc3, - }, - }, - { - name: "skip /proc/self", - offset: 257, - wants: map[string]vfs.Dirent{ - "thread-self": threadSelfLink, - "1": proc1, - "2": proc2, - "3": proc3, - }, - }, - { - name: "skip symlinks", - offset: 258, - wants: map[string]vfs.Dirent{ - "1": proc1, - "2": proc2, - "3": proc3, - }, - }, - { - name: "skip first process", - offset: 260, - wants: map[string]vfs.Dirent{ - "2": proc2, - "3": proc3, - }, - }, - { - name: "last process", - offset: 261, - wants: map[string]vfs.Dirent{ - "3": proc3, - }, - }, - { - name: "after last", - offset: 262, - wants: nil, - }, - { - name: "TaskLimit+1", - offset: kernel.TasksLimit + 1, - wants: nil, - }, - { - name: "max", - offset: math.MaxInt64, - wants: nil, - }, - } { - t.Run(tc.name, func(t *testing.T) { - s := s.WithSubtest(t) - fd, err := s.VFS.OpenAt( - s.Ctx, - s.Creds, - s.PathOpAtRoot("/proc"), - &vfs.OpenOptions{}, - ) - if err != nil { - t.Fatalf("vfsfs.OpenAt(/) failed: %v", err) - } - defer fd.DecRef(s.Ctx) - if _, err := fd.Seek(s.Ctx, tc.offset, linux.SEEK_SET); err != nil { - t.Fatalf("Seek(%d, SEEK_SET): %v", tc.offset, err) - } - - var collector testutil.DirentCollector - if err := fd.IterDirents(s.Ctx, &collector); err != nil { - t.Fatalf("IterDirent(): %v", err) - } - - expectedTypes := make(map[string]testutil.DirentType) - expectedOffsets := make(map[string]int64) - for name, want := range tc.wants { - expectedTypes[name] = want.Type - if want.NextOff != 0 { - expectedOffsets[name] = want.NextOff - } - } - - collector.SkipDotsChecks(true) // We seek()ed past the dots. - s.AssertAllDirentTypes(&collector, expectedTypes) - s.AssertDirentOffsets(&collector, expectedOffsets) - }) - } -} - -func TestTask(t *testing.T) { - s := setup(t) - defer s.Destroy() - - k := kernel.KernelFromContext(s.Ctx) - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) - _, err := testutil.CreateTask(s.Ctx, "name", tc, s.MntNs, s.Root, s.Root) - if err != nil { - t.Fatalf("CreateTask(): %v", err) - } - - collector := s.ListDirents(s.PathOpAtRoot("/proc/1")) - s.AssertAllDirentTypes(collector, taskStaticFiles) -} - -func TestProcSelf(t *testing.T) { - s := setup(t) - defer s.Destroy() - - k := kernel.KernelFromContext(s.Ctx) - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) - task, err := testutil.CreateTask(s.Ctx, "name", tc, s.MntNs, s.Root, s.Root) - if err != nil { - t.Fatalf("CreateTask(): %v", err) - } - - collector := s.WithTemporaryContext(task.AsyncContext()).ListDirents(&vfs.PathOperation{ - Root: s.Root, - Start: s.Root, - Path: fspath.Parse("/proc/self/"), - FollowFinalSymlink: true, - }) - s.AssertAllDirentTypes(collector, taskStaticFiles) -} - -func iterateDir(ctx context.Context, t *testing.T, s *testutil.System, fd *vfs.FileDescription) { - t.Logf("Iterating: %s", fd.MappedName(ctx)) - - var collector testutil.DirentCollector - if err := fd.IterDirents(ctx, &collector); err != nil { - t.Fatalf("IterDirents(): %v", err) - } - if err := collector.Contains(".", linux.DT_DIR); err != nil { - t.Error(err.Error()) - } - if err := collector.Contains("..", linux.DT_DIR); err != nil { - t.Error(err.Error()) - } - - for _, d := range collector.Dirents() { - if d.Name == "." || d.Name == ".." { - continue - } - absPath := path.Join(fd.MappedName(ctx), d.Name) - if d.Type == linux.DT_LNK { - link, err := s.VFS.ReadlinkAt( - ctx, - auth.CredentialsFromContext(ctx), - &vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(absPath)}, - ) - if err != nil { - t.Errorf("vfsfs.ReadlinkAt(%v) failed: %v", absPath, err) - } else { - t.Logf("Skipping symlink: %s => %s", absPath, link) - } - continue - } - - t.Logf("Opening: %s", absPath) - child, err := s.VFS.OpenAt( - ctx, - auth.CredentialsFromContext(ctx), - &vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(absPath)}, - &vfs.OpenOptions{}, - ) - if err != nil { - t.Errorf("vfsfs.OpenAt(%v) failed: %v", absPath, err) - continue - } - defer child.DecRef(ctx) - stat, err := child.Stat(ctx, vfs.StatOptions{}) - if err != nil { - t.Errorf("Stat(%v) failed: %v", absPath, err) - } - if got := linux.FileMode(stat.Mode).DirentType(); got != d.Type { - t.Errorf("wrong file mode, stat: %v, dirent: %v", got, d.Type) - } - if d.Type == linux.DT_DIR { - // Found another dir, let's do it again! - iterateDir(ctx, t, s, child) - } - } -} - -// TestTree iterates all directories and stats every file. -func TestTree(t *testing.T) { - s := setup(t) - defer s.Destroy() - - k := kernel.KernelFromContext(s.Ctx) - - pop := &vfs.PathOperation{ - Root: s.Root, - Start: s.Root, - Path: fspath.Parse("test-file"), - } - opts := &vfs.OpenOptions{ - Flags: linux.O_RDONLY | linux.O_CREAT, - Mode: 0777, - } - file, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, opts) - if err != nil { - t.Fatalf("failed to create test file: %v", err) - } - defer file.DecRef(s.Ctx) - - var tasks []*kernel.Task - for i := 0; i < 5; i++ { - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) - task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root) - if err != nil { - t.Fatalf("CreateTask(): %v", err) - } - // Add file to populate /proc/[pid]/fd and fdinfo directories. - task.FDTable().NewFDVFS2(task.AsyncContext(), 0, file, kernel.FDFlags{}) - tasks = append(tasks, task) - } - - ctx := tasks[0].AsyncContext() - fd, err := s.VFS.OpenAt( - ctx, - auth.CredentialsFromContext(s.Ctx), - &vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse("/proc")}, - &vfs.OpenOptions{}, - ) - if err != nil { - t.Fatalf("vfsfs.OpenAt(/proc) failed: %v", err) - } - iterateDir(ctx, t, s, fd) - fd.DecRef(ctx) -} diff --git a/pkg/sentry/fsimpl/signalfd/BUILD b/pkg/sentry/fsimpl/signalfd/BUILD deleted file mode 100644 index 403c6f254..000000000 --- a/pkg/sentry/fsimpl/signalfd/BUILD +++ /dev/null @@ -1,19 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "signalfd", - srcs = ["signalfd.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/sentry/kernel", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/usermem", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/fsimpl/signalfd/signalfd_state_autogen.go b/pkg/sentry/fsimpl/signalfd/signalfd_state_autogen.go new file mode 100644 index 000000000..3bf27c6a6 --- /dev/null +++ b/pkg/sentry/fsimpl/signalfd/signalfd_state_autogen.go @@ -0,0 +1,51 @@ +// automatically generated by stateify. + +package signalfd + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (sfd *SignalFileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/signalfd.SignalFileDescription" +} + +func (sfd *SignalFileDescription) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "DentryMetadataFileDescriptionImpl", + "NoLockFD", + "target", + "mask", + } +} + +func (sfd *SignalFileDescription) beforeSave() {} + +// +checklocksignore +func (sfd *SignalFileDescription) StateSave(stateSinkObject state.Sink) { + sfd.beforeSave() + stateSinkObject.Save(0, &sfd.vfsfd) + stateSinkObject.Save(1, &sfd.FileDescriptionDefaultImpl) + stateSinkObject.Save(2, &sfd.DentryMetadataFileDescriptionImpl) + stateSinkObject.Save(3, &sfd.NoLockFD) + stateSinkObject.Save(4, &sfd.target) + stateSinkObject.Save(5, &sfd.mask) +} + +func (sfd *SignalFileDescription) afterLoad() {} + +// +checklocksignore +func (sfd *SignalFileDescription) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &sfd.vfsfd) + stateSourceObject.Load(1, &sfd.FileDescriptionDefaultImpl) + stateSourceObject.Load(2, &sfd.DentryMetadataFileDescriptionImpl) + stateSourceObject.Load(3, &sfd.NoLockFD) + stateSourceObject.Load(4, &sfd.target) + stateSourceObject.Load(5, &sfd.mask) +} + +func init() { + state.Register((*SignalFileDescription)(nil)) +} diff --git a/pkg/sentry/fsimpl/sockfs/BUILD b/pkg/sentry/fsimpl/sockfs/BUILD deleted file mode 100644 index 9defca936..000000000 --- a/pkg/sentry/fsimpl/sockfs/BUILD +++ /dev/null @@ -1,18 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -licenses(["notice"]) - -go_library( - name = "sockfs", - srcs = ["sockfs.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/sentry/fsimpl/kernfs", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - ], -) diff --git a/pkg/sentry/fsimpl/sockfs/sockfs_state_autogen.go b/pkg/sentry/fsimpl/sockfs/sockfs_state_autogen.go new file mode 100644 index 000000000..cf6eddef2 --- /dev/null +++ b/pkg/sentry/fsimpl/sockfs/sockfs_state_autogen.go @@ -0,0 +1,96 @@ +// automatically generated by stateify. + +package sockfs + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (fsType *filesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/sockfs.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/sockfs.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "Filesystem", + "devMinor", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Filesystem) + stateSinkObject.Save(1, &fs.devMinor) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.Filesystem) + stateSourceObject.Load(1, &fs.devMinor) +} + +func (i *inode) StateTypeName() string { + return "pkg/sentry/fsimpl/sockfs.inode" +} + +func (i *inode) StateFields() []string { + return []string{ + "InodeAttrs", + "InodeNoopRefCount", + "InodeNotDirectory", + "InodeNotSymlink", + } +} + +func (i *inode) beforeSave() {} + +// +checklocksignore +func (i *inode) 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) +} + +func (i *inode) afterLoad() {} + +// +checklocksignore +func (i *inode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.InodeAttrs) + stateSourceObject.Load(1, &i.InodeNoopRefCount) + stateSourceObject.Load(2, &i.InodeNotDirectory) + stateSourceObject.Load(3, &i.InodeNotSymlink) +} + +func init() { + state.Register((*filesystemType)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*inode)(nil)) +} diff --git a/pkg/sentry/fsimpl/sys/BUILD b/pkg/sentry/fsimpl/sys/BUILD deleted file mode 100644 index ab21f028e..000000000 --- a/pkg/sentry/fsimpl/sys/BUILD +++ /dev/null @@ -1,55 +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/refsvfs2: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/errors/linuxerr", - "//pkg/log", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/sentry/arch", - "//pkg/sentry/fsimpl/kernfs", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/memmap", - "//pkg/sentry/vfs", - "//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..17bc43d2e --- /dev/null +++ b/pkg/sentry/fsimpl/sys/dir_refs.go @@ -0,0 +1,140 @@ +package sys + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const direnableLogging = false + +// obj 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 dirobj *dir + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *dirRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *dirRefs) RefType() string { + return fmt.Sprintf("%T", dirobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *dirRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *dirRefs) LogRefs() bool { + return direnableLogging +} + +// 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) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *dirRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if direnableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if direnableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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()) { + v := atomic.AddInt64(&r.refCount, -1) + if direnableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *dirRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} 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..c5adf7db3 --- /dev/null +++ b/pkg/sentry/fsimpl/sys/sys_state_autogen.go @@ -0,0 +1,263 @@ +// 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() {} + +// +checklocksignore +func (r *dirRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *dirRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +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() {} + +// +checklocksignore +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() {} + +// +checklocksignore +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() {} + +// +checklocksignore +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() {} + +// +checklocksignore +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() {} + +// +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/sys.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "Filesystem", + "devMinor", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.Filesystem) + stateSinkObject.Save(1, &fs.devMinor) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +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() {} + +// +checklocksignore +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() {} + +// +checklocksignore +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() {} + +// +checklocksignore +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() {} + +// +checklocksignore +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() {} + +// +checklocksignore +func (i *implStatFS) StateSave(stateSinkObject state.Sink) { + i.beforeSave() +} + +func (i *implStatFS) afterLoad() {} + +// +checklocksignore +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 0c46a3a13..000000000 --- a/pkg/sentry/fsimpl/sys/sys_test.go +++ /dev/null @@ -1,103 +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, - }) -} - -func TestCgroupMountpointExists(t *testing.T) { - // Note: The mountpoint is only created if cgroups are available. This is - // the VFS2 implementation of sysfs and the test runs with VFS2 enabled, so - // we expect to see the mount point unconditionally. - s := newTestSystem(t) - defer s.Destroy() - pop := s.PathOpAtRoot("/fs") - s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{ - "cgroup": linux.DT_DIR, - }) - pop = s.PathOpAtRoot("/fs/cgroup") - s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{ /*empty*/ }) -} diff --git a/pkg/sentry/fsimpl/testutil/BUILD b/pkg/sentry/fsimpl/testutil/BUILD deleted file mode 100644 index b3f9d1010..000000000 --- a/pkg/sentry/fsimpl/testutil/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -licenses(["notice"]) - -go_library( - name = "testutil", - testonly = 1, - srcs = [ - "kernel.go", - "testutil.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/cpuid", - "//pkg/fspath", - "//pkg/hostarch", - "//pkg/memutil", - "//pkg/sentry/fsbridge", - "//pkg/sentry/fsimpl/tmpfs", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/sched", - "//pkg/sentry/limits", - "//pkg/sentry/loader", - "//pkg/sentry/mm", - "//pkg/sentry/pgalloc", - "//pkg/sentry/platform", - "//pkg/sentry/platform/kvm", - "//pkg/sentry/platform/ptrace", - "//pkg/sentry/time", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/usermem", - "@com_github_google_go_cmp//cmp:go_default_library", - ], -) diff --git a/pkg/sentry/fsimpl/testutil/kernel.go b/pkg/sentry/fsimpl/testutil/kernel.go deleted file mode 100644 index 473b41cff..000000000 --- a/pkg/sentry/fsimpl/testutil/kernel.go +++ /dev/null @@ -1,182 +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 testutil - -import ( - "flag" - "fmt" - "os" - "runtime" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/cpuid" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/memutil" - "gvisor.dev/gvisor/pkg/sentry/fsbridge" - "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/kernel/sched" - "gvisor.dev/gvisor/pkg/sentry/limits" - "gvisor.dev/gvisor/pkg/sentry/loader" - "gvisor.dev/gvisor/pkg/sentry/mm" - "gvisor.dev/gvisor/pkg/sentry/pgalloc" - "gvisor.dev/gvisor/pkg/sentry/platform" - "gvisor.dev/gvisor/pkg/sentry/time" - "gvisor.dev/gvisor/pkg/sentry/vfs" - - // Platforms are plugable. - _ "gvisor.dev/gvisor/pkg/sentry/platform/kvm" - _ "gvisor.dev/gvisor/pkg/sentry/platform/ptrace" -) - -var ( - platformFlag = flag.String("platform", "ptrace", "specify which platform to use") -) - -// Boot initializes a new bare bones kernel for test. -func Boot() (*kernel.Kernel, error) { - platformCtr, err := platform.Lookup(*platformFlag) - if err != nil { - return nil, fmt.Errorf("platform not found: %v", err) - } - deviceFile, err := platformCtr.OpenDevice() - if err != nil { - return nil, fmt.Errorf("creating platform: %v", err) - } - plat, err := platformCtr.New(deviceFile) - if err != nil { - return nil, fmt.Errorf("creating platform: %v", err) - } - - kernel.VFS2Enabled = true - k := &kernel.Kernel{ - Platform: plat, - } - - mf, err := createMemoryFile() - if err != nil { - return nil, err - } - k.SetMemoryFile(mf) - - // Pass k as the platform since it is savable, unlike the actual platform. - vdso, err := loader.PrepareVDSO(k) - if err != nil { - return nil, fmt.Errorf("creating vdso: %v", err) - } - - // Create timekeeper. - tk := kernel.NewTimekeeper(k, vdso.ParamPage.FileRange()) - tk.SetClocks(time.NewCalibratedClocks()) - - creds := auth.NewRootCredentials(auth.NewRootUserNamespace()) - - // Initiate the Kernel object, which is required by the Context passed - // to createVFS in order to mount (among other things) procfs. - if err = k.Init(kernel.InitKernelArgs{ - ApplicationCores: uint(runtime.GOMAXPROCS(-1)), - FeatureSet: cpuid.HostFeatureSet(), - Timekeeper: tk, - RootUserNamespace: creds.UserNamespace, - Vdso: vdso, - RootUTSNamespace: kernel.NewUTSNamespace("hostname", "domain", creds.UserNamespace), - RootIPCNamespace: kernel.NewIPCNamespace(creds.UserNamespace), - RootAbstractSocketNamespace: kernel.NewAbstractSocketNamespace(), - PIDNamespace: kernel.NewRootPIDNamespace(creds.UserNamespace), - }); err != nil { - return nil, fmt.Errorf("initializing kernel: %v", err) - } - - k.VFS().MustRegisterFilesystemType(tmpfs.Name, &tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - AllowUserList: true, - }) - - ls, err := limits.NewLinuxLimitSet() - if err != nil { - return nil, err - } - tg := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, ls) - k.TestOnlySetGlobalInit(tg) - - return k, nil -} - -// CreateTask creates a new bare bones task for tests. -func CreateTask(ctx context.Context, name string, tc *kernel.ThreadGroup, mntns *vfs.MountNamespace, root, cwd vfs.VirtualDentry) (*kernel.Task, error) { - k := kernel.KernelFromContext(ctx) - if k == nil { - return nil, fmt.Errorf("cannot find kernel from context") - } - - exe, err := newFakeExecutable(ctx, k.VFS(), auth.CredentialsFromContext(ctx), root) - if err != nil { - return nil, err - } - m := mm.NewMemoryManager(k, k, k.SleepForAddressSpaceActivation) - m.SetExecutable(ctx, fsbridge.NewVFSFile(exe)) - - config := &kernel.TaskConfig{ - Kernel: k, - ThreadGroup: tc, - TaskImage: &kernel.TaskImage{Name: name, MemoryManager: m}, - Credentials: auth.CredentialsFromContext(ctx), - NetworkNamespace: k.RootNetworkNamespace(), - AllowedCPUMask: sched.NewFullCPUSet(k.ApplicationCores()), - UTSNamespace: kernel.UTSNamespaceFromContext(ctx), - IPCNamespace: kernel.IPCNamespaceFromContext(ctx), - AbstractSocketNamespace: kernel.NewAbstractSocketNamespace(), - MountNamespaceVFS2: mntns, - FSContext: kernel.NewFSContextVFS2(root, cwd, 0022), - FDTable: k.NewFDTable(), - } - t, err := k.TaskSet().NewTask(ctx, config) - if err != nil { - config.ThreadGroup.Release(ctx) - return nil, err - } - return t, nil -} - -func newFakeExecutable(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, root vfs.VirtualDentry) (*vfs.FileDescription, error) { - const name = "executable" - pop := &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(name), - } - opts := &vfs.OpenOptions{ - Flags: linux.O_RDONLY | linux.O_CREAT, - Mode: 0777, - } - return vfsObj.OpenAt(ctx, creds, pop, opts) -} - -func createMemoryFile() (*pgalloc.MemoryFile, error) { - const memfileName = "test-memory" - memfd, err := memutil.CreateMemFD(memfileName, 0) - if err != nil { - return nil, fmt.Errorf("error creating memfd: %v", err) - } - memfile := os.NewFile(uintptr(memfd), memfileName) - mf, err := pgalloc.NewMemoryFile(memfile, pgalloc.MemoryFileOpts{}) - if err != nil { - _ = memfile.Close() - return nil, fmt.Errorf("error creating pgalloc.MemoryFile: %v", err) - } - return mf, nil -} diff --git a/pkg/sentry/fsimpl/testutil/testutil.go b/pkg/sentry/fsimpl/testutil/testutil.go deleted file mode 100644 index 59e6f9c92..000000000 --- a/pkg/sentry/fsimpl/testutil/testutil.go +++ /dev/null @@ -1,288 +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 testutil provides common test utilities for kernfs-based -// filesystems. -package testutil - -import ( - "fmt" - "io" - "strings" - "testing" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - - "gvisor.dev/gvisor/pkg/hostarch" -) - -// System represents the context for a single test. -// -// Test systems must be explicitly destroyed with System.Destroy. -type System struct { - t *testing.T - Ctx context.Context - Creds *auth.Credentials - VFS *vfs.VirtualFilesystem - Root vfs.VirtualDentry - MntNs *vfs.MountNamespace -} - -// NewSystem constructs a System. -// -// Precondition: Caller must hold a reference on mns, whose ownership -// is transferred to the new System. -func NewSystem(ctx context.Context, t *testing.T, v *vfs.VirtualFilesystem, mns *vfs.MountNamespace) *System { - root := mns.Root() - root.IncRef() - s := &System{ - t: t, - Ctx: ctx, - Creds: auth.CredentialsFromContext(ctx), - VFS: v, - MntNs: mns, - Root: root, - } - return s -} - -// WithSubtest creates a temporary test system with a new test harness, -// referencing all other resources from the original system. This is useful when -// a system is reused for multiple subtests, and the T needs to change for each -// case. Note that this is safe when test cases run in parallel, as all -// resources referenced by the system are immutable, or handle interior -// mutations in a thread-safe manner. -// -// The returned system must not outlive the original and should not be destroyed -// via System.Destroy. -func (s *System) WithSubtest(t *testing.T) *System { - return &System{ - t: t, - Ctx: s.Ctx, - Creds: s.Creds, - VFS: s.VFS, - MntNs: s.MntNs, - Root: s.Root, - } -} - -// WithTemporaryContext constructs a temporary test system with a new context -// ctx. The temporary system borrows all resources and references from the -// original system. The returned temporary system must not outlive the original -// system, and should not be destroyed via System.Destroy. -func (s *System) WithTemporaryContext(ctx context.Context) *System { - return &System{ - t: s.t, - Ctx: ctx, - Creds: s.Creds, - VFS: s.VFS, - MntNs: s.MntNs, - Root: s.Root, - } -} - -// Destroy release resources associated with a test system. -func (s *System) Destroy() { - s.Root.DecRef(s.Ctx) - s.MntNs.DecRef(s.Ctx) // Reference on MntNs passed to NewSystem. -} - -// ReadToEnd reads the contents of fd until EOF to a string. -func (s *System) ReadToEnd(fd *vfs.FileDescription) (string, error) { - buf := make([]byte, hostarch.PageSize) - bufIOSeq := usermem.BytesIOSequence(buf) - opts := vfs.ReadOptions{} - - var content strings.Builder - for { - n, err := fd.Read(s.Ctx, bufIOSeq, opts) - if n == 0 || err != nil { - if err == io.EOF { - err = nil - } - return content.String(), err - } - content.Write(buf[:n]) - } -} - -// PathOpAtRoot constructs a PathOperation with the given path from -// the root of the filesystem. -func (s *System) PathOpAtRoot(path string) *vfs.PathOperation { - return &vfs.PathOperation{ - Root: s.Root, - Start: s.Root, - Path: fspath.Parse(path), - } -} - -// GetDentryOrDie attempts to resolve a dentry referred to by the -// provided path operation. If unsuccessful, the test fails. -func (s *System) GetDentryOrDie(pop *vfs.PathOperation) vfs.VirtualDentry { - vd, err := s.VFS.GetDentryAt(s.Ctx, s.Creds, pop, &vfs.GetDentryOptions{}) - if err != nil { - s.t.Fatalf("GetDentryAt(pop:%+v) failed: %v", pop, err) - } - return vd -} - -// DirentType is an alias for values for linux_dirent64.d_type. -type DirentType = uint8 - -// ListDirents lists the Dirents for a directory at pop. -func (s *System) ListDirents(pop *vfs.PathOperation) *DirentCollector { - fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, &vfs.OpenOptions{Flags: linux.O_RDONLY}) - if err != nil { - s.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err) - } - defer fd.DecRef(s.Ctx) - - collector := &DirentCollector{} - if err := fd.IterDirents(s.Ctx, collector); err != nil { - s.t.Fatalf("IterDirent failed: %v", err) - } - return collector -} - -// AssertAllDirentTypes verifies that the set of dirents in collector contains -// exactly the specified set of expected entries. AssertAllDirentTypes respects -// collector.skipDots, and implicitly checks for "." and ".." accordingly. -func (s *System) AssertAllDirentTypes(collector *DirentCollector, expected map[string]DirentType) { - if expected == nil { - expected = make(map[string]DirentType) - } - // Also implicitly check for "." and "..", if enabled. - if !collector.skipDots { - expected["."] = linux.DT_DIR - expected[".."] = linux.DT_DIR - } - - dentryTypes := make(map[string]DirentType) - collector.mu.Lock() - for _, dirent := range collector.dirents { - dentryTypes[dirent.Name] = dirent.Type - } - collector.mu.Unlock() - if diff := cmp.Diff(expected, dentryTypes); diff != "" { - s.t.Fatalf("IterDirent had unexpected results:\n--- want\n+++ got\n%v", diff) - } -} - -// AssertDirentOffsets verifies that collector contains at least the entries -// specified in expected, with the given NextOff field. Entries specified in -// expected but missing from collector result in failure. Extra entries in -// collector are ignored. AssertDirentOffsets respects collector.skipDots, and -// implicitly checks for "." and ".." accordingly. -func (s *System) AssertDirentOffsets(collector *DirentCollector, expected map[string]int64) { - // Also implicitly check for "." and "..", if enabled. - if !collector.skipDots { - expected["."] = 1 - expected[".."] = 2 - } - - dentryNextOffs := make(map[string]int64) - collector.mu.Lock() - for _, dirent := range collector.dirents { - // Ignore extra entries in dentries that are not in expected. - if _, ok := expected[dirent.Name]; ok { - dentryNextOffs[dirent.Name] = dirent.NextOff - } - } - collector.mu.Unlock() - if diff := cmp.Diff(expected, dentryNextOffs); diff != "" { - s.t.Fatalf("IterDirent had unexpected results:\n--- want\n+++ got\n%v", diff) - } -} - -// DirentCollector provides an implementation for vfs.IterDirentsCallback for -// testing. It simply iterates to the end of a given directory FD and collects -// all dirents emitted by the callback. -type DirentCollector struct { - mu sync.Mutex - order []*vfs.Dirent - dirents map[string]*vfs.Dirent - // When the collector is used in various Assert* functions, should "." and - // ".." be implicitly checked? - skipDots bool -} - -// SkipDotsChecks enables or disables the implicit checks on "." and ".." when -// the collector is used in various Assert* functions. Note that "." and ".." -// are still collected if passed to d.Handle, so the caller should only disable -// the checks when they aren't expected. -func (d *DirentCollector) SkipDotsChecks(value bool) { - d.skipDots = value -} - -// Handle implements vfs.IterDirentsCallback.Handle. -func (d *DirentCollector) Handle(dirent vfs.Dirent) error { - d.mu.Lock() - if d.dirents == nil { - d.dirents = make(map[string]*vfs.Dirent) - } - d.order = append(d.order, &dirent) - d.dirents[dirent.Name] = &dirent - d.mu.Unlock() - return nil -} - -// Count returns the number of dirents currently in the collector. -func (d *DirentCollector) Count() int { - d.mu.Lock() - defer d.mu.Unlock() - return len(d.dirents) -} - -// Contains checks whether the collector has a dirent with the given name and -// type. -func (d *DirentCollector) Contains(name string, typ uint8) error { - d.mu.Lock() - defer d.mu.Unlock() - dirent, ok := d.dirents[name] - if !ok { - return fmt.Errorf("no dirent named %q found", name) - } - if dirent.Type != typ { - return fmt.Errorf("dirent named %q found, but was expecting type %s, got: %+v", name, linux.DirentType.Parse(uint64(typ)), dirent) - } - return nil -} - -// Dirents returns all dirents discovered by this collector. -func (d *DirentCollector) Dirents() map[string]*vfs.Dirent { - d.mu.Lock() - dirents := make(map[string]*vfs.Dirent) - for n, d := range d.dirents { - dirents[n] = d - } - d.mu.Unlock() - return dirents -} - -// OrderedDirents returns an ordered list of dirents as discovered by this -// collector. -func (d *DirentCollector) OrderedDirents() []*vfs.Dirent { - d.mu.Lock() - dirents := make([]*vfs.Dirent, len(d.order)) - copy(dirents, d.order) - d.mu.Unlock() - return dirents -} diff --git a/pkg/sentry/fsimpl/timerfd/BUILD b/pkg/sentry/fsimpl/timerfd/BUILD deleted file mode 100644 index 2b83d7d9a..000000000 --- a/pkg/sentry/fsimpl/timerfd/BUILD +++ /dev/null @@ -1,18 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -licenses(["notice"]) - -go_library( - name = "timerfd", - srcs = ["timerfd.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/sentry/kernel/time", - "//pkg/sentry/vfs", - "//pkg/usermem", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/fsimpl/timerfd/timerfd_state_autogen.go b/pkg/sentry/fsimpl/timerfd/timerfd_state_autogen.go new file mode 100644 index 000000000..12970f25c --- /dev/null +++ b/pkg/sentry/fsimpl/timerfd/timerfd_state_autogen.go @@ -0,0 +1,54 @@ +// automatically generated by stateify. + +package timerfd + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (tfd *TimerFileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/timerfd.TimerFileDescription" +} + +func (tfd *TimerFileDescription) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "DentryMetadataFileDescriptionImpl", + "NoLockFD", + "events", + "timer", + "val", + } +} + +func (tfd *TimerFileDescription) beforeSave() {} + +// +checklocksignore +func (tfd *TimerFileDescription) StateSave(stateSinkObject state.Sink) { + tfd.beforeSave() + stateSinkObject.Save(0, &tfd.vfsfd) + stateSinkObject.Save(1, &tfd.FileDescriptionDefaultImpl) + stateSinkObject.Save(2, &tfd.DentryMetadataFileDescriptionImpl) + stateSinkObject.Save(3, &tfd.NoLockFD) + stateSinkObject.Save(4, &tfd.events) + stateSinkObject.Save(5, &tfd.timer) + stateSinkObject.Save(6, &tfd.val) +} + +func (tfd *TimerFileDescription) afterLoad() {} + +// +checklocksignore +func (tfd *TimerFileDescription) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &tfd.vfsfd) + stateSourceObject.Load(1, &tfd.FileDescriptionDefaultImpl) + stateSourceObject.Load(2, &tfd.DentryMetadataFileDescriptionImpl) + stateSourceObject.Load(3, &tfd.NoLockFD) + stateSourceObject.Load(4, &tfd.events) + stateSourceObject.Load(5, &tfd.timer) + stateSourceObject.Load(6, &tfd.val) +} + +func init() { + state.Register((*TimerFileDescription)(nil)) +} diff --git a/pkg/sentry/fsimpl/tmpfs/BUILD b/pkg/sentry/fsimpl/tmpfs/BUILD deleted file mode 100644 index 94486bb63..000000000 --- a/pkg/sentry/fsimpl/tmpfs/BUILD +++ /dev/null @@ -1,129 +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 = "tmpfs", - prefix = "dentry", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*dentry", - "Linker": "*dentry", - }, -) - -go_template_instance( - name = "fstree", - out = "fstree.go", - package = "tmpfs", - prefix = "generic", - template = "//pkg/sentry/vfs/genericfstree:generic_fstree", - types = { - "Dentry": "dentry", - }, -) - -go_template_instance( - name = "inode_refs", - out = "inode_refs.go", - package = "tmpfs", - prefix = "inode", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "inode", - }, -) - -go_library( - name = "tmpfs", - srcs = [ - "dentry_list.go", - "device_file.go", - "directory.go", - "filesystem.go", - "fstree.go", - "inode_refs.go", - "named_pipe.go", - "regular_file.go", - "save_restore.go", - "socket_file.go", - "symlink.go", - "tmpfs.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/amutex", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/hostarch", - "//pkg/log", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/safemem", - "//pkg/sentry/arch", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/lock", - "//pkg/sentry/fsmetric", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/pipe", - "//pkg/sentry/kernel/time", - "//pkg/sentry/memmap", - "//pkg/sentry/pgalloc", - "//pkg/sentry/platform", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/uniqueid", - "//pkg/sentry/usage", - "//pkg/sentry/vfs", - "//pkg/sentry/vfs/memxattr", - "//pkg/sync", - "//pkg/usermem", - ], -) - -go_test( - name = "benchmark_test", - size = "small", - srcs = ["benchmark_test.go"], - deps = [ - ":tmpfs", - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/refs", - "//pkg/sentry/contexttest", - "//pkg/sentry/fs", - "//pkg/sentry/fs/tmpfs", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - ], -) - -go_test( - name = "tmpfs_test", - size = "small", - srcs = [ - "pipe_test.go", - "regular_file_test.go", - "stat_test.go", - "tmpfs_test.go", - ], - library = ":tmpfs", - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fspath", - "//pkg/sentry/contexttest", - "//pkg/sentry/fs/lock", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/vfs", - "//pkg/usermem", - ], -) diff --git a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go b/pkg/sentry/fsimpl/tmpfs/benchmark_test.go deleted file mode 100644 index 2c29343c1..000000000 --- a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go +++ /dev/null @@ -1,488 +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 benchmark_test - -import ( - "fmt" - "runtime" - "strings" - "testing" - - "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/refs" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" - _ "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// Differences from stat_benchmark: -// -// - Syscall interception, CopyInPath, copyOutStat, and overlayfs overheads are -// not included. -// -// - *MountStat benchmarks use a tmpfs root mount and a tmpfs submount at /tmp. -// Non-MountStat benchmarks use a tmpfs root mount and no submounts. -// stat_benchmark uses a varying root mount, a tmpfs submount at /tmp, and a -// subdirectory /tmp/<top_dir> (assuming TEST_TMPDIR == "/tmp"). Thus -// stat_benchmark at depth 1 does a comparable amount of work to *MountStat -// benchmarks at depth 2, and non-MountStat benchmarks at depth 3. -var depths = []int{1, 2, 3, 8, 64, 100} - -const ( - mountPointName = "tmp" - filename = "gvisor_test_temp_0_1557494568" -) - -// This is copied from syscalls/linux/sys_file.go, with the dependency on -// kernel.Task stripped out. -func fileOpOn(ctx context.Context, mntns *fs.MountNamespace, root, wd *fs.Dirent, dirFD int32, path string, resolve bool, fn func(root *fs.Dirent, d *fs.Dirent) error) error { - var ( - d *fs.Dirent // The file. - rel *fs.Dirent // The relative directory for search (if required.) - err error - ) - - // Extract the working directory (maybe). - if len(path) > 0 && path[0] == '/' { - // Absolute path; rel can be nil. - } else if dirFD == linux.AT_FDCWD { - // Need to reference the working directory. - rel = wd - } else { - // Need to extract the given FD. - return linuxerr.EBADF - } - - // Lookup the node. - remainingTraversals := uint(linux.MaxSymlinkTraversals) - if resolve { - d, err = mntns.FindInode(ctx, root, rel, path, &remainingTraversals) - } else { - d, err = mntns.FindLink(ctx, root, rel, path, &remainingTraversals) - } - if err != nil { - return err - } - - err = fn(root, d) - d.DecRef(ctx) - return err -} - -func BenchmarkVFS1TmpfsStat(b *testing.B) { - for _, depth := range depths { - b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { - ctx := contexttest.Context(b) - - // Create VFS. - tmpfsFS, ok := fs.FindFilesystem("tmpfs") - if !ok { - b.Fatalf("failed to find tmpfs filesystem type") - } - rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil) - if err != nil { - b.Fatalf("failed to create tmpfs root mount: %v", err) - } - mntns, err := fs.NewMountNamespace(ctx, rootInode) - if err != nil { - b.Fatalf("failed to create mount namespace: %v", err) - } - defer mntns.DecRef(ctx) - - var filePathBuilder strings.Builder - filePathBuilder.WriteByte('/') - - // Create nested directories with given depth. - root := mntns.Root() - defer root.DecRef(ctx) - d := root - d.IncRef() - defer d.DecRef(ctx) - for i := depth; i > 0; i-- { - name := fmt.Sprintf("%d", i) - if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil { - b.Fatalf("failed to create directory %q: %v", name, err) - } - next, err := d.Walk(ctx, root, name) - if err != nil { - b.Fatalf("failed to walk to directory %q: %v", name, err) - } - d.DecRef(ctx) - d = next - filePathBuilder.WriteString(name) - filePathBuilder.WriteByte('/') - } - - // Create the file that will be stat'd. - file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644)) - if err != nil { - b.Fatalf("failed to create file %q: %v", filename, err) - } - file.DecRef(ctx) - filePathBuilder.WriteString(filename) - filePath := filePathBuilder.String() - - dirPath := false - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - uattr, err := d.Inode.UnstableAttr(ctx) - if err != nil { - return err - } - // Sanity check. - if uattr.Perms.User.Execute { - b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode()) - } - return nil - }) - if err != nil { - b.Fatalf("stat(%q) failed: %v", filePath, err) - } - } - // Don't include deferred cleanup in benchmark time. - b.StopTimer() - }) - } -} - -func BenchmarkVFS2TmpfsStat(b *testing.B) { - for _, depth := range depths { - b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { - ctx := contexttest.Context(b) - creds := auth.CredentialsFromContext(ctx) - - // Create VFS. - vfsObj := vfs.VirtualFilesystem{} - if err := vfsObj.Init(ctx); err != nil { - b.Fatalf("VFS init: %v", err) - } - vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) - if err != nil { - b.Fatalf("failed to create tmpfs root mount: %v", err) - } - defer mntns.DecRef(ctx) - - var filePathBuilder strings.Builder - filePathBuilder.WriteByte('/') - - // Create nested directories with given depth. - root := mntns.Root() - root.IncRef() - defer root.DecRef(ctx) - vd := root - vd.IncRef() - for i := depth; i > 0; i-- { - name := fmt.Sprintf("%d", i) - pop := vfs.PathOperation{ - Root: root, - Start: vd, - Path: fspath.Parse(name), - } - if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{ - Mode: 0755, - }); err != nil { - b.Fatalf("failed to create directory %q: %v", name, err) - } - nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{}) - if err != nil { - b.Fatalf("failed to walk to directory %q: %v", name, err) - } - vd.DecRef(ctx) - vd = nextVD - filePathBuilder.WriteString(name) - filePathBuilder.WriteByte('/') - } - - // Create the file that will be stat'd. - fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: vd, - Path: fspath.Parse(filename), - FollowFinalSymlink: true, - }, &vfs.OpenOptions{ - Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL, - Mode: 0644, - }) - vd.DecRef(ctx) - vd = vfs.VirtualDentry{} - if err != nil { - b.Fatalf("failed to create file %q: %v", filename, err) - } - defer fd.DecRef(ctx) - filePathBuilder.WriteString(filename) - filePath := filePathBuilder.String() - - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(filePath), - FollowFinalSymlink: true, - }, &vfs.StatOptions{}) - if err != nil { - b.Fatalf("stat(%q) failed: %v", filePath, err) - } - // Sanity check. - if stat.Mode&^linux.S_IFMT != 0644 { - b.Fatalf("got wrong permissions (%0o)", stat.Mode) - } - } - // Don't include deferred cleanup in benchmark time. - b.StopTimer() - }) - } -} - -func BenchmarkVFS1TmpfsMountStat(b *testing.B) { - for _, depth := range depths { - b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { - ctx := contexttest.Context(b) - - // Create VFS. - tmpfsFS, ok := fs.FindFilesystem("tmpfs") - if !ok { - b.Fatalf("failed to find tmpfs filesystem type") - } - rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil) - if err != nil { - b.Fatalf("failed to create tmpfs root mount: %v", err) - } - mntns, err := fs.NewMountNamespace(ctx, rootInode) - if err != nil { - b.Fatalf("failed to create mount namespace: %v", err) - } - defer mntns.DecRef(ctx) - - var filePathBuilder strings.Builder - filePathBuilder.WriteByte('/') - - // Create and mount the submount. - root := mntns.Root() - defer root.DecRef(ctx) - if err := root.Inode.CreateDirectory(ctx, root, mountPointName, fs.FilePermsFromMode(0755)); err != nil { - b.Fatalf("failed to create mount point: %v", err) - } - mountPoint, err := root.Walk(ctx, root, mountPointName) - if err != nil { - b.Fatalf("failed to walk to mount point: %v", err) - } - defer mountPoint.DecRef(ctx) - submountInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil) - if err != nil { - b.Fatalf("failed to create tmpfs submount: %v", err) - } - if err := mntns.Mount(ctx, mountPoint, submountInode); err != nil { - b.Fatalf("failed to mount tmpfs submount: %v", err) - } - filePathBuilder.WriteString(mountPointName) - filePathBuilder.WriteByte('/') - - // Create nested directories with given depth. - d, err := root.Walk(ctx, root, mountPointName) - if err != nil { - b.Fatalf("failed to walk to mount root: %v", err) - } - defer d.DecRef(ctx) - for i := depth; i > 0; i-- { - name := fmt.Sprintf("%d", i) - if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil { - b.Fatalf("failed to create directory %q: %v", name, err) - } - next, err := d.Walk(ctx, root, name) - if err != nil { - b.Fatalf("failed to walk to directory %q: %v", name, err) - } - d.DecRef(ctx) - d = next - filePathBuilder.WriteString(name) - filePathBuilder.WriteByte('/') - } - - // Create the file that will be stat'd. - file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644)) - if err != nil { - b.Fatalf("failed to create file %q: %v", filename, err) - } - file.DecRef(ctx) - filePathBuilder.WriteString(filename) - filePath := filePathBuilder.String() - - dirPath := false - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - uattr, err := d.Inode.UnstableAttr(ctx) - if err != nil { - return err - } - // Sanity check. - if uattr.Perms.User.Execute { - b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode()) - } - return nil - }) - if err != nil { - b.Fatalf("stat(%q) failed: %v", filePath, err) - } - } - // Don't include deferred cleanup in benchmark time. - b.StopTimer() - }) - } -} - -func BenchmarkVFS2TmpfsMountStat(b *testing.B) { - for _, depth := range depths { - b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { - ctx := contexttest.Context(b) - creds := auth.CredentialsFromContext(ctx) - - // Create VFS. - vfsObj := vfs.VirtualFilesystem{} - if err := vfsObj.Init(ctx); err != nil { - b.Fatalf("VFS init: %v", err) - } - vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) - if err != nil { - b.Fatalf("failed to create tmpfs root mount: %v", err) - } - defer mntns.DecRef(ctx) - - var filePathBuilder strings.Builder - filePathBuilder.WriteByte('/') - - // Create the mount point. - root := mntns.Root() - root.IncRef() - defer root.DecRef(ctx) - pop := vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(mountPointName), - } - if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{ - Mode: 0755, - }); err != nil { - b.Fatalf("failed to create mount point: %v", err) - } - // Save the mount point for later use. - mountPoint, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{}) - if err != nil { - b.Fatalf("failed to walk to mount point: %v", err) - } - defer mountPoint.DecRef(ctx) - // Create and mount the submount. - if _, err := vfsObj.MountAt(ctx, creds, "", &pop, "tmpfs", &vfs.MountOptions{}); err != nil { - b.Fatalf("failed to mount tmpfs submount: %v", err) - } - filePathBuilder.WriteString(mountPointName) - filePathBuilder.WriteByte('/') - - // Create nested directories with given depth. - vd, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{}) - if err != nil { - b.Fatalf("failed to walk to mount root: %v", err) - } - for i := depth; i > 0; i-- { - name := fmt.Sprintf("%d", i) - pop := vfs.PathOperation{ - Root: root, - Start: vd, - Path: fspath.Parse(name), - } - if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{ - Mode: 0755, - }); err != nil { - b.Fatalf("failed to create directory %q: %v", name, err) - } - nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{}) - if err != nil { - b.Fatalf("failed to walk to directory %q: %v", name, err) - } - vd.DecRef(ctx) - vd = nextVD - filePathBuilder.WriteString(name) - filePathBuilder.WriteByte('/') - } - - // Create the file that will be stat'd. - fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: vd, - Path: fspath.Parse(filename), - FollowFinalSymlink: true, - }, &vfs.OpenOptions{ - Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL, - Mode: 0644, - }) - vd.DecRef(ctx) - if err != nil { - b.Fatalf("failed to create file %q: %v", filename, err) - } - fd.DecRef(ctx) - filePathBuilder.WriteString(filename) - filePath := filePathBuilder.String() - - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(filePath), - FollowFinalSymlink: true, - }, &vfs.StatOptions{}) - if err != nil { - b.Fatalf("stat(%q) failed: %v", filePath, err) - } - // Sanity check. - if stat.Mode&^linux.S_IFMT != 0644 { - b.Fatalf("got wrong permissions (%0o)", stat.Mode) - } - } - // Don't include deferred cleanup in benchmark time. - b.StopTimer() - }) - } -} - -func init() { - // Turn off reference leak checking for a fair comparison between vfs1 and - // vfs2. - refs.SetLeakMode(refs.NoLeakChecking) -} diff --git a/pkg/sentry/fsimpl/tmpfs/dentry_list.go b/pkg/sentry/fsimpl/tmpfs/dentry_list.go new file mode 100644 index 000000000..b95dd7101 --- /dev/null +++ b/pkg/sentry/fsimpl/tmpfs/dentry_list.go @@ -0,0 +1,221 @@ +package tmpfs + +// 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/tmpfs/fstree.go b/pkg/sentry/fsimpl/tmpfs/fstree.go new file mode 100644 index 000000000..d46351488 --- /dev/null +++ b/pkg/sentry/fsimpl/tmpfs/fstree.go @@ -0,0 +1,55 @@ +package tmpfs + +import ( + "gvisor.dev/gvisor/pkg/fspath" + "gvisor.dev/gvisor/pkg/sentry/vfs" +) + +// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is +// either d2's parent or an ancestor of d2's parent. +func genericIsAncestorDentry(d, d2 *dentry) bool { + for d2 != nil { + if d2.parent == d { + return true + } + if d2.parent == d2 { + return false + } + d2 = d2.parent + } + return false +} + +// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d. +func genericParentOrSelf(d *dentry) *dentry { + if d.parent != nil { + return d.parent + } + return d +} + +// PrependPath is a generic implementation of FilesystemImpl.PrependPath(). +func genericPrependPath(vfsroot vfs.VirtualDentry, mnt *vfs.Mount, d *dentry, b *fspath.Builder) error { + for { + if mnt == vfsroot.Mount() && &d.vfsd == vfsroot.Dentry() { + return vfs.PrependPathAtVFSRootError{} + } + if mnt != nil && &d.vfsd == mnt.Root() { + return nil + } + if d.parent == nil { + return vfs.PrependPathAtNonMountRootError{} + } + b.PrependComponent(d.name) + d = d.parent + } +} + +// DebugPathname returns a pathname to d relative to its filesystem root. +// DebugPathname does not correspond to any Linux function; it's used to +// generate dentry pathnames for debugging. +func genericDebugPathname(d *dentry) string { + var b fspath.Builder + _ = genericPrependPath(vfs.VirtualDentry{}, nil, d, &b) + return b.String() +} diff --git a/pkg/sentry/fsimpl/tmpfs/inode_refs.go b/pkg/sentry/fsimpl/tmpfs/inode_refs.go new file mode 100644 index 000000000..f0f032e0c --- /dev/null +++ b/pkg/sentry/fsimpl/tmpfs/inode_refs.go @@ -0,0 +1,140 @@ +package tmpfs + +import ( + "fmt" + "sync/atomic" + + "gvisor.dev/gvisor/pkg/refsvfs2" +) + +// enableLogging indicates whether reference-related events should be logged (with +// stack traces). This is false by default and should only be set to true for +// debugging purposes, as it can generate an extremely large amount of output +// and drastically degrade performance. +const inodeenableLogging = false + +// obj 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 inodeobj *inode + +// Refs implements refs.RefCounter. It keeps a reference count using atomic +// operations and calls the destructor when the count reaches zero. +// +// NOTE: Do not introduce additional fields to the Refs struct. It is used by +// many filesystem objects, and we want to keep it as small as possible (i.e., +// the same size as using an int64 directly) to avoid taking up extra cache +// space. In general, this template should not be extended at the cost of +// performance. If it does not offer enough flexibility for a particular object +// (example: b/187877947), we should implement the RefCounter/CheckedObject +// interfaces manually. +// +// +stateify savable +type inodeRefs 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 +} + +// InitRefs initializes r with one reference and, if enabled, activates leak +// checking. +func (r *inodeRefs) InitRefs() { + atomic.StoreInt64(&r.refCount, 1) + refsvfs2.Register(r) +} + +// RefType implements refsvfs2.CheckedObject.RefType. +func (r *inodeRefs) RefType() string { + return fmt.Sprintf("%T", inodeobj)[1:] +} + +// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. +func (r *inodeRefs) LeakMessage() string { + return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) +} + +// LogRefs implements refsvfs2.CheckedObject.LogRefs. +func (r *inodeRefs) LogRefs() bool { + return inodeenableLogging +} + +// ReadRefs returns the current number of references. The returned count is +// inherently racy and is unsafe to use without external synchronization. +func (r *inodeRefs) ReadRefs() int64 { + return atomic.LoadInt64(&r.refCount) +} + +// IncRef implements refs.RefCounter.IncRef. +// +//go:nosplit +func (r *inodeRefs) IncRef() { + v := atomic.AddInt64(&r.refCount, 1) + if inodeenableLogging { + refsvfs2.LogIncRef(r, v) + } + if v <= 1 { + panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) + } +} + +// TryIncRef implements refs.TryRefCounter.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 *inodeRefs) TryIncRef() bool { + const speculativeRef = 1 << 32 + if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { + + atomic.AddInt64(&r.refCount, -speculativeRef) + return false + } + + v := atomic.AddInt64(&r.refCount, -speculativeRef+1) + if inodeenableLogging { + refsvfs2.LogTryIncRef(r, v) + } + 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 *inodeRefs) DecRef(destroy func()) { + v := atomic.AddInt64(&r.refCount, -1) + if inodeenableLogging { + refsvfs2.LogDecRef(r, v) + } + switch { + case v < 0: + panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) + + case v == 0: + refsvfs2.Unregister(r) + + if destroy != nil { + destroy() + } + } +} + +func (r *inodeRefs) afterLoad() { + if r.ReadRefs() > 0 { + refsvfs2.Register(r) + } +} diff --git a/pkg/sentry/fsimpl/tmpfs/pipe_test.go b/pkg/sentry/fsimpl/tmpfs/pipe_test.go deleted file mode 100644 index 99afd9817..000000000 --- a/pkg/sentry/fsimpl/tmpfs/pipe_test.go +++ /dev/null @@ -1,239 +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 tmpfs - -import ( - "bytes" - "testing" - - "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/contexttest" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/usermem" -) - -const fileName = "mypipe" - -func TestSeparateFDs(t *testing.T) { - ctx, creds, vfsObj, root := setup(t) - defer root.DecRef(ctx) - - // Open the read side. This is done in a concurrently because opening - // One end the pipe blocks until the other end is opened. - pop := vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(fileName), - FollowFinalSymlink: true, - } - rfdchan := make(chan *vfs.FileDescription) - go func() { - openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY} - rfd, _ := vfsObj.OpenAt(ctx, creds, &pop, &openOpts) - rfdchan <- rfd - }() - - // Open the write side. - openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY} - wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts) - if err != nil { - t.Fatalf("failed to open pipe for writing %q: %v", fileName, err) - } - defer wfd.DecRef(ctx) - - rfd, ok := <-rfdchan - if !ok { - t.Fatalf("failed to open pipe for reading %q", fileName) - } - defer rfd.DecRef(ctx) - - const msg = "vamos azul" - checkEmpty(ctx, t, rfd) - checkWrite(ctx, t, wfd, msg) - checkRead(ctx, t, rfd, msg) -} - -func TestNonblockingRead(t *testing.T) { - ctx, creds, vfsObj, root := setup(t) - defer root.DecRef(ctx) - - // Open the read side as nonblocking. - pop := vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(fileName), - FollowFinalSymlink: true, - } - openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY | linux.O_NONBLOCK} - rfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts) - if err != nil { - t.Fatalf("failed to open pipe for reading %q: %v", fileName, err) - } - defer rfd.DecRef(ctx) - - // Open the write side. - openOpts = vfs.OpenOptions{Flags: linux.O_WRONLY} - wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts) - if err != nil { - t.Fatalf("failed to open pipe for writing %q: %v", fileName, err) - } - defer wfd.DecRef(ctx) - - const msg = "geh blau" - checkEmpty(ctx, t, rfd) - checkWrite(ctx, t, wfd, msg) - checkRead(ctx, t, rfd, msg) -} - -func TestNonblockingWriteError(t *testing.T) { - ctx, creds, vfsObj, root := setup(t) - defer root.DecRef(ctx) - - // Open the write side as nonblocking, which should return ENXIO. - pop := vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(fileName), - FollowFinalSymlink: true, - } - openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY | linux.O_NONBLOCK} - _, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts) - if !linuxerr.Equals(linuxerr.ENXIO, err) { - t.Fatalf("expected ENXIO, but got error: %v", err) - } -} - -func TestSingleFD(t *testing.T) { - ctx, creds, vfsObj, root := setup(t) - defer root.DecRef(ctx) - - // Open the pipe as readable and writable. - pop := vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(fileName), - FollowFinalSymlink: true, - } - openOpts := vfs.OpenOptions{Flags: linux.O_RDWR} - fd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts) - if err != nil { - t.Fatalf("failed to open pipe for writing %q: %v", fileName, err) - } - defer fd.DecRef(ctx) - - const msg = "forza blu" - checkEmpty(ctx, t, fd) - checkWrite(ctx, t, fd, msg) - checkRead(ctx, t, fd, msg) -} - -// setup creates a VFS with a pipe in the root directory at path fileName. The -// returned VirtualDentry must be DecRef()'d be the caller. It calls t.Fatal -// upon failure. -func setup(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesystem, vfs.VirtualDentry) { - ctx := contexttest.Context(t) - creds := auth.CredentialsFromContext(ctx) - - // Create VFS. - vfsObj := &vfs.VirtualFilesystem{} - if err := vfsObj.Init(ctx); err != nil { - t.Fatalf("VFS init: %v", err) - } - vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) - if err != nil { - t.Fatalf("failed to create tmpfs root mount: %v", err) - } - - // Create the pipe. - root := mntns.Root() - root.IncRef() - pop := vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(fileName), - } - mknodOpts := vfs.MknodOptions{Mode: linux.ModeNamedPipe | 0644} - if err := vfsObj.MknodAt(ctx, creds, &pop, &mknodOpts); err != nil { - t.Fatalf("failed to create file %q: %v", fileName, err) - } - - // Sanity check: the file pipe exists and has the correct mode. - stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(fileName), - FollowFinalSymlink: true, - }, &vfs.StatOptions{}) - if err != nil { - t.Fatalf("stat(%q) failed: %v", fileName, err) - } - if stat.Mode&^linux.S_IFMT != 0644 { - t.Errorf("got wrong permissions (%0o)", stat.Mode) - } - if stat.Mode&linux.S_IFMT != linux.ModeNamedPipe { - t.Errorf("got wrong file type (%0o)", stat.Mode) - } - - return ctx, creds, vfsObj, root -} - -// checkEmpty calls t.Fatal if the pipe in fd is not empty. -func checkEmpty(ctx context.Context, t *testing.T, fd *vfs.FileDescription) { - readData := make([]byte, 1) - dst := usermem.BytesIOSequence(readData) - bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{}) - if err != linuxerr.ErrWouldBlock { - t.Fatalf("expected ErrWouldBlock reading from empty pipe %q, but got: %v", fileName, err) - } - if bytesRead != 0 { - t.Fatalf("expected to read 0 bytes, but got %d", bytesRead) - } -} - -// checkWrite calls t.Fatal if it fails to write all of msg to fd. -func checkWrite(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) { - writeData := []byte(msg) - src := usermem.BytesIOSequence(writeData) - bytesWritten, err := fd.Write(ctx, src, vfs.WriteOptions{}) - if err != nil { - t.Fatalf("error writing to pipe %q: %v", fileName, err) - } - if bytesWritten != int64(len(writeData)) { - t.Fatalf("expected to write %d bytes, but wrote %d", len(writeData), bytesWritten) - } -} - -// checkRead calls t.Fatal if it fails to read msg from fd. -func checkRead(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) { - readData := make([]byte, len(msg)) - dst := usermem.BytesIOSequence(readData) - bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{}) - if err != nil { - t.Fatalf("error reading from pipe %q: %v", fileName, err) - } - if bytesRead != int64(len(msg)) { - t.Fatalf("expected to read %d bytes, but got %d", len(msg), bytesRead) - } - if !bytes.Equal(readData, []byte(msg)) { - t.Fatalf("expected to read %q from pipe, but got %q", msg, string(readData)) - } -} diff --git a/pkg/sentry/fsimpl/tmpfs/regular_file_test.go b/pkg/sentry/fsimpl/tmpfs/regular_file_test.go deleted file mode 100644 index cb7711b39..000000000 --- a/pkg/sentry/fsimpl/tmpfs/regular_file_test.go +++ /dev/null @@ -1,349 +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 tmpfs - -import ( - "bytes" - "fmt" - "io" - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs/lock" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/usermem" -) - -// Test that we can write some data to a file and read it back.` -func TestSimpleWriteRead(t *testing.T) { - ctx := contexttest.Context(t) - fd, cleanup, err := newFileFD(ctx, 0644) - if err != nil { - t.Fatal(err) - } - defer cleanup() - - // Write. - data := []byte("foobarbaz") - n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{}) - if err != nil { - t.Fatalf("fd.Write failed: %v", err) - } - if n != int64(len(data)) { - t.Errorf("fd.Write got short write length %d, want %d", n, len(data)) - } - if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want { - t.Errorf("fd.Write left offset at %d, want %d", got, want) - } - - // Seek back to beginning. - if _, err := fd.Seek(ctx, 0, linux.SEEK_SET); err != nil { - t.Fatalf("fd.Seek failed: %v", err) - } - if got, want := fd.Impl().(*regularFileFD).off, int64(0); got != want { - t.Errorf("fd.Seek(0) left offset at %d, want %d", got, want) - } - - // Read. - buf := make([]byte, len(data)) - n, err = fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}) - if err != nil && err != io.EOF { - t.Fatalf("fd.Read failed: %v", err) - } - if n != int64(len(data)) { - t.Errorf("fd.Read got short read length %d, want %d", n, len(data)) - } - if got, want := string(buf), string(data); got != want { - t.Errorf("Read got %q want %s", got, want) - } - if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want { - t.Errorf("fd.Write left offset at %d, want %d", got, want) - } -} - -func TestPWrite(t *testing.T) { - ctx := contexttest.Context(t) - fd, cleanup, err := newFileFD(ctx, 0644) - if err != nil { - t.Fatal(err) - } - defer cleanup() - - // Fill file with 1k 'a's. - data := bytes.Repeat([]byte{'a'}, 1000) - n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{}) - if err != nil { - t.Fatalf("fd.Write failed: %v", err) - } - if n != int64(len(data)) { - t.Errorf("fd.Write got short write length %d, want %d", n, len(data)) - } - - // Write "gVisor is awesome" at various offsets. - buf := []byte("gVisor is awesome") - offsets := []int{0, 1, 2, 10, 20, 50, 100, len(data) - 100, len(data) - 1, len(data), len(data) + 1} - for _, offset := range offsets { - name := fmt.Sprintf("PWrite offset=%d", offset) - t.Run(name, func(t *testing.T) { - n, err := fd.PWrite(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.WriteOptions{}) - if err != nil { - t.Errorf("fd.PWrite got err %v want nil", err) - } - if n != int64(len(buf)) { - t.Errorf("fd.PWrite got %d bytes want %d", n, len(buf)) - } - - // Update data to reflect expected file contents. - if len(data) < offset+len(buf) { - data = append(data, make([]byte, (offset+len(buf))-len(data))...) - } - copy(data[offset:], buf) - - // Read the whole file and compare with data. - readBuf := make([]byte, len(data)) - n, err = fd.PRead(ctx, usermem.BytesIOSequence(readBuf), 0, vfs.ReadOptions{}) - if err != nil { - t.Fatalf("fd.PRead failed: %v", err) - } - if n != int64(len(data)) { - t.Errorf("fd.PRead got short read length %d, want %d", n, len(data)) - } - if got, want := string(readBuf), string(data); got != want { - t.Errorf("PRead got %q want %s", got, want) - } - - }) - } -} - -func TestLocks(t *testing.T) { - ctx := contexttest.Context(t) - fd, cleanup, err := newFileFD(ctx, 0644) - if err != nil { - t.Fatal(err) - } - defer cleanup() - - uid1 := 123 - uid2 := 456 - if err := fd.Impl().LockBSD(ctx, uid1, 0 /* ownerPID */, lock.ReadLock, nil); err != nil { - t.Fatalf("fd.Impl().LockBSD failed: err = %v", err) - } - if err := fd.Impl().LockBSD(ctx, uid2, 0 /* ownerPID */, lock.ReadLock, nil); err != nil { - t.Fatalf("fd.Impl().LockBSD failed: err = %v", err) - } - if got, want := fd.Impl().LockBSD(ctx, uid2, 0 /* ownerPID */, lock.WriteLock, nil), linuxerr.ErrWouldBlock; got != want { - t.Fatalf("fd.Impl().LockBSD failed: got = %v, want = %v", got, want) - } - if err := fd.Impl().UnlockBSD(ctx, uid1); err != nil { - t.Fatalf("fd.Impl().UnlockBSD failed: err = %v", err) - } - if err := fd.Impl().LockBSD(ctx, uid2, 0 /* ownerPID */, lock.WriteLock, nil); err != nil { - t.Fatalf("fd.Impl().LockBSD failed: err = %v", err) - } - - if err := fd.Impl().LockPOSIX(ctx, uid1, 0 /* ownerPID */, lock.ReadLock, lock.LockRange{Start: 0, End: 1}, nil); err != nil { - t.Fatalf("fd.Impl().LockPOSIX failed: err = %v", err) - } - if err := fd.Impl().LockPOSIX(ctx, uid2, 0 /* ownerPID */, lock.ReadLock, lock.LockRange{Start: 1, End: 2}, nil); err != nil { - t.Fatalf("fd.Impl().LockPOSIX failed: err = %v", err) - } - if err := fd.Impl().LockPOSIX(ctx, uid1, 0 /* ownerPID */, lock.WriteLock, lock.LockRange{Start: 0, End: 1}, nil); err != nil { - t.Fatalf("fd.Impl().LockPOSIX failed: err = %v", err) - } - if got, want := fd.Impl().LockPOSIX(ctx, uid2, 0 /* ownerPID */, lock.ReadLock, lock.LockRange{Start: 0, End: 1}, nil), linuxerr.ErrWouldBlock; got != want { - t.Fatalf("fd.Impl().LockPOSIX failed: got = %v, want = %v", got, want) - } - if err := fd.Impl().UnlockPOSIX(ctx, uid1, lock.LockRange{Start: 0, End: 1}); err != nil { - t.Fatalf("fd.Impl().UnlockPOSIX failed: err = %v", err) - } -} - -func TestPRead(t *testing.T) { - ctx := contexttest.Context(t) - fd, cleanup, err := newFileFD(ctx, 0644) - if err != nil { - t.Fatal(err) - } - defer cleanup() - - // Write 100 sequences of 'gVisor is awesome'. - data := bytes.Repeat([]byte("gVisor is awsome"), 100) - n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{}) - if err != nil { - t.Fatalf("fd.Write failed: %v", err) - } - if n != int64(len(data)) { - t.Errorf("fd.Write got short write length %d, want %d", n, len(data)) - } - - // Read various sizes from various offsets. - sizes := []int{0, 1, 2, 10, 20, 50, 100, 1000} - offsets := []int{0, 1, 2, 10, 20, 50, 100, 1000, len(data) - 100, len(data) - 1, len(data), len(data) + 1} - - for _, size := range sizes { - for _, offset := range offsets { - name := fmt.Sprintf("PRead offset=%d size=%d", offset, size) - t.Run(name, func(t *testing.T) { - var ( - wantRead []byte - wantErr error - ) - if offset < len(data) { - wantRead = data[offset:] - } else if size > 0 { - wantErr = io.EOF - } - if offset+size < len(data) { - wantRead = wantRead[:size] - } - buf := make([]byte, size) - n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.ReadOptions{}) - if err != wantErr { - t.Errorf("fd.PRead got err %v want %v", err, wantErr) - } - if n != int64(len(wantRead)) { - t.Errorf("fd.PRead got %d bytes want %d", n, len(wantRead)) - } - if got := string(buf[:n]); got != string(wantRead) { - t.Errorf("fd.PRead got %q want %q", got, string(wantRead)) - } - }) - } - } -} - -func TestTruncate(t *testing.T) { - ctx := contexttest.Context(t) - fd, cleanup, err := newFileFD(ctx, 0644) - if err != nil { - t.Fatal(err) - } - defer cleanup() - - // Fill the file with some data. - data := bytes.Repeat([]byte("gVisor is awsome"), 100) - written, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{}) - if err != nil { - t.Fatalf("fd.Write failed: %v", err) - } - - // Size should be same as written. - sizeStatOpts := vfs.StatOptions{Mask: linux.STATX_SIZE} - stat, err := fd.Stat(ctx, sizeStatOpts) - if err != nil { - t.Fatalf("fd.Stat failed: %v", err) - } - if got, want := int64(stat.Size), written; got != want { - t.Errorf("fd.Stat got size %d, want %d", got, want) - } - - // Truncate down. - newSize := uint64(10) - if err := fd.SetStat(ctx, vfs.SetStatOptions{ - Stat: linux.Statx{ - Mask: linux.STATX_SIZE, - Size: newSize, - }, - }); err != nil { - t.Errorf("fd.Truncate failed: %v", err) - } - // Size should be updated. - statAfterTruncateDown, err := fd.Stat(ctx, sizeStatOpts) - if err != nil { - t.Fatalf("fd.Stat failed: %v", err) - } - if got, want := statAfterTruncateDown.Size, newSize; got != want { - t.Errorf("fd.Stat got size %d, want %d", got, want) - } - // We should only read newSize worth of data. - buf := make([]byte, 1000) - if n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0, vfs.ReadOptions{}); err != nil && err != io.EOF { - t.Fatalf("fd.PRead failed: %v", err) - } else if uint64(n) != newSize { - t.Errorf("fd.PRead got size %d, want %d", n, newSize) - } - // Mtime and Ctime should be bumped. - if got := statAfterTruncateDown.Mtime.ToNsec(); got <= stat.Mtime.ToNsec() { - t.Errorf("fd.Stat got Mtime %v, want > %v", got, stat.Mtime) - } - if got := statAfterTruncateDown.Ctime.ToNsec(); got <= stat.Ctime.ToNsec() { - t.Errorf("fd.Stat got Ctime %v, want > %v", got, stat.Ctime) - } - - // Truncate up. - newSize = 100 - if err := fd.SetStat(ctx, vfs.SetStatOptions{ - Stat: linux.Statx{ - Mask: linux.STATX_SIZE, - Size: newSize, - }, - }); err != nil { - t.Errorf("fd.Truncate failed: %v", err) - } - // Size should be updated. - statAfterTruncateUp, err := fd.Stat(ctx, sizeStatOpts) - if err != nil { - t.Fatalf("fd.Stat failed: %v", err) - } - if got, want := statAfterTruncateUp.Size, newSize; got != want { - t.Errorf("fd.Stat got size %d, want %d", got, want) - } - // We should read newSize worth of data. - buf = make([]byte, 1000) - if n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0, vfs.ReadOptions{}); err != nil && err != io.EOF { - t.Fatalf("fd.PRead failed: %v", err) - } else if uint64(n) != newSize { - t.Errorf("fd.PRead got size %d, want %d", n, newSize) - } - // Bytes should be null after 10, since we previously truncated to 10. - for i := uint64(10); i < newSize; i++ { - if buf[i] != 0 { - t.Errorf("fd.PRead got byte %d=%x, want 0", i, buf[i]) - break - } - } - // Mtime and Ctime should be bumped. - if got := statAfterTruncateUp.Mtime.ToNsec(); got <= statAfterTruncateDown.Mtime.ToNsec() { - t.Errorf("fd.Stat got Mtime %v, want > %v", got, statAfterTruncateDown.Mtime) - } - if got := statAfterTruncateUp.Ctime.ToNsec(); got <= statAfterTruncateDown.Ctime.ToNsec() { - t.Errorf("fd.Stat got Ctime %v, want > %v", got, stat.Ctime) - } - - // Truncate to the current size. - newSize = statAfterTruncateUp.Size - if err := fd.SetStat(ctx, vfs.SetStatOptions{ - Stat: linux.Statx{ - Mask: linux.STATX_SIZE, - Size: newSize, - }, - }); err != nil { - t.Errorf("fd.Truncate failed: %v", err) - } - statAfterTruncateNoop, err := fd.Stat(ctx, sizeStatOpts) - if err != nil { - t.Fatalf("fd.Stat failed: %v", err) - } - // Mtime and Ctime should not be bumped, since operation is a noop. - if got := statAfterTruncateNoop.Mtime.ToNsec(); got != statAfterTruncateUp.Mtime.ToNsec() { - t.Errorf("fd.Stat got Mtime %v, want %v", got, statAfterTruncateUp.Mtime) - } - if got := statAfterTruncateNoop.Ctime.ToNsec(); got != statAfterTruncateUp.Ctime.ToNsec() { - t.Errorf("fd.Stat got Ctime %v, want %v", got, statAfterTruncateUp.Ctime) - } -} diff --git a/pkg/sentry/fsimpl/tmpfs/stat_test.go b/pkg/sentry/fsimpl/tmpfs/stat_test.go deleted file mode 100644 index f7ee4aab2..000000000 --- a/pkg/sentry/fsimpl/tmpfs/stat_test.go +++ /dev/null @@ -1,236 +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 tmpfs - -import ( - "fmt" - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -func TestStatAfterCreate(t *testing.T) { - ctx := contexttest.Context(t) - mode := linux.FileMode(0644) - - // Run with different file types. - for _, typ := range []string{"file", "dir", "pipe"} { - t.Run(fmt.Sprintf("type=%q", typ), func(t *testing.T) { - var ( - fd *vfs.FileDescription - cleanup func() - err error - ) - switch typ { - case "file": - fd, cleanup, err = newFileFD(ctx, mode) - case "dir": - fd, cleanup, err = newDirFD(ctx, mode) - case "pipe": - fd, cleanup, err = newPipeFD(ctx, mode) - default: - panic(fmt.Sprintf("unknown typ %q", typ)) - } - if err != nil { - t.Fatal(err) - } - defer cleanup() - - got, err := fd.Stat(ctx, vfs.StatOptions{}) - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - // Atime, Ctime, Mtime should all be current time (non-zero). - atime, ctime, mtime := got.Atime.ToNsec(), got.Ctime.ToNsec(), got.Mtime.ToNsec() - if atime != ctime || ctime != mtime { - t.Errorf("got atime=%d ctime=%d mtime=%d, wanted equal values", atime, ctime, mtime) - } - if atime == 0 { - t.Errorf("got atime=%d, want non-zero", atime) - } - - // Btime should be 0, as it is not set by tmpfs. - if btime := got.Btime.ToNsec(); btime != 0 { - t.Errorf("got btime %d, want 0", got.Btime.ToNsec()) - } - - // Size should be 0 (except for directories, which make up a size - // of 20 per entry, including the "." and ".." entries present in - // otherwise-empty directories). - wantSize := uint64(0) - if typ == "dir" { - wantSize = 40 - } - if got.Size != wantSize { - t.Errorf("got size %d, want %d", got.Size, wantSize) - } - - // Nlink should be 1 for files, 2 for dirs. - wantNlink := uint32(1) - if typ == "dir" { - wantNlink = 2 - } - if got.Nlink != wantNlink { - t.Errorf("got nlink %d, want %d", got.Nlink, wantNlink) - } - - // UID and GID are set from context creds. - creds := auth.CredentialsFromContext(ctx) - if got.UID != uint32(creds.EffectiveKUID) { - t.Errorf("got uid %d, want %d", got.UID, uint32(creds.EffectiveKUID)) - } - if got.GID != uint32(creds.EffectiveKGID) { - t.Errorf("got gid %d, want %d", got.GID, uint32(creds.EffectiveKGID)) - } - - // Mode. - wantMode := uint16(mode) - switch typ { - case "file": - wantMode |= linux.S_IFREG - case "dir": - wantMode |= linux.S_IFDIR - case "pipe": - wantMode |= linux.S_IFIFO - default: - panic(fmt.Sprintf("unknown typ %q", typ)) - } - - if got.Mode != wantMode { - t.Errorf("got mode %x, want %x", got.Mode, wantMode) - } - - // Ino. - if got.Ino == 0 { - t.Errorf("got ino %d, want not 0", got.Ino) - } - }) - } -} - -func TestSetStatAtime(t *testing.T) { - ctx := contexttest.Context(t) - fd, cleanup, err := newFileFD(ctx, 0644) - if err != nil { - t.Fatal(err) - } - defer cleanup() - - allStatOptions := vfs.StatOptions{Mask: linux.STATX_ALL} - - // Get initial stat. - initialStat, err := fd.Stat(ctx, allStatOptions) - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - // Set atime, but without the mask. - if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: linux.Statx{ - Mask: 0, - Atime: linux.NsecToStatxTimestamp(100), - }}); err != nil { - t.Errorf("SetStat atime without mask failed: %v", err) - } - // Atime should be unchanged. - if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil { - t.Errorf("Stat got error: %v", err) - } else if gotStat.Atime != initialStat.Atime { - t.Errorf("Stat got atime %d, want %d", gotStat.Atime, initialStat.Atime) - } - - // Set atime, this time included in the mask. - setStat := linux.Statx{ - Mask: linux.STATX_ATIME, - Atime: linux.NsecToStatxTimestamp(100), - } - if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: setStat}); err != nil { - t.Errorf("SetStat atime with mask failed: %v", err) - } - if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil { - t.Errorf("Stat got error: %v", err) - } else if gotStat.Atime != setStat.Atime { - t.Errorf("Stat got atime %d, want %d", gotStat.Atime, setStat.Atime) - } -} - -func TestSetStat(t *testing.T) { - ctx := contexttest.Context(t) - mode := linux.FileMode(0644) - - // Run with different file types. - for _, typ := range []string{"file", "dir", "pipe"} { - t.Run(fmt.Sprintf("type=%q", typ), func(t *testing.T) { - var ( - fd *vfs.FileDescription - cleanup func() - err error - ) - switch typ { - case "file": - fd, cleanup, err = newFileFD(ctx, mode) - case "dir": - fd, cleanup, err = newDirFD(ctx, mode) - case "pipe": - fd, cleanup, err = newPipeFD(ctx, mode) - default: - panic(fmt.Sprintf("unknown typ %q", typ)) - } - if err != nil { - t.Fatal(err) - } - defer cleanup() - - allStatOptions := vfs.StatOptions{Mask: linux.STATX_ALL} - - // Get initial stat. - initialStat, err := fd.Stat(ctx, allStatOptions) - if err != nil { - t.Fatalf("Stat failed: %v", err) - } - - // Set atime, but without the mask. - if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: linux.Statx{ - Mask: 0, - Atime: linux.NsecToStatxTimestamp(100), - }}); err != nil { - t.Errorf("SetStat atime without mask failed: %v", err) - } - // Atime should be unchanged. - if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil { - t.Errorf("Stat got error: %v", err) - } else if gotStat.Atime != initialStat.Atime { - t.Errorf("Stat got atime %d, want %d", gotStat.Atime, initialStat.Atime) - } - - // Set atime, this time included in the mask. - setStat := linux.Statx{ - Mask: linux.STATX_ATIME, - Atime: linux.NsecToStatxTimestamp(100), - } - if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: setStat}); err != nil { - t.Errorf("SetStat atime with mask failed: %v", err) - } - if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil { - t.Errorf("Stat got error: %v", err) - } else if gotStat.Atime != setStat.Atime { - t.Errorf("Stat got atime %d, want %d", gotStat.Atime, setStat.Atime) - } - }) - } -} diff --git a/pkg/sentry/fsimpl/tmpfs/tmpfs_state_autogen.go b/pkg/sentry/fsimpl/tmpfs/tmpfs_state_autogen.go new file mode 100644 index 000000000..6d9f09eef --- /dev/null +++ b/pkg/sentry/fsimpl/tmpfs/tmpfs_state_autogen.go @@ -0,0 +1,593 @@ +// automatically generated by stateify. + +package tmpfs + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (l *dentryList) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.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/tmpfs.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 (d *deviceFile) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.deviceFile" +} + +func (d *deviceFile) StateFields() []string { + return []string{ + "inode", + "kind", + "major", + "minor", + } +} + +func (d *deviceFile) beforeSave() {} + +// +checklocksignore +func (d *deviceFile) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.inode) + stateSinkObject.Save(1, &d.kind) + stateSinkObject.Save(2, &d.major) + stateSinkObject.Save(3, &d.minor) +} + +func (d *deviceFile) afterLoad() {} + +// +checklocksignore +func (d *deviceFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.inode) + stateSourceObject.Load(1, &d.kind) + stateSourceObject.Load(2, &d.major) + stateSourceObject.Load(3, &d.minor) +} + +func (dir *directory) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.directory" +} + +func (dir *directory) StateFields() []string { + return []string{ + "dentry", + "inode", + "childMap", + "numChildren", + "childList", + } +} + +func (dir *directory) beforeSave() {} + +// +checklocksignore +func (dir *directory) StateSave(stateSinkObject state.Sink) { + dir.beforeSave() + stateSinkObject.Save(0, &dir.dentry) + stateSinkObject.Save(1, &dir.inode) + stateSinkObject.Save(2, &dir.childMap) + stateSinkObject.Save(3, &dir.numChildren) + stateSinkObject.Save(4, &dir.childList) +} + +func (dir *directory) afterLoad() {} + +// +checklocksignore +func (dir *directory) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &dir.dentry) + stateSourceObject.Load(1, &dir.inode) + stateSourceObject.Load(2, &dir.childMap) + stateSourceObject.Load(3, &dir.numChildren) + stateSourceObject.Load(4, &dir.childList) +} + +func (fd *directoryFD) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.directoryFD" +} + +func (fd *directoryFD) StateFields() []string { + return []string{ + "fileDescription", + "DirectoryFileDescriptionDefaultImpl", + "iter", + "off", + } +} + +func (fd *directoryFD) beforeSave() {} + +// +checklocksignore +func (fd *directoryFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.fileDescription) + stateSinkObject.Save(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSinkObject.Save(2, &fd.iter) + stateSinkObject.Save(3, &fd.off) +} + +func (fd *directoryFD) afterLoad() {} + +// +checklocksignore +func (fd *directoryFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.fileDescription) + stateSourceObject.Load(1, &fd.DirectoryFileDescriptionDefaultImpl) + stateSourceObject.Load(2, &fd.iter) + stateSourceObject.Load(3, &fd.off) +} + +func (r *inodeRefs) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.inodeRefs" +} + +func (r *inodeRefs) StateFields() []string { + return []string{ + "refCount", + } +} + +func (r *inodeRefs) beforeSave() {} + +// +checklocksignore +func (r *inodeRefs) StateSave(stateSinkObject state.Sink) { + r.beforeSave() + stateSinkObject.Save(0, &r.refCount) +} + +// +checklocksignore +func (r *inodeRefs) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &r.refCount) + stateSourceObject.AfterLoad(r.afterLoad) +} + +func (n *namedPipe) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.namedPipe" +} + +func (n *namedPipe) StateFields() []string { + return []string{ + "inode", + "pipe", + } +} + +func (n *namedPipe) beforeSave() {} + +// +checklocksignore +func (n *namedPipe) StateSave(stateSinkObject state.Sink) { + n.beforeSave() + stateSinkObject.Save(0, &n.inode) + stateSinkObject.Save(1, &n.pipe) +} + +func (n *namedPipe) afterLoad() {} + +// +checklocksignore +func (n *namedPipe) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &n.inode) + stateSourceObject.Load(1, &n.pipe) +} + +func (rf *regularFile) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.regularFile" +} + +func (rf *regularFile) StateFields() []string { + return []string{ + "inode", + "memoryUsageKind", + "mappings", + "writableMappingPages", + "data", + "seals", + "size", + } +} + +func (rf *regularFile) beforeSave() {} + +// +checklocksignore +func (rf *regularFile) StateSave(stateSinkObject state.Sink) { + rf.beforeSave() + stateSinkObject.Save(0, &rf.inode) + stateSinkObject.Save(1, &rf.memoryUsageKind) + stateSinkObject.Save(2, &rf.mappings) + stateSinkObject.Save(3, &rf.writableMappingPages) + stateSinkObject.Save(4, &rf.data) + stateSinkObject.Save(5, &rf.seals) + stateSinkObject.Save(6, &rf.size) +} + +// +checklocksignore +func (rf *regularFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &rf.inode) + stateSourceObject.Load(1, &rf.memoryUsageKind) + stateSourceObject.Load(2, &rf.mappings) + stateSourceObject.Load(3, &rf.writableMappingPages) + stateSourceObject.Load(4, &rf.data) + stateSourceObject.Load(5, &rf.seals) + stateSourceObject.Load(6, &rf.size) + stateSourceObject.AfterLoad(rf.afterLoad) +} + +func (fd *regularFileFD) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.regularFileFD" +} + +func (fd *regularFileFD) StateFields() []string { + return []string{ + "fileDescription", + "off", + } +} + +func (fd *regularFileFD) beforeSave() {} + +// +checklocksignore +func (fd *regularFileFD) StateSave(stateSinkObject state.Sink) { + fd.beforeSave() + stateSinkObject.Save(0, &fd.fileDescription) + stateSinkObject.Save(1, &fd.off) +} + +func (fd *regularFileFD) afterLoad() {} + +// +checklocksignore +func (fd *regularFileFD) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fd.fileDescription) + stateSourceObject.Load(1, &fd.off) +} + +func (s *socketFile) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.socketFile" +} + +func (s *socketFile) StateFields() []string { + return []string{ + "inode", + "ep", + } +} + +func (s *socketFile) beforeSave() {} + +// +checklocksignore +func (s *socketFile) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.inode) + stateSinkObject.Save(1, &s.ep) +} + +func (s *socketFile) afterLoad() {} + +// +checklocksignore +func (s *socketFile) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.inode) + stateSourceObject.Load(1, &s.ep) +} + +func (s *symlink) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.symlink" +} + +func (s *symlink) StateFields() []string { + return []string{ + "inode", + "target", + } +} + +func (s *symlink) beforeSave() {} + +// +checklocksignore +func (s *symlink) StateSave(stateSinkObject state.Sink) { + s.beforeSave() + stateSinkObject.Save(0, &s.inode) + stateSinkObject.Save(1, &s.target) +} + +func (s *symlink) afterLoad() {} + +// +checklocksignore +func (s *symlink) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &s.inode) + stateSourceObject.Load(1, &s.target) +} + +func (fstype *FilesystemType) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.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/tmpfs.filesystem" +} + +func (fs *filesystem) StateFields() []string { + return []string{ + "vfsfs", + "mfp", + "clock", + "devMinor", + "mopts", + "nextInoMinusOne", + "root", + } +} + +func (fs *filesystem) beforeSave() {} + +// +checklocksignore +func (fs *filesystem) StateSave(stateSinkObject state.Sink) { + fs.beforeSave() + stateSinkObject.Save(0, &fs.vfsfs) + stateSinkObject.Save(1, &fs.mfp) + stateSinkObject.Save(2, &fs.clock) + stateSinkObject.Save(3, &fs.devMinor) + stateSinkObject.Save(4, &fs.mopts) + stateSinkObject.Save(5, &fs.nextInoMinusOne) + stateSinkObject.Save(6, &fs.root) +} + +func (fs *filesystem) afterLoad() {} + +// +checklocksignore +func (fs *filesystem) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &fs.vfsfs) + stateSourceObject.Load(1, &fs.mfp) + stateSourceObject.Load(2, &fs.clock) + stateSourceObject.Load(3, &fs.devMinor) + stateSourceObject.Load(4, &fs.mopts) + stateSourceObject.Load(5, &fs.nextInoMinusOne) + stateSourceObject.Load(6, &fs.root) +} + +func (f *FilesystemOpts) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.FilesystemOpts" +} + +func (f *FilesystemOpts) StateFields() []string { + return []string{ + "RootFileType", + "RootSymlinkTarget", + "FilesystemType", + } +} + +func (f *FilesystemOpts) beforeSave() {} + +// +checklocksignore +func (f *FilesystemOpts) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.RootFileType) + stateSinkObject.Save(1, &f.RootSymlinkTarget) + stateSinkObject.Save(2, &f.FilesystemType) +} + +func (f *FilesystemOpts) afterLoad() {} + +// +checklocksignore +func (f *FilesystemOpts) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.RootFileType) + stateSourceObject.Load(1, &f.RootSymlinkTarget) + stateSourceObject.Load(2, &f.FilesystemType) +} + +func (d *dentry) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.dentry" +} + +func (d *dentry) StateFields() []string { + return []string{ + "vfsd", + "parent", + "name", + "dentryEntry", + "inode", + } +} + +func (d *dentry) beforeSave() {} + +// +checklocksignore +func (d *dentry) StateSave(stateSinkObject state.Sink) { + d.beforeSave() + stateSinkObject.Save(0, &d.vfsd) + stateSinkObject.Save(1, &d.parent) + stateSinkObject.Save(2, &d.name) + stateSinkObject.Save(3, &d.dentryEntry) + stateSinkObject.Save(4, &d.inode) +} + +func (d *dentry) afterLoad() {} + +// +checklocksignore +func (d *dentry) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &d.vfsd) + stateSourceObject.Load(1, &d.parent) + stateSourceObject.Load(2, &d.name) + stateSourceObject.Load(3, &d.dentryEntry) + stateSourceObject.Load(4, &d.inode) +} + +func (i *inode) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.inode" +} + +func (i *inode) StateFields() []string { + return []string{ + "fs", + "refs", + "xattrs", + "mode", + "nlink", + "uid", + "gid", + "ino", + "atime", + "ctime", + "mtime", + "locks", + "watches", + "impl", + } +} + +func (i *inode) beforeSave() {} + +// +checklocksignore +func (i *inode) StateSave(stateSinkObject state.Sink) { + i.beforeSave() + stateSinkObject.Save(0, &i.fs) + stateSinkObject.Save(1, &i.refs) + stateSinkObject.Save(2, &i.xattrs) + stateSinkObject.Save(3, &i.mode) + stateSinkObject.Save(4, &i.nlink) + stateSinkObject.Save(5, &i.uid) + stateSinkObject.Save(6, &i.gid) + stateSinkObject.Save(7, &i.ino) + stateSinkObject.Save(8, &i.atime) + stateSinkObject.Save(9, &i.ctime) + stateSinkObject.Save(10, &i.mtime) + stateSinkObject.Save(11, &i.locks) + stateSinkObject.Save(12, &i.watches) + stateSinkObject.Save(13, &i.impl) +} + +func (i *inode) afterLoad() {} + +// +checklocksignore +func (i *inode) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &i.fs) + stateSourceObject.Load(1, &i.refs) + stateSourceObject.Load(2, &i.xattrs) + stateSourceObject.Load(3, &i.mode) + stateSourceObject.Load(4, &i.nlink) + stateSourceObject.Load(5, &i.uid) + stateSourceObject.Load(6, &i.gid) + stateSourceObject.Load(7, &i.ino) + stateSourceObject.Load(8, &i.atime) + stateSourceObject.Load(9, &i.ctime) + stateSourceObject.Load(10, &i.mtime) + stateSourceObject.Load(11, &i.locks) + stateSourceObject.Load(12, &i.watches) + stateSourceObject.Load(13, &i.impl) +} + +func (fd *fileDescription) StateTypeName() string { + return "pkg/sentry/fsimpl/tmpfs.fileDescription" +} + +func (fd *fileDescription) StateFields() []string { + return []string{ + "vfsfd", + "FileDescriptionDefaultImpl", + "LockFD", + } +} + +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.LockFD) +} + +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.LockFD) +} + +func init() { + state.Register((*dentryList)(nil)) + state.Register((*dentryEntry)(nil)) + state.Register((*deviceFile)(nil)) + state.Register((*directory)(nil)) + state.Register((*directoryFD)(nil)) + state.Register((*inodeRefs)(nil)) + state.Register((*namedPipe)(nil)) + state.Register((*regularFile)(nil)) + state.Register((*regularFileFD)(nil)) + state.Register((*socketFile)(nil)) + state.Register((*symlink)(nil)) + state.Register((*FilesystemType)(nil)) + state.Register((*filesystem)(nil)) + state.Register((*FilesystemOpts)(nil)) + state.Register((*dentry)(nil)) + state.Register((*inode)(nil)) + state.Register((*fileDescription)(nil)) +} diff --git a/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go b/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go deleted file mode 100644 index fc5323abc..000000000 --- a/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go +++ /dev/null @@ -1,157 +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 tmpfs - -import ( - "fmt" - "sync/atomic" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// nextFileID is used to generate unique file names. -var nextFileID int64 - -// newTmpfsRoot creates a new tmpfs mount, and returns the root. If the error -// is not nil, then cleanup should be called when the root is no longer needed. -func newTmpfsRoot(ctx context.Context) (*vfs.VirtualFilesystem, vfs.VirtualDentry, func(), error) { - creds := auth.CredentialsFromContext(ctx) - - vfsObj := &vfs.VirtualFilesystem{} - if err := vfsObj.Init(ctx); err != nil { - return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("VFS init: %v", err) - } - - vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ - AllowUserMount: true, - }) - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) - if err != nil { - return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("failed to create tmpfs root mount: %v", err) - } - root := mntns.Root() - root.IncRef() - return vfsObj, root, func() { - root.DecRef(ctx) - mntns.DecRef(ctx) - }, nil -} - -// newFileFD creates a new file in a new tmpfs mount, and returns the FD. If -// the returned err is not nil, then cleanup should be called when the FD is no -// longer needed. -func newFileFD(ctx context.Context, mode linux.FileMode) (*vfs.FileDescription, func(), error) { - creds := auth.CredentialsFromContext(ctx) - vfsObj, root, cleanup, err := newTmpfsRoot(ctx) - if err != nil { - return nil, nil, err - } - - filename := fmt.Sprintf("tmpfs-test-file-%d", atomic.AddInt64(&nextFileID, 1)) - - // Create the file that will be write/read. - fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(filename), - }, &vfs.OpenOptions{ - Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL, - Mode: linux.ModeRegular | mode, - }) - if err != nil { - cleanup() - return nil, nil, fmt.Errorf("failed to create file %q: %v", filename, err) - } - - return fd, cleanup, nil -} - -// newDirFD is like newFileFD, but for directories. -func newDirFD(ctx context.Context, mode linux.FileMode) (*vfs.FileDescription, func(), error) { - creds := auth.CredentialsFromContext(ctx) - vfsObj, root, cleanup, err := newTmpfsRoot(ctx) - if err != nil { - return nil, nil, err - } - - dirname := fmt.Sprintf("tmpfs-test-dir-%d", atomic.AddInt64(&nextFileID, 1)) - - // Create the dir. - if err := vfsObj.MkdirAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(dirname), - }, &vfs.MkdirOptions{ - Mode: linux.ModeDirectory | mode, - }); err != nil { - cleanup() - return nil, nil, fmt.Errorf("failed to create directory %q: %v", dirname, err) - } - - // Open the dir and return it. - fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(dirname), - }, &vfs.OpenOptions{ - Flags: linux.O_RDONLY | linux.O_DIRECTORY, - }) - if err != nil { - cleanup() - return nil, nil, fmt.Errorf("failed to open directory %q: %v", dirname, err) - } - - return fd, cleanup, nil -} - -// newPipeFD is like newFileFD, but for pipes. -func newPipeFD(ctx context.Context, mode linux.FileMode) (*vfs.FileDescription, func(), error) { - creds := auth.CredentialsFromContext(ctx) - vfsObj, root, cleanup, err := newTmpfsRoot(ctx) - if err != nil { - return nil, nil, err - } - - name := fmt.Sprintf("tmpfs-test-%d", atomic.AddInt64(&nextFileID, 1)) - - if err := vfsObj.MknodAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(name), - }, &vfs.MknodOptions{ - Mode: linux.ModeNamedPipe | mode, - }); err != nil { - cleanup() - return nil, nil, fmt.Errorf("failed to create pipe %q: %v", name, err) - } - - fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(name), - }, &vfs.OpenOptions{ - Flags: linux.O_RDWR, - }) - if err != nil { - cleanup() - return nil, nil, fmt.Errorf("failed to open pipe %q: %v", name, err) - } - - return fd, cleanup, nil -} 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) - } - } - }) - } -} |