gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/proc/tasks_test.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package proc
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  	"path"
    21  	"strconv"
    22  	"testing"
    23  
    24  	"gvisor.dev/gvisor/pkg/abi/linux"
    25  	"gvisor.dev/gvisor/pkg/context"
    26  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    27  	"gvisor.dev/gvisor/pkg/fspath"
    28  	"gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
    29  	"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
    30  	"gvisor.dev/gvisor/pkg/sentry/kernel"
    31  	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
    32  	"gvisor.dev/gvisor/pkg/sentry/vfs"
    33  	"gvisor.dev/gvisor/pkg/usermem"
    34  )
    35  
    36  var (
    37  	// Next offset 256 by convention. Adds 1 for the next offset.
    38  	selfLink       = vfs.Dirent{Type: linux.DT_LNK, NextOff: 256 + 0 + 1}
    39  	threadSelfLink = vfs.Dirent{Type: linux.DT_LNK, NextOff: 256 + 1 + 1}
    40  
    41  	// /proc/[pid] next offset starts at 256+2 (files above), then adds the
    42  	// PID, and adds 1 for the next offset.
    43  	proc1 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 1 + 1}
    44  	proc2 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 2 + 1}
    45  	proc3 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 3 + 1}
    46  )
    47  
    48  var (
    49  	tasksStaticFiles = map[string]testutil.DirentType{
    50  		"bus":            linux.DT_DIR,
    51  		"cmdline":        linux.DT_REG,
    52  		"cpuinfo":        linux.DT_REG,
    53  		"filesystems":    linux.DT_REG,
    54  		"fs":             linux.DT_DIR,
    55  		"irq":            linux.DT_DIR,
    56  		"loadavg":        linux.DT_REG,
    57  		"meminfo":        linux.DT_REG,
    58  		"mounts":         linux.DT_LNK,
    59  		"net":            linux.DT_LNK,
    60  		"self":           linux.DT_LNK,
    61  		"sentry-meminfo": linux.DT_REG,
    62  		"stat":           linux.DT_REG,
    63  		"sys":            linux.DT_DIR,
    64  		"sysrq-trigger":  linux.DT_REG,
    65  		"thread-self":    linux.DT_LNK,
    66  		"uptime":         linux.DT_REG,
    67  		"version":        linux.DT_REG,
    68  	}
    69  	tasksStaticFilesNextOffs = map[string]int64{
    70  		"self":        selfLink.NextOff,
    71  		"thread-self": threadSelfLink.NextOff,
    72  	}
    73  	taskStaticFiles = map[string]testutil.DirentType{
    74  		"auxv":          linux.DT_REG,
    75  		"cgroup":        linux.DT_REG,
    76  		"cwd":           linux.DT_LNK,
    77  		"cmdline":       linux.DT_REG,
    78  		"comm":          linux.DT_REG,
    79  		"environ":       linux.DT_REG,
    80  		"exe":           linux.DT_LNK,
    81  		"fd":            linux.DT_DIR,
    82  		"fdinfo":        linux.DT_DIR,
    83  		"gid_map":       linux.DT_REG,
    84  		"io":            linux.DT_REG,
    85  		"limits":        linux.DT_REG,
    86  		"maps":          linux.DT_REG,
    87  		"mem":           linux.DT_REG,
    88  		"mountinfo":     linux.DT_REG,
    89  		"mounts":        linux.DT_REG,
    90  		"net":           linux.DT_DIR,
    91  		"ns":            linux.DT_DIR,
    92  		"oom_score":     linux.DT_REG,
    93  		"oom_score_adj": linux.DT_REG,
    94  		"root":          linux.DT_LNK,
    95  		"smaps":         linux.DT_REG,
    96  		"stat":          linux.DT_REG,
    97  		"statm":         linux.DT_REG,
    98  		"status":        linux.DT_REG,
    99  		"task":          linux.DT_DIR,
   100  		"uid_map":       linux.DT_REG,
   101  	}
   102  )
   103  
   104  func setup(t *testing.T) *testutil.System {
   105  	k, err := testutil.Boot()
   106  	if err != nil {
   107  		t.Fatalf("Error creating kernel: %v", err)
   108  	}
   109  
   110  	ctx := k.SupervisorContext()
   111  	creds := auth.CredentialsFromContext(ctx)
   112  
   113  	k.VFS().MustRegisterFilesystemType(Name, &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
   114  		AllowUserMount: true,
   115  	})
   116  
   117  	mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", tmpfs.Name, &vfs.MountOptions{}, k)
   118  	if err != nil {
   119  		t.Fatalf("NewMountNamespace(): %v", err)
   120  	}
   121  	root := mntns.Root(ctx)
   122  	defer root.DecRef(ctx)
   123  	pop := &vfs.PathOperation{
   124  		Root:  root,
   125  		Start: root,
   126  		Path:  fspath.Parse("/proc"),
   127  	}
   128  	if err := k.VFS().MkdirAt(ctx, creds, pop, &vfs.MkdirOptions{Mode: 0777}); err != nil {
   129  		t.Fatalf("MkDir(/proc): %v", err)
   130  	}
   131  
   132  	pop = &vfs.PathOperation{
   133  		Root:  root,
   134  		Start: root,
   135  		Path:  fspath.Parse("/proc"),
   136  	}
   137  	mntOpts := &vfs.MountOptions{
   138  		GetFilesystemOptions: vfs.GetFilesystemOptions{
   139  			InternalData: &InternalData{
   140  				Cgroups: map[string]string{
   141  					"cpuset": "/foo/cpuset",
   142  					"memory": "/foo/memory",
   143  				},
   144  			},
   145  		},
   146  	}
   147  	if _, err := k.VFS().MountAt(ctx, creds, "", pop, Name, mntOpts); err != nil {
   148  		t.Fatalf("MountAt(/proc): %v", err)
   149  	}
   150  	return testutil.NewSystem(ctx, t, k.VFS(), mntns)
   151  }
   152  
   153  func TestTasksEmpty(t *testing.T) {
   154  	s := setup(t)
   155  	defer s.Destroy()
   156  
   157  	collector := s.ListDirents(s.PathOpAtRoot("/proc"))
   158  	s.AssertAllDirentTypes(collector, tasksStaticFiles)
   159  	s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs)
   160  }
   161  
   162  func TestTasks(t *testing.T) {
   163  	s := setup(t)
   164  	defer s.Destroy()
   165  
   166  	expectedDirents := make(map[string]testutil.DirentType)
   167  	for n, d := range tasksStaticFiles {
   168  		expectedDirents[n] = d
   169  	}
   170  
   171  	k := kernel.KernelFromContext(s.Ctx)
   172  	var tasks []*kernel.Task
   173  	for i := 0; i < 5; i++ {
   174  		tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
   175  		task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root)
   176  		if err != nil {
   177  			t.Fatalf("CreateTask(): %v", err)
   178  		}
   179  		tasks = append(tasks, task)
   180  		expectedDirents[fmt.Sprintf("%d", i+1)] = linux.DT_DIR
   181  	}
   182  
   183  	collector := s.ListDirents(s.PathOpAtRoot("/proc"))
   184  	s.AssertAllDirentTypes(collector, expectedDirents)
   185  	s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs)
   186  
   187  	lastPid := 0
   188  	dirents := collector.OrderedDirents()
   189  	doneSkippingNonTaskDirs := false
   190  	for _, d := range dirents {
   191  		pid, err := strconv.Atoi(d.Name)
   192  		if err != nil {
   193  			if !doneSkippingNonTaskDirs {
   194  				// We haven't gotten to the task dirs yet.
   195  				continue
   196  			}
   197  			t.Fatalf("Invalid process directory %q", d.Name)
   198  		}
   199  		doneSkippingNonTaskDirs = true
   200  		if lastPid > pid {
   201  			t.Errorf("pids not in order: %v", dirents)
   202  		}
   203  		found := false
   204  		for _, t := range tasks {
   205  			if k.TaskSet().Root.IDOfTask(t) == kernel.ThreadID(pid) {
   206  				found = true
   207  			}
   208  		}
   209  		if !found {
   210  			t.Errorf("Additional task ID %d listed: %v", pid, tasks)
   211  		}
   212  		// Next offset starts at 256+2 ('self' and 'thread-self'), then adds the
   213  		// PID, and adds 1 for the next offset.
   214  		if want := int64(256 + 2 + pid + 1); d.NextOff != want {
   215  			t.Errorf("Wrong dirent offset want: %d got: %d: %+v", want, d.NextOff, d)
   216  		}
   217  	}
   218  	if !doneSkippingNonTaskDirs {
   219  		t.Fatalf("Never found any process directories.")
   220  	}
   221  
   222  	// Test lookup.
   223  	for _, path := range []string{"/proc/1", "/proc/2"} {
   224  		fd, err := s.VFS.OpenAt(
   225  			s.Ctx,
   226  			s.Creds,
   227  			s.PathOpAtRoot(path),
   228  			&vfs.OpenOptions{},
   229  		)
   230  		if err != nil {
   231  			t.Fatalf("vfsfs.OpenAt(%q) failed: %v", path, err)
   232  		}
   233  		defer fd.DecRef(s.Ctx)
   234  		buf := make([]byte, 1)
   235  		bufIOSeq := usermem.BytesIOSequence(buf)
   236  		if _, err := fd.Read(s.Ctx, bufIOSeq, vfs.ReadOptions{}); !linuxerr.Equals(linuxerr.EISDIR, err) {
   237  			t.Errorf("wrong error reading directory: %v", err)
   238  		}
   239  	}
   240  
   241  	if _, err := s.VFS.OpenAt(
   242  		s.Ctx,
   243  		s.Creds,
   244  		s.PathOpAtRoot("/proc/9999"),
   245  		&vfs.OpenOptions{},
   246  	); !linuxerr.Equals(linuxerr.ENOENT, err) {
   247  		t.Fatalf("wrong error from vfsfs.OpenAt(/proc/9999): %v", err)
   248  	}
   249  }
   250  
   251  func TestTasksOffset(t *testing.T) {
   252  	s := setup(t)
   253  	defer s.Destroy()
   254  
   255  	k := kernel.KernelFromContext(s.Ctx)
   256  	for i := 0; i < 3; i++ {
   257  		tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
   258  		if _, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root); err != nil {
   259  			t.Fatalf("CreateTask(): %v", err)
   260  		}
   261  	}
   262  
   263  	for _, tc := range []struct {
   264  		name   string
   265  		offset int64
   266  		wants  map[string]vfs.Dirent
   267  	}{
   268  		{
   269  			name:   "small offset",
   270  			offset: 100,
   271  			wants: map[string]vfs.Dirent{
   272  				"self":        selfLink,
   273  				"thread-self": threadSelfLink,
   274  				"1":           proc1,
   275  				"2":           proc2,
   276  				"3":           proc3,
   277  			},
   278  		},
   279  		{
   280  			name:   "offset at start",
   281  			offset: 256,
   282  			wants: map[string]vfs.Dirent{
   283  				"self":        selfLink,
   284  				"thread-self": threadSelfLink,
   285  				"1":           proc1,
   286  				"2":           proc2,
   287  				"3":           proc3,
   288  			},
   289  		},
   290  		{
   291  			name:   "skip /proc/self",
   292  			offset: 257,
   293  			wants: map[string]vfs.Dirent{
   294  				"thread-self": threadSelfLink,
   295  				"1":           proc1,
   296  				"2":           proc2,
   297  				"3":           proc3,
   298  			},
   299  		},
   300  		{
   301  			name:   "skip symlinks",
   302  			offset: 258,
   303  			wants: map[string]vfs.Dirent{
   304  				"1": proc1,
   305  				"2": proc2,
   306  				"3": proc3,
   307  			},
   308  		},
   309  		{
   310  			name:   "skip first process",
   311  			offset: 260,
   312  			wants: map[string]vfs.Dirent{
   313  				"2": proc2,
   314  				"3": proc3,
   315  			},
   316  		},
   317  		{
   318  			name:   "last process",
   319  			offset: 261,
   320  			wants: map[string]vfs.Dirent{
   321  				"3": proc3,
   322  			},
   323  		},
   324  		{
   325  			name:   "after last",
   326  			offset: 262,
   327  			wants:  nil,
   328  		},
   329  		{
   330  			name:   "TaskLimit+1",
   331  			offset: kernel.TasksLimit + 1,
   332  			wants:  nil,
   333  		},
   334  		{
   335  			name:   "max",
   336  			offset: math.MaxInt64,
   337  			wants:  nil,
   338  		},
   339  	} {
   340  		t.Run(tc.name, func(t *testing.T) {
   341  			s := s.WithSubtest(t)
   342  			fd, err := s.VFS.OpenAt(
   343  				s.Ctx,
   344  				s.Creds,
   345  				s.PathOpAtRoot("/proc"),
   346  				&vfs.OpenOptions{},
   347  			)
   348  			if err != nil {
   349  				t.Fatalf("vfsfs.OpenAt(/) failed: %v", err)
   350  			}
   351  			defer fd.DecRef(s.Ctx)
   352  			if _, err := fd.Seek(s.Ctx, tc.offset, linux.SEEK_SET); err != nil {
   353  				t.Fatalf("Seek(%d, SEEK_SET): %v", tc.offset, err)
   354  			}
   355  
   356  			var collector testutil.DirentCollector
   357  			if err := fd.IterDirents(s.Ctx, &collector); err != nil {
   358  				t.Fatalf("IterDirent(): %v", err)
   359  			}
   360  
   361  			expectedTypes := make(map[string]testutil.DirentType)
   362  			expectedOffsets := make(map[string]int64)
   363  			for name, want := range tc.wants {
   364  				expectedTypes[name] = want.Type
   365  				if want.NextOff != 0 {
   366  					expectedOffsets[name] = want.NextOff
   367  				}
   368  			}
   369  
   370  			collector.SkipDotsChecks(true) // We seek()ed past the dots.
   371  			s.AssertAllDirentTypes(&collector, expectedTypes)
   372  			s.AssertDirentOffsets(&collector, expectedOffsets)
   373  		})
   374  	}
   375  }
   376  
   377  func TestTask(t *testing.T) {
   378  	s := setup(t)
   379  	defer s.Destroy()
   380  
   381  	k := kernel.KernelFromContext(s.Ctx)
   382  	tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
   383  	_, err := testutil.CreateTask(s.Ctx, "name", tc, s.MntNs, s.Root, s.Root)
   384  	if err != nil {
   385  		t.Fatalf("CreateTask(): %v", err)
   386  	}
   387  
   388  	collector := s.ListDirents(s.PathOpAtRoot("/proc/1"))
   389  	s.AssertAllDirentTypes(collector, taskStaticFiles)
   390  }
   391  
   392  func TestProcSelf(t *testing.T) {
   393  	s := setup(t)
   394  	defer s.Destroy()
   395  
   396  	k := kernel.KernelFromContext(s.Ctx)
   397  	tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
   398  	task, err := testutil.CreateTask(s.Ctx, "name", tc, s.MntNs, s.Root, s.Root)
   399  	if err != nil {
   400  		t.Fatalf("CreateTask(): %v", err)
   401  	}
   402  
   403  	collector := s.WithTemporaryContext(task.AsyncContext()).ListDirents(&vfs.PathOperation{
   404  		Root:               s.Root,
   405  		Start:              s.Root,
   406  		Path:               fspath.Parse("/proc/self/"),
   407  		FollowFinalSymlink: true,
   408  	})
   409  	s.AssertAllDirentTypes(collector, taskStaticFiles)
   410  }
   411  
   412  func iterateDir(ctx context.Context, t *testing.T, s *testutil.System, fd *vfs.FileDescription) {
   413  	var collector testutil.DirentCollector
   414  	if err := fd.IterDirents(ctx, &collector); err != nil {
   415  		t.Fatalf("IterDirents(): %v", err)
   416  	}
   417  	if err := collector.Contains(".", linux.DT_DIR); err != nil {
   418  		t.Error(err.Error())
   419  	}
   420  	if err := collector.Contains("..", linux.DT_DIR); err != nil {
   421  		t.Error(err.Error())
   422  	}
   423  
   424  	for _, d := range collector.Dirents() {
   425  		if d.Name == "." || d.Name == ".." {
   426  			continue
   427  		}
   428  		absPath := path.Join(fd.MappedName(ctx), d.Name)
   429  		if d.Type == linux.DT_LNK {
   430  			_, err := s.VFS.ReadlinkAt(
   431  				ctx,
   432  				auth.CredentialsFromContext(ctx),
   433  				&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(absPath)},
   434  			)
   435  			if err != nil {
   436  				t.Errorf("vfsfs.ReadlinkAt(%v) failed: %v", absPath, err)
   437  			}
   438  			continue
   439  		}
   440  		child, err := s.VFS.OpenAt(
   441  			ctx,
   442  			auth.CredentialsFromContext(ctx),
   443  			&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(absPath)},
   444  			&vfs.OpenOptions{},
   445  		)
   446  		if err != nil {
   447  			t.Errorf("vfsfs.OpenAt(%v) failed: %v", absPath, err)
   448  			continue
   449  		}
   450  		defer child.DecRef(ctx)
   451  		stat, err := child.Stat(ctx, vfs.StatOptions{})
   452  		if err != nil {
   453  			t.Errorf("Stat(%v) failed: %v", absPath, err)
   454  		}
   455  		if got := linux.FileMode(stat.Mode).DirentType(); got != d.Type {
   456  			t.Errorf("wrong file mode, stat: %v, dirent: %v", got, d.Type)
   457  		}
   458  		if d.Type == linux.DT_DIR {
   459  			// Found another dir, let's do it again!
   460  			iterateDir(ctx, t, s, child)
   461  		}
   462  	}
   463  }
   464  
   465  // TestTree iterates all directories and stats every file.
   466  func TestTree(t *testing.T) {
   467  	s := setup(t)
   468  	defer s.Destroy()
   469  
   470  	k := kernel.KernelFromContext(s.Ctx)
   471  
   472  	pop := &vfs.PathOperation{
   473  		Root:  s.Root,
   474  		Start: s.Root,
   475  		Path:  fspath.Parse("test-file"),
   476  	}
   477  	opts := &vfs.OpenOptions{
   478  		Flags: linux.O_RDONLY | linux.O_CREAT,
   479  		Mode:  0777,
   480  	}
   481  	file, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, opts)
   482  	if err != nil {
   483  		t.Fatalf("failed to create test file: %v", err)
   484  	}
   485  	defer file.DecRef(s.Ctx)
   486  
   487  	var tasks []*kernel.Task
   488  	for i := 0; i < 5; i++ {
   489  		tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
   490  		task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root)
   491  		if err != nil {
   492  			t.Fatalf("CreateTask(): %v", err)
   493  		}
   494  		// Add file to populate /proc/[pid]/fd and fdinfo directories.
   495  		task.FDTable().NewFD(task.AsyncContext(), 0, file, kernel.FDFlags{})
   496  		tasks = append(tasks, task)
   497  	}
   498  
   499  	ctx := tasks[0].AsyncContext()
   500  	fd, err := s.VFS.OpenAt(
   501  		ctx,
   502  		auth.CredentialsFromContext(s.Ctx),
   503  		&vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse("/proc")},
   504  		&vfs.OpenOptions{},
   505  	)
   506  	if err != nil {
   507  		t.Fatalf("vfsfs.OpenAt(/proc) failed: %v", err)
   508  	}
   509  	iterateDir(ctx, t, s, fd)
   510  	fd.DecRef(ctx)
   511  }