Skip to content

Commit 561ff6d

Browse files
avagingvisor-bot
authored andcommitted
proc: fix security checks on /proc/pid files
This change updates the security checks to align with Linux kernel behavior: * Add ptrace_may_access-style checks (kernel.ContextCanTrace) when accessing /proc/[pid]/maps and /proc/[pid]/smaps. * Change the permissions of the /proc/[pid]/fd directory to 0500 (read/execute for owner only). * Add a similar kernel.ContextCanTrace check for /proc/[pid]/{environ,auxv}. PiperOrigin-RevId: 842001353
1 parent 2d946ce commit 561ff6d

File tree

7 files changed

+212
-53
lines changed

7 files changed

+212
-53
lines changed

pkg/sentry/fsimpl/cgroupfs/base.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,11 @@ func (c *cgroupInode) ReadControl(ctx context.Context, name string) (string, err
353353
}
354354

355355
var buf bytes.Buffer
356-
err = cbf.Source().Data().Generate(ctx, &buf)
356+
data, err := cbf.Source().Data(ctx)
357+
if err != nil {
358+
return "", err
359+
}
360+
err = data.Generate(ctx, &buf)
357361
return buf.String(), err
358362
}
359363

pkg/sentry/fsimpl/kernfs/dynamic_bytes_file.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ import (
2525
"gvisor.dev/gvisor/pkg/usermem"
2626
)
2727

28+
// DataSourceProvider represents a dynamic data source provider for DynamicBytesFile.
29+
type DataSourceProvider interface {
30+
DataSource(ctx context.Context) (vfs.DynamicBytesSource, error)
31+
}
32+
2833
// DynamicBytesFile implements kernfs.Inode and represents a read-only file
2934
// whose contents are backed by a vfs.DynamicBytesSource. If data additionally
3035
// implements vfs.WritableDynamicBytesSource, the file also supports dispatching
@@ -49,6 +54,10 @@ type DynamicBytesFile struct {
4954
// writes. This field cannot be changed to a different bytes source after
5055
// Init.
5156
data vfs.DynamicBytesSource
57+
58+
// dataSourceProvider is used to get the data source when the file is opened.
59+
// If it is nil, the data source is fixed to the value in the data field.
60+
dataSourceProvider DataSourceProvider
5261
}
5362

5463
var _ Inode = (*DynamicBytesFile)(nil)
@@ -64,8 +73,12 @@ func (f *DynamicBytesFile) Init(ctx context.Context, creds *auth.Credentials, de
6473

6574
// Open implements Inode.Open.
6675
func (f *DynamicBytesFile) Open(ctx context.Context, rp *vfs.ResolvingPath, d *Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
76+
data, err := f.Data(ctx)
77+
if err != nil {
78+
return nil, err
79+
}
6780
fd := &DynamicBytesFD{}
68-
if err := fd.Init(rp.Mount(), d, f.data, &f.locks, opts.Flags); err != nil {
81+
if err := fd.Init(rp.Mount(), d, data, &f.locks, opts.Flags); err != nil {
6982
return nil, err
7083
}
7184
return &fd.vfsfd, nil
@@ -83,9 +96,17 @@ func (f *DynamicBytesFile) Locks() *vfs.FileLocks {
8396
return &f.locks
8497
}
8598

99+
// SetDataSourceProvider sets a DataSourceProvider for the file.
100+
func (f *DynamicBytesFile) SetDataSourceProvider(p DataSourceProvider) {
101+
f.dataSourceProvider = p
102+
}
103+
86104
// Data returns the underlying data source.
87-
func (f *DynamicBytesFile) Data() vfs.DynamicBytesSource {
88-
return f.data
105+
func (f *DynamicBytesFile) Data(ctx context.Context) (vfs.DynamicBytesSource, error) {
106+
if f.dataSourceProvider != nil {
107+
return f.dataSourceProvider.DataSource(ctx)
108+
}
109+
return f.data, nil
89110
}
90111

91112
// DynamicBytesFD implements vfs.FileDescriptionImpl for an FD backed by a

pkg/sentry/fsimpl/proc/task.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,18 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns
5959
}
6060

6161
contents := map[string]kernfs.Inode{
62-
"auxv": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &auxvData{task: task}),
63-
"cmdline": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &metadataData{task: task, metaType: Cmdline}),
62+
"auxv": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0400, &mmFile{task: task, ftype: auxvMMFile}),
63+
"cmdline": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &cmdlineData{task: task}),
6464
"comm": fs.newComm(ctx, task, fs.NextIno(), 0644),
6565
"cwd": fs.newCwdSymlink(ctx, task, fs.NextIno()),
66-
"environ": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &metadataData{task: task, metaType: Environ}),
66+
"environ": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0400, &mmFile{task: task, ftype: environMMFile}),
6767
"exe": fs.newExeSymlink(ctx, task, fs.NextIno()),
6868
"fd": fs.newFDDirInode(ctx, task),
6969
"fdinfo": fs.newFDInfoDirInode(ctx, task),
7070
"gid_map": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0644, &idMapData{task: task, gids: true}),
7171
"io": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0400, newIO(task, isThreadGroup)),
7272
"limits": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &limitsData{task: task}),
73-
"maps": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &mapsData{task: task}),
73+
"maps": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &mmFile{task: task, ftype: mapsMMFile}),
7474
"mem": fs.newMemInode(ctx, task, fs.NextIno(), 0600),
7575
"mountinfo": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &mountInfoData{fs: fs, task: task}),
7676
"mounts": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &mountsData{fs: fs, task: task}),
@@ -86,7 +86,7 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns
8686
"oom_score": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, newStaticFile("0\n")),
8787
"oom_score_adj": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0644, &oomScoreAdj{task: task}),
8888
"root": fs.newRootSymlink(ctx, task, fs.NextIno()),
89-
"smaps": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &smapsData{task: task}),
89+
"smaps": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &mmFile{task: task, ftype: smapsMMFile}),
9090
"stat": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &taskStatData{task: task, pidns: pidns, tgstats: isThreadGroup}),
9191
"statm": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &statmData{task: task}),
9292
"status": fs.newStatusInode(ctx, task, pidns, fs.NextIno(), 0444),
@@ -248,7 +248,7 @@ func (i *taskOwnedInode) Stat(ctx context.Context, fs *vfs.Filesystem, opts vfs.
248248
}
249249

250250
// CheckPermissions implements kernfs.Inode.CheckPermissions.
251-
func (i *taskOwnedInode) CheckPermissions(_ context.Context, creds *auth.Credentials, ats vfs.AccessTypes) error {
251+
func (i *taskOwnedInode) CheckPermissions(ctx context.Context, creds *auth.Credentials, ats vfs.AccessTypes) error {
252252
mode := i.Mode()
253253
uid, gid := i.getOwner(mode)
254254
return vfs.GenericCheckPermissions(creds, ats, mode, uid, gid)

pkg/sentry/fsimpl/proc/task_fds.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func (fs *filesystem) newFDDirInode(ctx context.Context, task *kernel.Task) kern
131131
produceSymlink: true,
132132
},
133133
}
134-
inode.InodeAttrs.Init(ctx, task.Credentials(), linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.ModeDirectory|0555)
134+
inode.InodeAttrs.Init(ctx, task.Credentials(), linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.ModeDirectory|0500)
135135
inode.InitRefs()
136136
inode.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
137137
return inode
@@ -223,6 +223,9 @@ func (fs *filesystem) newFDSymlink(ctx context.Context, task *kernel.Task, fd in
223223
}
224224

225225
func (s *fdSymlink) Readlink(ctx context.Context, _ *vfs.Mount) (string, error) {
226+
if !kernel.ContextCanTrace(ctx, s.task, false) {
227+
return "", linuxerr.EACCES
228+
}
226229
file, _ := getTaskFD(s.task, s.fd)
227230
if file == nil {
228231
return "", linuxerr.ENOENT
@@ -236,6 +239,9 @@ func (s *fdSymlink) Readlink(ctx context.Context, _ *vfs.Mount) (string, error)
236239
}
237240

238241
func (s *fdSymlink) Getlink(ctx context.Context, mnt *vfs.Mount) (vfs.VirtualDentry, string, error) {
242+
if !kernel.ContextCanTrace(ctx, s.task, false) {
243+
return vfs.VirtualDentry{}, "", linuxerr.EACCES
244+
}
239245
file, _ := getTaskFD(s.task, s.fd)
240246
if file == nil {
241247
return vfs.VirtualDentry{}, "", linuxerr.ENOENT

pkg/sentry/fsimpl/proc/task_files.go

Lines changed: 111 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -102,20 +102,13 @@ func (w *bufferWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) {
102102
//
103103
// +stateify savable
104104
type auxvData struct {
105-
kernfs.DynamicBytesFile
106-
107-
task *kernel.Task
105+
mm *mm.MemoryManager
108106
}
109107

110-
var _ dynamicInode = (*auxvData)(nil)
111-
112108
// Generate implements vfs.DynamicBytesSource.Generate.
113109
func (d *auxvData) Generate(ctx context.Context, buf *bytes.Buffer) error {
114-
if d.task.ExitState() == kernel.TaskExitDead {
115-
return linuxerr.ESRCH
116-
}
117-
m, err := getMMIncRef(d.task)
118-
if err != nil {
110+
m := d.mm
111+
if m == nil || !m.IncUsers() {
119112
// Return empty file.
120113
return nil
121114
}
@@ -228,25 +221,19 @@ func GetMetadata(ctx context.Context, mm *mm.MemoryManager, buf *bytes.Buffer, t
228221
return nil
229222
}
230223

231-
// metadataData implements vfs.DynamicBytesSource for proc metadata fields like:
232-
//
233-
// - /proc/[pid]/cmdline
234-
// - /proc/[pid]/environ
224+
// cmdlineData implements vfs.DynamicBytesSource for /proc/[pid]/cmdline:
235225
//
236226
// +stateify savable
237-
type metadataData struct {
227+
type cmdlineData struct {
238228
kernfs.DynamicBytesFile
239229

240230
task *kernel.Task
241-
242-
// arg is the type of exec argument this file contains.
243-
metaType MetadataType
244231
}
245232

246-
var _ dynamicInode = (*metadataData)(nil)
233+
var _ dynamicInode = (*cmdlineData)(nil)
247234

248235
// Generate implements vfs.DynamicBytesSource.Generate.
249-
func (d *metadataData) Generate(ctx context.Context, buf *bytes.Buffer) error {
236+
func (d *cmdlineData) Generate(ctx context.Context, buf *bytes.Buffer) error {
250237
if d.task.ExitState() == kernel.TaskExitDead {
251238
return linuxerr.ESRCH
252239
}
@@ -256,7 +243,25 @@ func (d *metadataData) Generate(ctx context.Context, buf *bytes.Buffer) error {
256243
return nil
257244
}
258245
defer m.DecUsers(ctx)
259-
return GetMetadata(ctx, m, buf, d.metaType)
246+
return GetMetadata(ctx, m, buf, Cmdline)
247+
}
248+
249+
// environData implements vfs.DynamicBytesSource for /proc/[pid]/environ:
250+
//
251+
// +stateify savable
252+
type environData struct {
253+
mm *mm.MemoryManager
254+
}
255+
256+
// Generate implements vfs.DynamicBytesSource.Generate.
257+
func (d *environData) Generate(ctx context.Context, buf *bytes.Buffer) error {
258+
m := d.mm
259+
if m == nil || !m.IncUsers() {
260+
// Return empty file.
261+
return nil
262+
}
263+
defer m.DecUsers(ctx)
264+
return GetMetadata(ctx, m, buf, Environ)
260265
}
261266

262267
// +stateify savable
@@ -444,11 +449,20 @@ func (f *memInode) init(ctx context.Context, creds *auth.Credentials, devMajor,
444449

445450
// Open implements kernfs.Inode.Open.
446451
func (f *memInode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
447-
// TODO(gvisor.dev/issue/260): Add check for PTRACE_MODE_ATTACH_FSCREDS
448-
// Permission to read this file is governed by PTRACE_MODE_ATTACH_FSCREDS
449-
// Since we dont implement setfsuid/setfsgid we can just use PTRACE_MODE_ATTACH
450-
if !kernel.ContextCanTrace(ctx, f.task, true) {
451-
return nil, linuxerr.EACCES
452+
m := getMM(f.task)
453+
for {
454+
// TODO(gvisor.dev/issue/260): Add check for PTRACE_MODE_ATTACH_FSCREDS
455+
// Permission to read this file is governed by PTRACE_MODE_ATTACH_FSCREDS
456+
// Since we dont implement setfsuid/setfsgid we can just use PTRACE_MODE_ATTACH
457+
if !kernel.ContextCanTrace(ctx, f.task, true) {
458+
return nil, linuxerr.EACCES
459+
}
460+
// Check that the task still has the same mm (hasn't called execve).
461+
m2 := getMM(f.task)
462+
if m == m2 {
463+
break
464+
}
465+
m = m2
452466
}
453467
if err := checkTaskState(f.task); err != nil {
454468
return nil, err
@@ -624,7 +638,6 @@ func (d *limitsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
624638
} else {
625639
fmt.Fprintf(buf, "%-20d ", l.Max)
626640
}
627-
628641
if u := lt.Unit(); u != "" {
629642
fmt.Fprintf(buf, "%-10s", u)
630643
}
@@ -634,41 +647,99 @@ func (d *limitsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
634647
return nil
635648
}
636649

637-
// mapsData implements vfs.DynamicBytesSource for /proc/[pid]/maps.
650+
// mmFile implements vfs.DynamicBytesSource for files that links with a process mm.
638651
//
639652
// +stateify savable
640-
type mapsData struct {
653+
type mmFile struct {
641654
kernfs.DynamicBytesFile
642655

643-
task *kernel.Task
656+
ftype mmFileType
657+
task *kernel.Task
644658
}
645659

646-
var _ dynamicInode = (*mapsData)(nil)
660+
type mmFileType int
661+
662+
const (
663+
mapsMMFile mmFileType = iota
664+
smapsMMFile
665+
auxvMMFile
666+
environMMFile
667+
cmdlineMMFile
668+
)
669+
670+
var _ dynamicInode = (*mmFile)(nil)
671+
672+
func (f *mmFile) Init(ctx context.Context, creds *auth.Credentials, devMajor, devMinor uint32, ino uint64, data vfs.DynamicBytesSource, perm linux.FileMode) {
673+
f.DynamicBytesFile.Init(ctx, creds, devMajor, devMinor, ino, nil, perm)
674+
f.SetDataSourceProvider(f)
675+
}
676+
677+
// DataSource implements kernfs.DataSourceProvider.DataSource()
678+
func (f *mmFile) DataSource(ctx context.Context) (vfs.DynamicBytesSource, error) {
679+
m := getMM(f.task)
680+
for {
681+
if !kernel.ContextCanTrace(ctx, f.task, false) {
682+
return nil, linuxerr.EACCES
683+
}
684+
// Check that the task still has the same mm (hasn't called execve).
685+
m2 := getMM(f.task)
686+
if m == m2 {
687+
break
688+
}
689+
m = m2
690+
}
691+
switch f.ftype {
692+
case mapsMMFile:
693+
return &mapsData{mm: m}, nil
694+
case smapsMMFile:
695+
return &smapsData{mm: m}, nil
696+
case environMMFile:
697+
return &environData{mm: m}, nil
698+
case auxvMMFile:
699+
return &auxvData{mm: m}, nil
700+
default:
701+
panic(fmt.Sprintf("unknown file type: %d", f.ftype))
702+
}
703+
}
704+
705+
var _ kernfs.DataSourceProvider = (*mmFile)(nil)
706+
707+
// Generate implements vfs.DynamicBytesSource.Generate.
708+
func (f *mmFile) Generate(ctx context.Context, buf *bytes.Buffer) error {
709+
panic("unreachable")
710+
}
711+
712+
// mapsData implements vfs.DynamicBytesSource for /proc/[pid]/maps.
713+
//
714+
// +stateify savable
715+
type mapsData struct {
716+
mm *mm.MemoryManager
717+
}
647718

648719
// Generate implements vfs.DynamicBytesSource.Generate.
649720
func (d *mapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
650-
if mm := getMM(d.task); mm != nil {
651-
mm.ReadMapsDataInto(ctx, mm.MapsCallbackFuncForBuffer(buf))
721+
if d.mm == nil || !d.mm.IncUsers() {
722+
return nil
652723
}
724+
defer d.mm.DecUsers(ctx)
725+
d.mm.ReadMapsDataInto(ctx, d.mm.MapsCallbackFuncForBuffer(buf))
653726
return nil
654727
}
655728

656729
// smapsData implements vfs.DynamicBytesSource for /proc/[pid]/smaps.
657730
//
658731
// +stateify savable
659732
type smapsData struct {
660-
kernfs.DynamicBytesFile
661-
662-
task *kernel.Task
733+
mm *mm.MemoryManager
663734
}
664735

665-
var _ dynamicInode = (*smapsData)(nil)
666-
667736
// Generate implements vfs.DynamicBytesSource.Generate.
668737
func (d *smapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
669-
if mm := getMM(d.task); mm != nil {
670-
mm.ReadSmapsDataInto(ctx, buf)
738+
if d.mm == nil || !d.mm.IncUsers() {
739+
return nil
671740
}
741+
defer d.mm.DecUsers(ctx)
742+
d.mm.ReadSmapsDataInto(ctx, buf)
672743
return nil
673744
}
674745

pkg/sentry/kernel/ptrace.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func (t *Task) CanTrace(target *Task, attach bool) bool {
123123
return false
124124
}
125125

126-
if t.k.YAMAPtraceScope.Load() == linux.YAMA_SCOPE_RELATIONAL {
126+
if attach && t.k.YAMAPtraceScope.Load() == linux.YAMA_SCOPE_RELATIONAL {
127127
t.tg.pidns.owner.mu.RLock()
128128
defer t.tg.pidns.owner.mu.RUnlock()
129129
if !t.canTraceYAMALocked(target) {
@@ -144,7 +144,7 @@ func (t *Task) canTraceLocked(target *Task, attach bool) bool {
144144
return false
145145
}
146146

147-
if t.k.YAMAPtraceScope.Load() == linux.YAMA_SCOPE_RELATIONAL {
147+
if attach && t.k.YAMAPtraceScope.Load() == linux.YAMA_SCOPE_RELATIONAL {
148148
if !t.canTraceYAMALocked(target) {
149149
return false
150150
}

0 commit comments

Comments
 (0)