mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add Uid/Gid/Groups fields to VFS2 /proc/[pid]/status.
For comparison: ``` $ docker run --rm -it ubuntu:focal bash -c 'cat /proc/self/status' Name: cat Umask: 0022 State: R (running) Tgid: 1 Ngid: 0 Pid: 1 PPid: 0 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 64 Groups: NStgid: 1 NSpid: 1 NSpgid: 1 NSsid: 1 VmPeak: 2660 kB VmSize: 2660 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 528 kB VmRSS: 528 kB ... $ docker run --runtime=runsc-vfs2 --rm -it ubuntu:focal bash -c 'cat /proc/self/status' Name: cat State: R (running) Tgid: 1 Pid: 1 PPid: 0 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 4 Groups: VmSize: 10708 kB VmRSS: 3124 kB VmData: 316 kB ... ``` Fixes #6374 PiperOrigin-RevId: 387465655
This commit is contained in:
@@ -78,7 +78,7 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns
|
||||
"smaps": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &smapsData{task: task}),
|
||||
"stat": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &taskStatData{task: task, pidns: pidns, tgstats: isThreadGroup}),
|
||||
"statm": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &statmData{task: task}),
|
||||
"status": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &statusData{task: task, pidns: pidns}),
|
||||
"status": fs.newStatusInode(ctx, task, pidns, fs.NextIno(), 0444),
|
||||
"uid_map": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0644, &idMapData{task: task, gids: false}),
|
||||
}
|
||||
if isThreadGroup {
|
||||
|
||||
@@ -661,34 +661,119 @@ func (s *statmData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// statusData implements vfs.DynamicBytesSource for /proc/[pid]/status.
|
||||
// statusInode implements kernfs.Inode for /proc/[pid]/status.
|
||||
//
|
||||
// +stateify savable
|
||||
type statusData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
type statusInode struct {
|
||||
kernfs.InodeAttrs
|
||||
kernfs.InodeNoStatFS
|
||||
kernfs.InodeNoopRefCount
|
||||
kernfs.InodeNotDirectory
|
||||
kernfs.InodeNotSymlink
|
||||
|
||||
task *kernel.Task
|
||||
pidns *kernel.PIDNamespace
|
||||
locks vfs.FileLocks
|
||||
}
|
||||
|
||||
var _ dynamicInode = (*statusData)(nil)
|
||||
// statusFD implements vfs.FileDescriptionImpl and vfs.DynamicByteSource for
|
||||
// /proc/[pid]/status.
|
||||
//
|
||||
// +stateify savable
|
||||
type statusFD struct {
|
||||
statusFDLowerBase
|
||||
vfs.DynamicBytesFileDescriptionImpl
|
||||
vfs.LockFD
|
||||
|
||||
vfsfd vfs.FileDescription
|
||||
|
||||
inode *statusInode
|
||||
task *kernel.Task
|
||||
pidns *kernel.PIDNamespace
|
||||
userns *auth.UserNamespace // equivalent to struct file::f_cred::user_ns
|
||||
}
|
||||
|
||||
// statusFDLowerBase is a dumb hack to ensure that statusFD prefers
|
||||
// vfs.DynamicBytesFileDescriptionImpl methods to vfs.FileDescriptinDefaultImpl
|
||||
// methods.
|
||||
//
|
||||
// +stateify savable
|
||||
type statusFDLowerBase struct {
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
}
|
||||
|
||||
func (fs *filesystem) newStatusInode(ctx context.Context, task *kernel.Task, pidns *kernel.PIDNamespace, ino uint64, perm linux.FileMode) kernfs.Inode {
|
||||
// Note: credentials are overridden by taskOwnedInode.
|
||||
inode := &statusInode{
|
||||
task: task,
|
||||
pidns: pidns,
|
||||
}
|
||||
inode.InodeAttrs.Init(ctx, task.Credentials(), linux.UNNAMED_MAJOR, fs.devMinor, ino, linux.ModeRegular|perm)
|
||||
return &taskOwnedInode{Inode: inode, owner: task}
|
||||
}
|
||||
|
||||
// Open implements kernfs.Inode.Open.
|
||||
func (s *statusInode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &statusFD{
|
||||
inode: s,
|
||||
task: s.task,
|
||||
pidns: s.pidns,
|
||||
userns: rp.Credentials().UserNamespace,
|
||||
}
|
||||
fd.LockFD.Init(&s.locks)
|
||||
if err := fd.vfsfd.Init(fd, opts.Flags, rp.Mount(), d.VFSDentry(), &vfs.FileDescriptionOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd.SetDataSource(fd)
|
||||
return &fd.vfsfd, nil
|
||||
}
|
||||
|
||||
// SetStat implements kernfs.Inode.SetStat.
|
||||
func (*statusInode) SetStat(ctx context.Context, vfsfs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
|
||||
return linuxerr.EPERM
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (s *statusFD) Release(ctx context.Context) {
|
||||
}
|
||||
|
||||
// Stat implements vfs.FileDescriptionImpl.Stat.
|
||||
func (s *statusFD) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
fs := s.vfsfd.VirtualDentry().Mount().Filesystem()
|
||||
return s.inode.Stat(ctx, fs, opts)
|
||||
}
|
||||
|
||||
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
||||
func (s *statusFD) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
return linuxerr.EPERM
|
||||
}
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (s *statusData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
func (s *statusFD) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
fmt.Fprintf(buf, "Name:\t%s\n", s.task.Name())
|
||||
fmt.Fprintf(buf, "State:\t%s\n", s.task.StateStatus())
|
||||
fmt.Fprintf(buf, "Tgid:\t%d\n", s.pidns.IDOfThreadGroup(s.task.ThreadGroup()))
|
||||
fmt.Fprintf(buf, "Pid:\t%d\n", s.pidns.IDOfTask(s.task))
|
||||
|
||||
ppid := kernel.ThreadID(0)
|
||||
if parent := s.task.Parent(); parent != nil {
|
||||
ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup())
|
||||
}
|
||||
fmt.Fprintf(buf, "PPid:\t%d\n", ppid)
|
||||
|
||||
tpid := kernel.ThreadID(0)
|
||||
if tracer := s.task.Tracer(); tracer != nil {
|
||||
tpid = s.pidns.IDOfTask(tracer)
|
||||
}
|
||||
fmt.Fprintf(buf, "TracerPid:\t%d\n", tpid)
|
||||
|
||||
creds := s.task.Credentials()
|
||||
ruid := creds.RealKUID.In(s.userns).OrOverflow()
|
||||
euid := creds.EffectiveKUID.In(s.userns).OrOverflow()
|
||||
suid := creds.SavedKUID.In(s.userns).OrOverflow()
|
||||
rgid := creds.RealKGID.In(s.userns).OrOverflow()
|
||||
egid := creds.EffectiveKGID.In(s.userns).OrOverflow()
|
||||
sgid := creds.SavedKGID.In(s.userns).OrOverflow()
|
||||
var fds int
|
||||
var vss, rss, data uint64
|
||||
s.task.WithMuLocked(func(t *kernel.Task) {
|
||||
@@ -701,12 +786,26 @@ func (s *statusData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
data = mm.VirtualDataSize()
|
||||
}
|
||||
})
|
||||
// Filesystem user/group IDs aren't implemented; effective UID/GID are used
|
||||
// instead.
|
||||
fmt.Fprintf(buf, "Uid:\t%d\t%d\t%d\t%d\n", ruid, euid, suid, euid)
|
||||
fmt.Fprintf(buf, "Gid:\t%d\t%d\t%d\t%d\n", rgid, egid, sgid, egid)
|
||||
fmt.Fprintf(buf, "FDSize:\t%d\n", fds)
|
||||
buf.WriteString("Groups:\t ")
|
||||
// There is a space between each pair of supplemental GIDs, as well as an
|
||||
// unconditional trailing space that some applications actually depend on.
|
||||
var sep string
|
||||
for _, kgid := range creds.ExtraKGIDs {
|
||||
fmt.Fprintf(buf, "%s%d", sep, kgid.In(s.userns).OrOverflow())
|
||||
sep = " "
|
||||
}
|
||||
buf.WriteString(" \n")
|
||||
|
||||
fmt.Fprintf(buf, "VmSize:\t%d kB\n", vss>>10)
|
||||
fmt.Fprintf(buf, "VmRSS:\t%d kB\n", rss>>10)
|
||||
fmt.Fprintf(buf, "VmData:\t%d kB\n", data>>10)
|
||||
|
||||
fmt.Fprintf(buf, "Threads:\t%d\n", s.task.ThreadGroup().Count())
|
||||
creds := s.task.Credentials()
|
||||
fmt.Fprintf(buf, "CapInh:\t%016x\n", creds.InheritableCaps)
|
||||
fmt.Fprintf(buf, "CapPrm:\t%016x\n", creds.PermittedCaps)
|
||||
fmt.Fprintf(buf, "CapEff:\t%016x\n", creds.EffectiveCaps)
|
||||
|
||||
@@ -1750,6 +1750,7 @@ cc_binary(
|
||||
"//test/util:mount_util",
|
||||
"@com_google_absl//absl/container:node_hash_set",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"@com_google_absl//absl/time",
|
||||
gtest,
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
@@ -88,6 +90,7 @@ using ::testing::Gt;
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::IsSupersetOf;
|
||||
using ::testing::Pair;
|
||||
using ::testing::StartsWith;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
using ::testing::UnorderedElementsAreArray;
|
||||
|
||||
@@ -1622,10 +1625,41 @@ TEST(ProcPidStatusTest, HasBasicFields) {
|
||||
|
||||
ASSERT_FALSE(status_str.empty());
|
||||
const auto status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(status_str));
|
||||
EXPECT_THAT(status, IsSupersetOf({Pair("Name", thread_name),
|
||||
Pair("Tgid", absl::StrCat(tgid)),
|
||||
Pair("Pid", absl::StrCat(tid)),
|
||||
Pair("PPid", absl::StrCat(getppid()))}));
|
||||
EXPECT_THAT(status, IsSupersetOf({
|
||||
Pair("Name", thread_name),
|
||||
Pair("Tgid", absl::StrCat(tgid)),
|
||||
Pair("Pid", absl::StrCat(tid)),
|
||||
Pair("PPid", absl::StrCat(getppid())),
|
||||
}));
|
||||
|
||||
if (!IsRunningWithVFS1()) {
|
||||
uid_t ruid, euid, suid;
|
||||
ASSERT_THAT(getresuid(&ruid, &euid, &suid), SyscallSucceeds());
|
||||
gid_t rgid, egid, sgid;
|
||||
ASSERT_THAT(getresgid(&rgid, &egid, &sgid), SyscallSucceeds());
|
||||
std::vector<gid_t> supplementary_gids;
|
||||
int ngids = getgroups(0, nullptr);
|
||||
supplementary_gids.resize(ngids);
|
||||
ASSERT_THAT(getgroups(ngids, supplementary_gids.data()),
|
||||
SyscallSucceeds());
|
||||
|
||||
EXPECT_THAT(
|
||||
status,
|
||||
IsSupersetOf(std::vector<
|
||||
::testing::Matcher<std::pair<std::string, std::string>>>{
|
||||
// gVisor doesn't support fsuid/gid, and even if it did there is
|
||||
// no getfsuid/getfsgid().
|
||||
Pair("Uid", StartsWith(absl::StrFormat("%d\t%d\t%d\t", ruid, euid,
|
||||
suid))),
|
||||
Pair("Gid", StartsWith(absl::StrFormat("%d\t%d\t%d\t", rgid, egid,
|
||||
sgid))),
|
||||
// ParseProcStatus strips leading whitespace for each value,
|
||||
// so if the Groups line is empty then the trailing space is
|
||||
// stripped.
|
||||
Pair("Groups",
|
||||
StartsWith(absl::StrJoin(supplementary_gids, " "))),
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user