From 30182dc10f7b9f47698ec50441a910c90cd328dd Mon Sep 17 00:00:00 2001 From: Shambhavi Srivastava Date: Tue, 4 Oct 2022 15:55:04 -0700 Subject: [PATCH] Add /proc/[pid]/maps to runsc trace procfs Updates #7897 PiperOrigin-RevId: 478903608 --- pkg/hostarch/access_type.go | 13 ++++--- pkg/sentry/fsimpl/proc/task_files.go | 2 +- pkg/sentry/mm/mm.go | 3 ++ pkg/sentry/mm/procfs.go | 56 +++++++++++++++++----------- runsc/boot/procfs/BUILD | 1 + runsc/boot/procfs/dump.go | 37 ++++++++++++++++++ runsc/container/trace_test.go | 10 +++++ 7 files changed, 93 insertions(+), 29 deletions(-) diff --git a/pkg/hostarch/access_type.go b/pkg/hostarch/access_type.go index e30476840..a085b6009 100644 --- a/pkg/hostarch/access_type.go +++ b/pkg/hostarch/access_type.go @@ -117,10 +117,11 @@ func (a AccessType) Effective() AccessType { // Convenient access types. var ( - NoAccess = AccessType{} - Read = AccessType{Read: true} - Write = AccessType{Write: true} - Execute = AccessType{Execute: true} - ReadWrite = AccessType{Read: true, Write: true} - AnyAccess = AccessType{Read: true, Write: true, Execute: true} + NoAccess = AccessType{} + Read = AccessType{Read: true} + Write = AccessType{Write: true} + Execute = AccessType{Execute: true} + ReadWrite = AccessType{Read: true, Write: true} + ReadExecute = AccessType{Read: true, Execute: true} + AnyAccess = AccessType{Read: true, Write: true, Execute: true} ) diff --git a/pkg/sentry/fsimpl/proc/task_files.go b/pkg/sentry/fsimpl/proc/task_files.go index fff830f85..aec45540e 100644 --- a/pkg/sentry/fsimpl/proc/task_files.go +++ b/pkg/sentry/fsimpl/proc/task_files.go @@ -544,7 +544,7 @@ var _ dynamicInode = (*mapsData)(nil) // Generate implements vfs.DynamicBytesSource.Generate. func (d *mapsData) Generate(ctx context.Context, buf *bytes.Buffer) error { if mm := getMM(d.task); mm != nil { - mm.ReadMapsDataInto(ctx, buf) + mm.ReadMapsDataInto(ctx, mm.MapsCallbackFuncForBuffer(buf)) } return nil } diff --git a/pkg/sentry/mm/mm.go b/pkg/sentry/mm/mm.go index a6a55cd6b..ea5f46177 100644 --- a/pkg/sentry/mm/mm.go +++ b/pkg/sentry/mm/mm.go @@ -50,6 +50,9 @@ import ( "gvisor.dev/gvisor/pkg/sentry/platform" ) +// MapsCallbackFunc has all the parameters required for populating an entry of /proc/[pid]/maps. +type MapsCallbackFunc func(start, end hostarch.Addr, permissions hostarch.AccessType, private string, offset uint64, devMajor, devMinor uint32, inode uint64, path string) + // MemoryManager implements a virtual address space. // // +stateify savable diff --git a/pkg/sentry/mm/procfs.go b/pkg/sentry/mm/procfs.go index 6358e9c14..c5ff51809 100644 --- a/pkg/sentry/mm/procfs.go +++ b/pkg/sentry/mm/procfs.go @@ -20,6 +20,7 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/hostarch" + "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" "gvisor.dev/gvisor/pkg/sentry/memmap" ) @@ -57,9 +58,32 @@ func (mm *MemoryManager) NeedsUpdate(generation int64) bool { return true } +// MapsCallbackFuncForBuffer creates a /proc/[pid]/maps entry including the trailing newline. +func (mm *MemoryManager) MapsCallbackFuncForBuffer(buf *bytes.Buffer) MapsCallbackFunc { + return func(start, end hostarch.Addr, permissions hostarch.AccessType, private string, offset uint64, devMajor, devMinor uint32, inode uint64, path string) { + // Do not include the guard page: fs/proc/task_mmu.c:show_map_vma() => + // stack_guard_page_start(). + lineLen, err := fmt.Fprintf(buf, "%08x-%08x %s%s %08x %02x:%02x %d ", + start, end, permissions, private, offset, devMajor, devMinor, inode) + if err != nil { + log.Warningf("Failed to write to buffer with error: %v", err) + return + } + + if path != "" { + // Per linux, we pad until the 74th character. + for pad := 73 - lineLen; pad > 0; pad-- { + buf.WriteByte(' ') // never returns a non-nil error + } + buf.WriteString(path) // never returns a non-nil error + } + buf.WriteByte('\n') // never returns a non-nil error + } +} + // ReadMapsDataInto is called by fsimpl/proc.mapsData.Generate to // implement /proc/[pid]/maps. -func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, buf *bytes.Buffer) { +func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, fn MapsCallbackFunc) { // FIXME(b/235153601): Need to replace RLockBypass with RLockBypass // after fixing b/235153601. mm.mappingMu.RLockBypass() @@ -67,7 +91,7 @@ func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, buf *bytes.Buffer var start hostarch.Addr for vseg := mm.vmas.LowerBoundSegment(start); vseg.Ok(); vseg = vseg.NextSegment() { - mm.appendVMAMapsEntryLocked(ctx, vseg, buf) + mm.appendVMAMapsEntryLocked(ctx, vseg, fn) } // We always emulate vsyscall, so advertise it here. Everything about a @@ -80,7 +104,7 @@ func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, buf *bytes.Buffer // // Artifically adjust the seqfile handle so we only output vsyscall entry once. if start != vsyscallEnd { - buf.WriteString(vsyscallMapsEntry) + fn(hostarch.Addr(0xffffffffff600000), hostarch.Addr(0xffffffffff601000), hostarch.ReadExecute, "p", 0, 0, 0, 0, "[vsyscall]") } } @@ -129,12 +153,12 @@ func (mm *MemoryManager) ReadMapsSeqFileData(ctx context.Context, handle seqfile // Preconditions: mm.mappingMu must be locked. func (mm *MemoryManager) vmaMapsEntryLocked(ctx context.Context, vseg vmaIterator) []byte { var b bytes.Buffer - mm.appendVMAMapsEntryLocked(ctx, vseg, &b) + mm.appendVMAMapsEntryLocked(ctx, vseg, mm.MapsCallbackFuncForBuffer(&b)) return b.Bytes() } // Preconditions: mm.mappingMu must be locked. -func (mm *MemoryManager) appendVMAMapsEntryLocked(ctx context.Context, vseg vmaIterator, b *bytes.Buffer) { +func (mm *MemoryManager) appendVMAMapsEntryLocked(ctx context.Context, vseg vmaIterator, fn MapsCallbackFunc) { vma := vseg.ValuePtr() private := "p" if !vma.private { @@ -149,31 +173,19 @@ func (mm *MemoryManager) appendVMAMapsEntryLocked(ctx context.Context, vseg vmaI devMajor := uint32(dev >> devMinorBits) devMinor := uint32(dev & ((1 << devMinorBits) - 1)) - // Do not include the guard page: fs/proc/task_mmu.c:show_map_vma() => - // stack_guard_page_start(). - lineLen, _ := fmt.Fprintf(b, "%08x-%08x %s%s %08x %02x:%02x %d ", - vseg.Start(), vseg.End(), vma.realPerms, private, vma.off, devMajor, devMinor, ino) - // Figure out our filename or hint. - var s string + var path string if vma.hint != "" { - s = vma.hint + path = vma.hint } else if vma.id != nil { // FIXME(jamieliu): We are holding mm.mappingMu here, which is // consistent with Linux's holding mmap_sem in // fs/proc/task_mmu.c:show_map_vma() => fs/seq_file.c:seq_file_path(). // However, it's not clear that fs.File.MappedName() is actually // consistent with this lock order. - s = vma.id.MappedName(ctx) + path = vma.id.MappedName(ctx) } - if s != "" { - // Per linux, we pad until the 74th character. - for pad := 73 - lineLen; pad > 0; pad-- { - b.WriteByte(' ') - } - b.WriteString(s) - } - b.WriteByte('\n') + fn(vseg.Start(), vseg.End(), vma.realPerms, private, vma.off, devMajor, devMinor, ino, path) } // ReadSmapsDataInto is called by fsimpl/proc.smapsData.Generate to @@ -239,7 +251,7 @@ func (mm *MemoryManager) vmaSmapsEntryLocked(ctx context.Context, vseg vmaIterat } func (mm *MemoryManager) vmaSmapsEntryIntoLocked(ctx context.Context, vseg vmaIterator, b *bytes.Buffer) { - mm.appendVMAMapsEntryLocked(ctx, vseg, b) + mm.appendVMAMapsEntryLocked(ctx, vseg, mm.MapsCallbackFuncForBuffer(b)) vma := vseg.ValuePtr() // We take mm.activeMu here in each call to vmaSmapsEntryLocked, instead of diff --git a/runsc/boot/procfs/BUILD b/runsc/boot/procfs/BUILD index bc1ecf773..ae0e2b6ae 100644 --- a/runsc/boot/procfs/BUILD +++ b/runsc/boot/procfs/BUILD @@ -9,6 +9,7 @@ go_library( deps = [ "//pkg/abi/linux", "//pkg/context", + "//pkg/hostarch", "//pkg/log", "//pkg/sentry/fsimpl/proc", "//pkg/sentry/kernel", diff --git a/runsc/boot/procfs/dump.go b/runsc/boot/procfs/dump.go index e0fef61d1..9c002fc2c 100644 --- a/runsc/boot/procfs/dump.go +++ b/runsc/boot/procfs/dump.go @@ -23,6 +23,7 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/fsimpl/proc" "gvisor.dev/gvisor/pkg/sentry/kernel" @@ -65,6 +66,18 @@ type Stat struct { SID int32 `json:"sid"` } +// Mapping contains information for /proc/[pid]/maps. +type Mapping struct { + Address hostarch.AddrRange `json:"address,omitempty"` + Permissions hostarch.AccessType `json:"permissions"` + Private string `json:"private,omitempty"` + Offset uint64 `json:"offset"` + DevMajor uint32 `json:"deviceMajor,omitempty"` + DevMinor uint32 `json:"deviceMinor,omitempty"` + Inode uint64 `json:"inode,omitempty"` + Pathname string `json:"pathname,omitempty"` +} + // ProcessProcfsDump contains the procfs dump for one process. For more details // on fields that directly correspond to /proc fields, see proc(5). type ProcessProcfsDump struct { @@ -92,6 +105,8 @@ type ProcessProcfsDump struct { Status Status `json:"status,omitempty"` // Stat is /proc/[pid]/stat. Stat Stat `json:"stat,omitempty"` + // Maps is /proc/[pid]/maps. + Maps []Mapping `json:"maps,omitempty"` } // getMM returns t's MemoryManager. On success, the MemoryManager's users count @@ -251,6 +266,27 @@ func getStat(t *kernel.Task, pid kernel.ThreadID, pidns *kernel.PIDNamespace) St } } +func getMappings(ctx context.Context, mm *mm.MemoryManager) []Mapping { + var maps []Mapping + mm.ReadMapsDataInto(ctx, func(start, end hostarch.Addr, permissions hostarch.AccessType, private string, offset uint64, devMajor, devMinor uint32, inode uint64, path string) { + maps = append(maps, Mapping{ + Address: hostarch.AddrRange{ + Start: start, + End: end, + }, + Permissions: permissions, + Private: private, + Offset: offset, + DevMajor: devMajor, + DevMinor: devMinor, + Inode: inode, + Pathname: path, + }) + }) + + return maps +} + // Dump returns a procfs dump for process pid. t must be a task in process pid. func Dump(t *kernel.Task, pid kernel.ThreadID, pidns *kernel.PIDNamespace) (ProcessProcfsDump, error) { ctx := t.AsyncContext() @@ -282,5 +318,6 @@ func Dump(t *kernel.Task, pid kernel.ThreadID, pidns *kernel.PIDNamespace) (Proc Cgroup: t.GetCgroupEntries(), Status: getStatus(t, mm, pid, pidns), Stat: getStat(t, pid, pidns), + Maps: getMappings(ctx, mm), }, nil } diff --git a/runsc/container/trace_test.go b/runsc/container/trace_test.go index 9873ca415..369df613d 100644 --- a/runsc/container/trace_test.go +++ b/runsc/container/trace_test.go @@ -439,4 +439,14 @@ func TestProcfsDump(t *testing.T) { if procfsDump[0].Status.VMRSS == 0 { t.Errorf("expected VMSize to be set") } + if len(procfsDump[0].Maps) <= 0 { + t.Errorf("no region mapped for pid:%v", procfsDump[0].Status.PID) + } + + maps := procfsDump[0].Maps + for i := 0; i < len(procfsDump[0].Maps)-1; i++ { + if maps[i].Address.Overlaps(maps[i+1].Address) { + t.Errorf("overlapped addresses for pid:%v", procfsDump[0].Status.PID) + } + } }