github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/kernel/fd_table.go (about) 1 // Copyright 2018 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 kernel 16 17 import ( 18 "fmt" 19 "math" 20 "strings" 21 22 "github.com/ttpreport/gvisor-ligolo/pkg/abi/linux" 23 "github.com/ttpreport/gvisor-ligolo/pkg/bitmap" 24 "github.com/ttpreport/gvisor-ligolo/pkg/context" 25 "github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr" 26 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/fsimpl/lock" 27 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/limits" 28 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs" 29 "golang.org/x/sys/unix" 30 ) 31 32 // FDFlags define flags for an individual descriptor. 33 // 34 // +stateify savable 35 type FDFlags struct { 36 // CloseOnExec indicates the descriptor should be closed on exec. 37 CloseOnExec bool 38 } 39 40 // ToLinuxFileFlags converts a kernel.FDFlags object to a Linux file flags 41 // representation. 42 func (f FDFlags) ToLinuxFileFlags() (mask uint) { 43 if f.CloseOnExec { 44 mask |= linux.O_CLOEXEC 45 } 46 return 47 } 48 49 // ToLinuxFDFlags converts a kernel.FDFlags object to a Linux descriptor flags 50 // representation. 51 func (f FDFlags) ToLinuxFDFlags() (mask uint) { 52 if f.CloseOnExec { 53 mask |= linux.FD_CLOEXEC 54 } 55 return 56 } 57 58 // descriptor holds the details about a file descriptor, namely a pointer to 59 // the file itself and the descriptor flags. 60 // 61 // Note that this is immutable and can only be changed via operations on the 62 // descriptorTable. 63 // 64 // +stateify savable 65 type descriptor struct { 66 file *vfs.FileDescription 67 flags FDFlags 68 } 69 70 // MaxFdLimit defines the upper limit on the integer value of file descriptors. 71 const MaxFdLimit int32 = int32(bitmap.MaxBitEntryLimit) 72 73 // FDTable is used to manage File references and flags. 74 // 75 // +stateify savable 76 type FDTable struct { 77 FDTableRefs 78 79 k *Kernel 80 81 // mu protects below. 82 mu fdTableMutex `state:"nosave"` 83 84 // fdBitmap shows which fds are already in use. 85 fdBitmap bitmap.Bitmap `state:"nosave"` 86 87 // descriptorTable holds descriptors. 88 descriptorTable `state:".(map[int32]descriptor)"` 89 } 90 91 func (f *FDTable) saveDescriptorTable() map[int32]descriptor { 92 m := make(map[int32]descriptor) 93 f.mu.Lock() 94 defer f.mu.Unlock() 95 f.forEach(context.Background(), func(fd int32, file *vfs.FileDescription, flags FDFlags) { 96 m[fd] = descriptor{ 97 file: file, 98 flags: flags, 99 } 100 }) 101 return m 102 } 103 104 func (f *FDTable) loadDescriptorTable(m map[int32]descriptor) { 105 ctx := context.Background() 106 f.initNoLeakCheck() // Initialize table. 107 f.fdBitmap = bitmap.New(uint32(math.MaxUint16)) 108 for fd, d := range m { 109 if fd < 0 { 110 panic(fmt.Sprintf("FD is not supposed to be negative. FD: %d", fd)) 111 } 112 113 if file := f.set(fd, d.file, d.flags); file != nil { 114 panic("file set") 115 } 116 f.fdBitmap.Add(uint32(fd)) 117 // Note that we do _not_ need to acquire a extra table reference here. The 118 // table reference will already be accounted for in the file, so we drop the 119 // reference taken by set above. 120 if d.file != nil { 121 d.file.DecRef(ctx) 122 } 123 } 124 } 125 126 // drop drops the table reference. 127 func (f *FDTable) drop(ctx context.Context, file *vfs.FileDescription) { 128 // Release any POSIX lock possibly held by the FDTable. 129 if file.SupportsLocks() { 130 err := file.UnlockPOSIX(ctx, f, lock.LockRange{0, lock.LockEOF}) 131 if err != nil && !linuxerr.Equals(linuxerr.ENOLCK, err) { 132 panic(fmt.Sprintf("UnlockPOSIX failed: %v", err)) 133 } 134 } 135 136 // Drop the table's reference. 137 file.DecRef(ctx) 138 } 139 140 // NewFDTable allocates a new FDTable that may be used by tasks in k. 141 func (k *Kernel) NewFDTable() *FDTable { 142 f := &FDTable{k: k} 143 f.init() 144 return f 145 } 146 147 // DecRef implements RefCounter.DecRef. 148 // 149 // If f reaches zero references, all of its file descriptors are removed. 150 func (f *FDTable) DecRef(ctx context.Context) { 151 f.FDTableRefs.DecRef(func() { 152 f.RemoveIf(ctx, func(*vfs.FileDescription, FDFlags) bool { 153 return true 154 }) 155 }) 156 } 157 158 // forEachUpTo iterates over all non-nil files upto maxFds (non-inclusive) in sorted order. 159 // 160 // It is the caller's responsibility to acquire an appropriate lock. 161 func (f *FDTable) forEachUpTo(ctx context.Context, maxFd int32, fn func(fd int32, file *vfs.FileDescription, flags FDFlags)) { 162 // Iterate through the fdBitmap. 163 f.fdBitmap.ForEach(0, uint32(maxFd), func(ufd uint32) bool { 164 fd := int32(ufd) 165 file, flags, ok := f.get(fd) 166 if !ok { 167 return true 168 } 169 if file != nil { 170 if !file.TryIncRef() { 171 return true 172 } 173 fn(fd, file, flags) 174 file.DecRef(ctx) 175 } 176 return true 177 }) 178 } 179 180 // forEach iterates over all non-nil files upto maxFd in sorted order. 181 // 182 // It is the caller's responsibility to acquire an appropriate lock. 183 func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *vfs.FileDescription, flags FDFlags)) { 184 f.forEachUpTo(ctx, MaxFdLimit, fn) 185 } 186 187 // String is a stringer for FDTable. 188 func (f *FDTable) String() string { 189 var buf strings.Builder 190 ctx := context.Background() 191 files := make(map[int32]*vfs.FileDescription) 192 f.mu.Lock() 193 // Can't release f.mu from defer, because vfsObj.PathnameWithDeleted 194 // should not be called under the fdtable mutex. 195 f.forEach(ctx, func(fd int32, file *vfs.FileDescription, flags FDFlags) { 196 if file != nil { 197 file.IncRef() 198 files[fd] = file 199 } 200 }) 201 f.mu.Unlock() 202 defer func() { 203 for _, f := range files { 204 f.DecRef(ctx) 205 } 206 }() 207 208 for fd, file := range files { 209 vfsObj := file.Mount().Filesystem().VirtualFilesystem() 210 vd := file.VirtualDentry() 211 if vd.Dentry() == nil { 212 panic(fmt.Sprintf("fd %d (type %T) has nil dentry: %#v", fd, file.Impl(), file)) 213 } 214 name, err := vfsObj.PathnameWithDeleted(ctx, vfs.VirtualDentry{}, file.VirtualDentry()) 215 if err != nil { 216 fmt.Fprintf(&buf, "<err: %v>\n", err) 217 continue 218 } 219 fmt.Fprintf(&buf, "\tfd:%d => name %s\n", fd, name) 220 } 221 return buf.String() 222 } 223 224 // NewFDs allocates new FDs guaranteed to be the lowest number available 225 // greater than or equal to the minFD parameter. All files will share the set 226 // flags. Success is guaranteed to be all or none. 227 func (f *FDTable) NewFDs(ctx context.Context, minFD int32, files []*vfs.FileDescription, flags FDFlags) (fds []int32, err error) { 228 if minFD < 0 { 229 // Don't accept negative FDs. 230 return nil, unix.EINVAL 231 } 232 233 // Default limit. 234 end := MaxFdLimit 235 236 // Ensure we don't get past the provided limit. 237 if limitSet := limits.FromContext(ctx); limitSet != nil { 238 lim := limitSet.Get(limits.NumberOfFiles) 239 // Only set if the limit is smaller than the max to avoid overflow. 240 if lim.Cur != limits.Infinity && lim.Cur < uint64(MaxFdLimit) { 241 end = int32(lim.Cur) 242 } 243 if minFD+int32(len(files)) > end { 244 return nil, unix.EMFILE 245 } 246 } 247 248 f.mu.Lock() 249 250 // max is used as the largest number in fdBitmap + 1. 251 max := int32(0) 252 if !f.fdBitmap.IsEmpty() { 253 max = int32(f.fdBitmap.Maximum()) 254 max++ 255 } 256 257 // Adjust max in case it is less than minFD. 258 if max < minFD { 259 max = minFD 260 } 261 // Install all entries. 262 for len(fds) < len(files) { 263 // Try to use free bit in fdBitmap. 264 // If all bits in fdBitmap are used, expand fd to the max. 265 fd, err := f.fdBitmap.FirstZero(uint32(minFD)) 266 if err != nil { 267 fd = uint32(max) 268 max++ 269 } 270 if fd >= uint32(end) { 271 break 272 } 273 f.fdBitmap.Add(fd) 274 f.set(int32(fd), files[len(fds)], flags) 275 fds = append(fds, int32(fd)) 276 minFD = int32(fd) 277 } 278 279 // Failure? Unwind existing FDs. 280 if len(fds) < len(files) { 281 for _, i := range fds { 282 f.set(i, nil, FDFlags{}) 283 f.fdBitmap.Remove(uint32(i)) 284 } 285 f.mu.Unlock() 286 287 // Drop the reference taken by the call to f.set() that 288 // originally installed the file. Don't call f.drop() 289 // (generating inotify events, etc.) since the file should 290 // appear to have never been inserted into f. 291 for _, file := range files[:len(fds)] { 292 file.DecRef(ctx) 293 } 294 return nil, unix.EMFILE 295 } 296 297 f.mu.Unlock() 298 return fds, nil 299 } 300 301 // NewFD allocates a file descriptor greater than or equal to minFD for 302 // the given file description. If it succeeds, it takes a reference on file. 303 func (f *FDTable) NewFD(ctx context.Context, minFD int32, file *vfs.FileDescription, flags FDFlags) (int32, error) { 304 files := []*vfs.FileDescription{file} 305 fileSlice, error := f.NewFDs(ctx, minFD, files, flags) 306 if error != nil { 307 return -1, error 308 } 309 return fileSlice[0], nil 310 } 311 312 // NewFDAt sets the file reference for the given FD. If there is an active 313 // reference for that FD, the ref count for that existing reference is 314 // decremented. 315 func (f *FDTable) NewFDAt(ctx context.Context, fd int32, file *vfs.FileDescription, flags FDFlags) error { 316 df, err := f.newFDAt(ctx, fd, file, flags) 317 if err != nil { 318 return err 319 } 320 if df != nil { 321 f.drop(ctx, df) 322 } 323 return nil 324 } 325 326 func (f *FDTable) newFDAt(ctx context.Context, fd int32, file *vfs.FileDescription, flags FDFlags) (*vfs.FileDescription, error) { 327 if fd < 0 { 328 // Don't accept negative FDs. 329 return nil, unix.EBADF 330 } 331 332 // Check the limit for the provided file. 333 if limitSet := limits.FromContext(ctx); limitSet != nil { 334 if lim := limitSet.Get(limits.NumberOfFiles); lim.Cur != limits.Infinity && uint64(fd) >= lim.Cur { 335 return nil, unix.EMFILE 336 } 337 } 338 339 // Install the entry. 340 f.mu.Lock() 341 defer f.mu.Unlock() 342 343 df := f.set(fd, file, flags) 344 // Add fd to fdBitmap. 345 if file != nil { 346 f.fdBitmap.Add(uint32(fd)) 347 } 348 349 return df, nil 350 } 351 352 // SetFlags sets the flags for the given file descriptor. 353 // 354 // True is returned iff flags were changed. 355 func (f *FDTable) SetFlags(ctx context.Context, fd int32, flags FDFlags) error { 356 if fd < 0 { 357 // Don't accept negative FDs. 358 return unix.EBADF 359 } 360 361 f.mu.Lock() 362 defer f.mu.Unlock() 363 364 file, _, _ := f.get(fd) 365 if file == nil { 366 // No file found. 367 return unix.EBADF 368 } 369 370 // Update the flags. 371 f.set(fd, file, flags) 372 return nil 373 } 374 375 // SetFlagsForRange sets the flags for the given range of file descriptors 376 // (inclusive: [startFd, endFd]). 377 func (f *FDTable) SetFlagsForRange(ctx context.Context, startFd int32, endFd int32, flags FDFlags) error { 378 if startFd < 0 || startFd > endFd { 379 return unix.EBADF 380 } 381 382 f.mu.Lock() 383 defer f.mu.Unlock() 384 385 for fd, err := f.fdBitmap.FirstOne(uint32(startFd)); err == nil && fd <= uint32(endFd); fd, err = f.fdBitmap.FirstOne(fd + 1) { 386 fdI32 := int32(fd) 387 file, _, _ := f.get(fdI32) 388 f.set(fdI32, file, flags) 389 } 390 391 return nil 392 } 393 394 // Get returns a reference to the file and the flags for the FD or nil if no 395 // file is defined for the given fd. 396 // 397 // N.B. Callers are required to use DecRef when they are done. 398 // 399 //go:nosplit 400 func (f *FDTable) Get(fd int32) (*vfs.FileDescription, FDFlags) { 401 if fd < 0 { 402 return nil, FDFlags{} 403 } 404 405 for { 406 file, flags, _ := f.get(fd) 407 if file != nil { 408 if !file.TryIncRef() { 409 continue // Race caught. 410 } 411 // Reference acquired. 412 return file, flags 413 } 414 // No file available. 415 return nil, FDFlags{} 416 } 417 } 418 419 // GetFDs returns a sorted list of valid fds. 420 // 421 // Precondition: The caller must be running on the task goroutine, or Task.mu 422 // must be locked. 423 func (f *FDTable) GetFDs(ctx context.Context) []int32 { 424 f.mu.Lock() 425 defer f.mu.Unlock() 426 fds := make([]int32, 0, int(f.fdBitmap.GetNumOnes())) 427 f.forEach(ctx, func(fd int32, _ *vfs.FileDescription, _ FDFlags) { 428 fds = append(fds, fd) 429 }) 430 return fds 431 } 432 433 // Exists returns whether fd is defined in the table. It is inherently racy. 434 // 435 //go:nosplit 436 func (f *FDTable) Exists(fd int32) bool { 437 if fd < 0 { 438 return false 439 } 440 file, _, _ := f.get(fd) 441 return file != nil 442 } 443 444 // Fork returns an independent FDTable, cloning all FDs up to maxFds (non-inclusive). 445 func (f *FDTable) Fork(ctx context.Context, maxFd int32) *FDTable { 446 clone := f.k.NewFDTable() 447 f.mu.Lock() 448 defer f.mu.Unlock() 449 f.forEachUpTo(ctx, maxFd, func(fd int32, file *vfs.FileDescription, flags FDFlags) { 450 // The set function here will acquire an appropriate table 451 // reference for the clone. We don't need anything else. 452 if df := clone.set(fd, file, flags); df != nil { 453 panic("file set") 454 } 455 clone.fdBitmap.Add(uint32(fd)) 456 }) 457 return clone 458 } 459 460 // Remove removes an FD from and returns a tuple where one of the files is non-nil 461 // iff successful. 462 // 463 // N.B. Callers are required to use DecRef on the returned file when they are done. 464 func (f *FDTable) Remove(ctx context.Context, fd int32) *vfs.FileDescription { 465 if fd < 0 { 466 return nil 467 } 468 469 f.mu.Lock() 470 file, _, _ := f.get(fd) 471 if file != nil { 472 // Add reference for caller. 473 file.IncRef() 474 file = f.set(fd, nil, FDFlags{}) // Zap entry. 475 f.fdBitmap.Remove(uint32(fd)) 476 } 477 f.mu.Unlock() 478 479 if file != nil { 480 f.drop(ctx, file) 481 } 482 return file 483 } 484 485 // RemoveIf removes all FDs where cond is true. 486 func (f *FDTable) RemoveIf(ctx context.Context, cond func(*vfs.FileDescription, FDFlags) bool) { 487 var files []*vfs.FileDescription 488 489 f.mu.Lock() 490 f.forEach(ctx, func(fd int32, file *vfs.FileDescription, flags FDFlags) { 491 if cond(file, flags) { 492 df := f.set(fd, nil, FDFlags{}) // Clear from table. 493 f.fdBitmap.Remove(uint32(fd)) 494 if df != nil { 495 files = append(files, df) 496 } 497 } 498 }) 499 f.mu.Unlock() 500 501 for _, file := range files { 502 f.drop(ctx, file) 503 } 504 } 505 506 // RemoveNextInRange removes the next FD that falls within the given range, 507 // and returns a tuple where one of the files is non-nil iff successful. 508 // 509 // N.B. Callers are required to use DecRef on the returned file when they are done. 510 func (f *FDTable) RemoveNextInRange(ctx context.Context, startFd int32, endFd int32) (int32, *vfs.FileDescription) { 511 if startFd < 0 || startFd > endFd { 512 return MaxFdLimit, nil 513 } 514 515 f.mu.Lock() 516 517 fdUint, err := f.fdBitmap.FirstOne(uint32(startFd)) 518 fd := int32(fdUint) 519 if err != nil || fd > endFd { 520 f.mu.Unlock() 521 return MaxFdLimit, nil 522 } 523 file, _, _ := f.get(fd) 524 525 if file != nil { 526 // Add reference for caller. 527 file.IncRef() 528 file = f.set(fd, nil, FDFlags{}) // Zap entry. 529 f.fdBitmap.Remove(uint32(fd)) 530 } 531 f.mu.Unlock() 532 533 if file != nil { 534 f.drop(ctx, file) 535 } 536 537 return fd, file 538 } 539 540 // GetLastFd returns the last set FD in the FDTable bitmap. 541 func (f *FDTable) GetLastFd() int32 { 542 f.mu.Lock() 543 defer f.mu.Unlock() 544 545 last := f.fdBitmap.Maximum() 546 if last > bitmap.MaxBitEntryLimit { 547 return MaxFdLimit 548 } 549 return int32(last) 550 }