github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/syscalls/linux/sys_getdents.go (about) 1 // Copyright 2020 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 linux 16 17 import ( 18 "fmt" 19 20 "github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr" 21 "github.com/nicocha30/gvisor-ligolo/pkg/hostarch" 22 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch" 23 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel" 24 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs" 25 "github.com/nicocha30/gvisor-ligolo/pkg/sync" 26 "github.com/nicocha30/gvisor-ligolo/pkg/usermem" 27 ) 28 29 // Getdents implements Linux syscall getdents(2). 30 func Getdents(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { 31 return getdents(t, args, false /* isGetdents64 */) 32 } 33 34 // Getdents64 implements Linux syscall getdents64(2). 35 func Getdents64(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { 36 return getdents(t, args, true /* isGetdents64 */) 37 } 38 39 // DirentStructBytesWithoutName is enough to fit (struct linux_dirent) and 40 // (struct linux_dirent64) without accounting for the name parameter. 41 const DirentStructBytesWithoutName = 8 + 8 + 2 + 1 + 1 42 43 func getdents(t *kernel.Task, args arch.SyscallArguments, isGetdents64 bool) (uintptr, *kernel.SyscallControl, error) { 44 fd := args[0].Int() 45 addr := args[1].Pointer() 46 size := int(args[2].Uint()) 47 if size < DirentStructBytesWithoutName { 48 return 0, nil, linuxerr.EINVAL 49 } 50 51 file := t.GetFile(fd) 52 if file == nil { 53 return 0, nil, linuxerr.EBADF 54 } 55 defer file.DecRef(t) 56 57 // We want to be sure of the allowed buffer size before calling IterDirents, 58 // because this function depends on IterDirents saving state of which dirent 59 // was the last one that was successfully operated on. 60 allowedSize, err := t.MemoryManager().EnsurePMAsExist(t, addr, int64(size), usermem.IOOpts{ 61 AddressSpaceActive: true, 62 }) 63 if allowedSize == 0 { 64 return 0, nil, err 65 } 66 67 cb := getGetdentsCallback(t, int(allowedSize), size, isGetdents64) 68 err = file.IterDirents(t, cb) 69 n, _ := t.CopyOutBytes(addr, cb.buf[:cb.copied]) 70 71 putGetdentsCallback(cb) 72 73 // Only report an error in case we didn't copy anything. 74 // If we did manage to give _something_ to the caller then the correct 75 // behaviour is to return success. 76 if n == 0 { 77 return 0, nil, err 78 } 79 80 return uintptr(n), nil, nil 81 } 82 83 type getdentsCallback struct { 84 t *kernel.Task 85 buf []byte 86 copied int 87 userReportedSize int 88 isGetdents64 bool 89 } 90 91 var getdentsCallbackPool = sync.Pool{ 92 New: func() any { 93 return &getdentsCallback{} 94 }, 95 } 96 97 func getGetdentsCallback(t *kernel.Task, size int, userReportedSize int, isGetdents64 bool) *getdentsCallback { 98 cb := getdentsCallbackPool.Get().(*getdentsCallback) 99 buf := cb.buf 100 if cap(buf) < size { 101 buf = make([]byte, size) 102 } else { 103 buf = buf[:size] 104 } 105 106 *cb = getdentsCallback{ 107 t: t, 108 buf: buf, 109 copied: 0, 110 userReportedSize: userReportedSize, 111 isGetdents64: isGetdents64, 112 } 113 return cb 114 } 115 116 func putGetdentsCallback(cb *getdentsCallback) { 117 cb.t = nil 118 cb.buf = cb.buf[:0] 119 getdentsCallbackPool.Put(cb) 120 } 121 122 // Handle implements vfs.IterDirentsCallback.Handle. 123 func (cb *getdentsCallback) Handle(dirent vfs.Dirent) error { 124 remaining := len(cb.buf) - cb.copied 125 if cb.isGetdents64 { 126 // struct linux_dirent64 { 127 // ino64_t d_ino; /* 64-bit inode number */ 128 // off64_t d_off; /* 64-bit offset to next structure */ 129 // unsigned short d_reclen; /* Size of this dirent */ 130 // unsigned char d_type; /* File type */ 131 // char d_name[]; /* Filename (null-terminated) */ 132 // }; 133 size := DirentStructBytesWithoutName + len(dirent.Name) 134 size = (size + 7) &^ 7 // round up to multiple of 8 135 if size > remaining { 136 // This is only needed to imitate Linux, since it writes out to the user 137 // as it's iterating over dirs. We don't do that because we can't take 138 // the mm.mappingMu while holding the filesystem mutex. 139 if cb.copied == 0 && cb.userReportedSize >= size { 140 return linuxerr.EFAULT 141 } 142 return linuxerr.EINVAL 143 } 144 buf := cb.buf[cb.copied : cb.copied+size] 145 hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino) 146 hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff)) 147 hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size)) 148 buf[18] = dirent.Type 149 copy(buf[19:], dirent.Name) 150 // Zero out all remaining bytes in buf, including the NUL terminator 151 // after dirent.Name. 152 bufTail := buf[19+len(dirent.Name):] 153 for i := range bufTail { 154 bufTail[i] = 0 155 } 156 cb.copied += size 157 } else { 158 // struct linux_dirent { 159 // unsigned long d_ino; /* Inode number */ 160 // unsigned long d_off; /* Offset to next linux_dirent */ 161 // unsigned short d_reclen; /* Length of this linux_dirent */ 162 // char d_name[]; /* Filename (null-terminated) */ 163 // /* length is actually (d_reclen - 2 - 164 // offsetof(struct linux_dirent, d_name)) */ 165 // /* 166 // char pad; // Zero padding byte 167 // char d_type; // File type (only since Linux 168 // // 2.6.4); offset is (d_reclen - 1) 169 // */ 170 // }; 171 if cb.t.Arch().Width() != 8 { 172 panic(fmt.Sprintf("unsupported sizeof(unsigned long): %d", cb.t.Arch().Width())) 173 } 174 size := DirentStructBytesWithoutName + len(dirent.Name) 175 size = (size + 7) &^ 7 // round up to multiple of sizeof(long) 176 if size > remaining { 177 if cb.copied == 0 && cb.userReportedSize >= size { 178 return linuxerr.EFAULT 179 } 180 return linuxerr.EINVAL 181 } 182 buf := cb.buf[cb.copied : cb.copied+size] 183 hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino) 184 hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff)) 185 hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size)) 186 copy(buf[18:], dirent.Name) 187 // Zero out all remaining bytes in buf, including the NUL terminator 188 // after dirent.Name and the zero padding byte between the name and 189 // dirent type. 190 bufTail := buf[18+len(dirent.Name) : size-1] 191 for i := range bufTail { 192 bufTail[i] = 0 193 } 194 buf[size-1] = dirent.Type 195 cb.copied += size 196 } 197 198 return nil 199 }