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 }