1 //go:build !windows 2 // +build !windows 3 4 package utils 5 6 import ( 7 "fmt" 8 "os" 9 "strconv" 10 _ "unsafe" // for go:linkname 11 12 "golang.org/x/sys/unix" 13 ) 14 15 // EnsureProcHandle returns whether or not the given file handle is on procfs. 16 func EnsureProcHandle(fh *os.File) error { 17 var buf unix.Statfs_t 18 if err := unix.Fstatfs(int(fh.Fd()), &buf); err != nil { 19 return fmt.Errorf("ensure %s is on procfs: %w", fh.Name(), err) 20 } 21 if buf.Type != unix.PROC_SUPER_MAGIC { 22 return fmt.Errorf("%s is not on procfs", fh.Name()) 23 } 24 return nil 25 } 26 27 type fdFunc func(fd int) 28 29 // fdRangeFrom calls the passed fdFunc for each file descriptor that is open in 30 // the current process. 31 func fdRangeFrom(minFd int, fn fdFunc) error { 32 fdDir, err := os.Open("/proc/self/fd") 33 if err != nil { 34 return err 35 } 36 defer fdDir.Close() 37 38 if err := EnsureProcHandle(fdDir); err != nil { 39 return err 40 } 41 42 fdList, err := fdDir.Readdirnames(-1) 43 if err != nil { 44 return err 45 } 46 for _, fdStr := range fdList { 47 fd, err := strconv.Atoi(fdStr) 48 // Ignore non-numeric file names. 49 if err != nil { 50 continue 51 } 52 // Ignore descriptors lower than our specified minimum. 53 if fd < minFd { 54 continue 55 } 56 // Ignore the file descriptor we used for readdir, as it will be closed 57 // when we return. 58 if uintptr(fd) == fdDir.Fd() { 59 continue 60 } 61 // Run the closure. 62 fn(fd) 63 } 64 return nil 65 } 66 67 // CloseExecFrom sets the O_CLOEXEC flag on all file descriptors greater or 68 // equal to minFd in the current process. 69 func CloseExecFrom(minFd int) error { 70 return fdRangeFrom(minFd, unix.CloseOnExec) 71 } 72 73 //go:linkname runtime_IsPollDescriptor internal/poll.IsPollDescriptor 74 75 // In order to make sure we do not close the internal epoll descriptors the Go 76 // runtime uses, we need to ensure that we skip descriptors that match 77 // "internal/poll".IsPollDescriptor. Yes, this is a Go runtime internal thing, 78 // unfortunately there's no other way to be sure we're only keeping the file 79 // descriptors the Go runtime needs. Hopefully nothing blows up doing this... 80 func runtime_IsPollDescriptor(fd uintptr) bool //nolint:revive 81 82 // UnsafeCloseFrom closes all file descriptors greater or equal to minFd in the 83 // current process, except for those critical to Go's runtime (such as the 84 // netpoll management descriptors). 85 // 86 // NOTE: That this function is incredibly dangerous to use in most Go code, as 87 // closing file descriptors from underneath *os.File handles can lead to very 88 // bad behaviour (the closed file descriptor can be re-used and then any 89 // *os.File operations would apply to the wrong file). This function is only 90 // intended to be called from the last stage of runc init. 91 func UnsafeCloseFrom(minFd int) error { 92 // We must not close some file descriptors. 93 return fdRangeFrom(minFd, func(fd int) { 94 if runtime_IsPollDescriptor(uintptr(fd)) { 95 // These are the Go runtimes internal netpoll file descriptors. 96 // These file descriptors are operated on deep in the Go scheduler, 97 // and closing those files from underneath Go can result in panics. 98 // There is no issue with keeping them because they are not 99 // executable and are not useful to an attacker anyway. Also we 100 // don't have any choice. 101 return 102 } 103 // There's nothing we can do about errors from close(2), and the 104 // only likely error to be seen is EBADF which indicates the fd was 105 // already closed (in which case, we got what we wanted). 106 _ = unix.Close(fd) 107 }) 108 } 109 110 // NewSockPair returns a new unix socket pair 111 func NewSockPair(name string) (parent *os.File, child *os.File, err error) { 112 fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) 113 if err != nil { 114 return nil, nil, err 115 } 116 return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil 117 } 118