github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/loader/loader.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 loader loads an executable file into a MemoryManager. 16 package loader 17 18 import ( 19 "bytes" 20 "fmt" 21 "io" 22 "path" 23 24 "github.com/ttpreport/gvisor-ligolo/pkg/abi" 25 "github.com/ttpreport/gvisor-ligolo/pkg/abi/linux" 26 "github.com/ttpreport/gvisor-ligolo/pkg/abi/linux/errno" 27 "github.com/ttpreport/gvisor-ligolo/pkg/context" 28 "github.com/ttpreport/gvisor-ligolo/pkg/cpuid" 29 "github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr" 30 "github.com/ttpreport/gvisor-ligolo/pkg/fspath" 31 "github.com/ttpreport/gvisor-ligolo/pkg/hostarch" 32 "github.com/ttpreport/gvisor-ligolo/pkg/rand" 33 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/arch" 34 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel/auth" 35 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/mm" 36 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs" 37 "github.com/ttpreport/gvisor-ligolo/pkg/syserr" 38 "github.com/ttpreport/gvisor-ligolo/pkg/usermem" 39 ) 40 41 // LoadArgs holds specifications for an executable file to be loaded. 42 type LoadArgs struct { 43 // MemoryManager is the memory manager to load the executable into. 44 MemoryManager *mm.MemoryManager 45 46 // RemainingTraversals is the maximum number of symlinks to follow to 47 // resolve Filename. This counter is passed by reference to keep it 48 // updated throughout the call stack. 49 RemainingTraversals *uint 50 51 // ResolveFinal indicates whether the final link of Filename should be 52 // resolved, if it is a symlink. 53 ResolveFinal bool 54 55 // Filename is the path for the executable. 56 Filename string 57 58 // File is an open FD of the executable. If File is not nil, then File will 59 // be loaded and Filename will be ignored. 60 // 61 // The caller is responsible for checking that the user can execute this file. 62 File *vfs.FileDescription 63 64 // Root is the current filesystem root. 65 Root vfs.VirtualDentry 66 67 // WorkingDir is the current working directory. 68 WorkingDir vfs.VirtualDentry 69 70 // If AfterOpen is not nil, it is called after every successful call to 71 // Opener.OpenPath(). 72 AfterOpen func(f *vfs.FileDescription) 73 74 // CloseOnExec indicates that the executable (or one of its parent 75 // directories) was opened with O_CLOEXEC. If the executable is an 76 // interpreter script, then cause an ENOENT error to occur, since the 77 // script would otherwise be inaccessible to the interpreter. 78 CloseOnExec bool 79 80 // Argv is the vector of arguments to pass to the executable. 81 Argv []string 82 83 // Envv is the vector of environment variables to pass to the 84 // executable. 85 Envv []string 86 87 // Features specifies the CPU feature set for the executable. 88 Features cpuid.FeatureSet 89 } 90 91 // openPath opens args.Filename and checks that it is valid for loading. 92 // 93 // openPath returns an *fs.Dirent and *fs.File for args.Filename, which is not 94 // installed in the Task FDTable. The caller takes ownership of both. 95 // 96 // args.Filename must be a readable, executable, regular file. 97 func openPath(ctx context.Context, args LoadArgs) (*vfs.FileDescription, error) { 98 if args.Filename == "" { 99 ctx.Infof("cannot open empty name") 100 return nil, linuxerr.ENOENT 101 } 102 103 // TODO(gvisor.dev/issue/160): Linux requires only execute permission, 104 // not read. However, our backing filesystems may prevent us from reading 105 // the file without read permission. Additionally, a task with a 106 // non-readable executable has additional constraints on access via 107 // ptrace and procfs. 108 opts := vfs.OpenOptions{ 109 Flags: linux.O_RDONLY, 110 FileExec: true, 111 } 112 vfsObj := args.Root.Mount().Filesystem().VirtualFilesystem() 113 creds := auth.CredentialsFromContext(ctx) 114 path := fspath.Parse(args.Filename) 115 pop := &vfs.PathOperation{ 116 Root: args.Root, 117 Start: args.WorkingDir, 118 Path: path, 119 FollowFinalSymlink: args.ResolveFinal, 120 } 121 if path.Absolute { 122 pop.Start = args.Root 123 } 124 fd, err := vfsObj.OpenAt(ctx, creds, pop, &opts) 125 if err != nil { 126 return nil, err 127 } 128 if args.AfterOpen != nil { 129 args.AfterOpen(fd) 130 } 131 return fd, nil 132 } 133 134 // checkIsRegularFile prevents us from trying to execute a directory, pipe, etc. 135 func checkIsRegularFile(ctx context.Context, fd *vfs.FileDescription, filename string) error { 136 stat, err := fd.Stat(ctx, vfs.StatOptions{}) 137 if err != nil { 138 return err 139 } 140 if t := linux.FileMode(stat.Mode).FileType(); t != linux.ModeRegular { 141 ctx.Infof("%q is not a regular file: %v", filename, t) 142 return linuxerr.EACCES 143 } 144 return nil 145 } 146 147 // allocStack allocates and maps a stack in to any available part of the address space. 148 func allocStack(ctx context.Context, m *mm.MemoryManager, a *arch.Context64) (*arch.Stack, error) { 149 ar, err := m.MapStack(ctx) 150 if err != nil { 151 return nil, err 152 } 153 return &arch.Stack{Arch: a, IO: m, Bottom: ar.End}, nil 154 } 155 156 const ( 157 // maxLoaderAttempts is the maximum number of attempts to try to load 158 // an interpreter scripts, to prevent loops. 6 (initial + 5 changes) is 159 // what the Linux kernel allows (fs/exec.c:search_binary_handler). 160 maxLoaderAttempts = 6 161 ) 162 163 // loadExecutable loads an executable that is pointed to by args.File. The 164 // caller is responsible for checking that the user can execute this file. 165 // If nil, the path args.Filename is resolved and loaded (check that the user 166 // can execute this file is done here in this case). If the executable is an 167 // interpreter script rather than an ELF, the binary of the corresponding 168 // interpreter will be loaded. 169 // 170 // It returns: 171 // - loadedELF, description of the loaded binary 172 // - arch.Context64 matching the binary arch 173 // - fs.Dirent of the binary file 174 // - Possibly updated args.Argv 175 func loadExecutable(ctx context.Context, args LoadArgs) (loadedELF, *arch.Context64, *vfs.FileDescription, []string, error) { 176 for i := 0; i < maxLoaderAttempts; i++ { 177 if args.File == nil { 178 var err error 179 args.File, err = openPath(ctx, args) 180 if err != nil { 181 ctx.Infof("Error opening %s: %v", args.Filename, err) 182 return loadedELF{}, nil, nil, nil, err 183 } 184 // Ensure file is release in case the code loops or errors out. 185 defer args.File.DecRef(ctx) 186 } else { 187 if err := checkIsRegularFile(ctx, args.File, args.Filename); err != nil { 188 return loadedELF{}, nil, nil, nil, err 189 } 190 } 191 192 // Check the header. Is this an ELF or interpreter script? 193 var hdr [4]uint8 194 // N.B. We assume that reading from a regular file cannot block. 195 _, err := args.File.ReadFull(ctx, usermem.BytesIOSequence(hdr[:]), 0) 196 // Allow unexpected EOF, as a valid executable could be only three bytes 197 // (e.g., #!a). 198 if err != nil && err != io.ErrUnexpectedEOF { 199 if err == io.EOF { 200 err = linuxerr.ENOEXEC 201 } 202 return loadedELF{}, nil, nil, nil, err 203 } 204 205 switch { 206 case bytes.Equal(hdr[:], []byte(elfMagic)): 207 loaded, ac, err := loadELF(ctx, args) 208 if err != nil { 209 ctx.Infof("Error loading ELF: %v", err) 210 return loadedELF{}, nil, nil, nil, err 211 } 212 // An ELF is always terminal. Hold on to file. 213 args.File.IncRef() 214 return loaded, ac, args.File, args.Argv, err 215 216 case bytes.Equal(hdr[:2], []byte(interpreterScriptMagic)): 217 if args.CloseOnExec { 218 return loadedELF{}, nil, nil, nil, linuxerr.ENOENT 219 } 220 args.Filename, args.Argv, err = parseInterpreterScript(ctx, args.Filename, args.File, args.Argv) 221 if err != nil { 222 ctx.Infof("Error loading interpreter script: %v", err) 223 return loadedELF{}, nil, nil, nil, err 224 } 225 // Refresh the traversal limit for the interpreter. 226 *args.RemainingTraversals = linux.MaxSymlinkTraversals 227 228 default: 229 ctx.Infof("Unknown magic: %v", hdr) 230 return loadedELF{}, nil, nil, nil, linuxerr.ENOEXEC 231 } 232 // Set to nil in case we loop on a Interpreter Script. 233 args.File = nil 234 } 235 236 return loadedELF{}, nil, nil, nil, linuxerr.ELOOP 237 } 238 239 // Load loads args.File into a MemoryManager. If args.File is nil, the path 240 // args.Filename is resolved and loaded instead. 241 // 242 // If Load returns ErrSwitchFile it should be called again with the returned 243 // path and argv. 244 // 245 // Preconditions: 246 // - The Task MemoryManager is empty. 247 // - Load is called on the Task goroutine. 248 func Load(ctx context.Context, args LoadArgs, extraAuxv []arch.AuxEntry, vdso *VDSO) (abi.OS, *arch.Context64, string, *syserr.Error) { 249 // Load the executable itself. 250 loaded, ac, file, newArgv, err := loadExecutable(ctx, args) 251 if err != nil { 252 return 0, nil, "", syserr.NewDynamic(fmt.Sprintf("failed to load %s: %v", args.Filename, err), syserr.FromError(err).ToLinux()) 253 } 254 defer file.DecRef(ctx) 255 256 // Load the VDSO. 257 vdsoAddr, err := loadVDSO(ctx, args.MemoryManager, vdso, loaded) 258 if err != nil { 259 return 0, nil, "", syserr.NewDynamic(fmt.Sprintf("error loading VDSO: %v", err), syserr.FromError(err).ToLinux()) 260 } 261 262 // Setup the heap. brk starts at the next page after the end of the 263 // executable. Userspace can assume that the remainer of the page after 264 // loaded.end is available for its use. 265 e, ok := loaded.end.RoundUp() 266 if !ok { 267 return 0, nil, "", syserr.NewDynamic(fmt.Sprintf("brk overflows: %#x", loaded.end), errno.ENOEXEC) 268 } 269 args.MemoryManager.BrkSetup(ctx, e) 270 271 // Allocate our stack. 272 stack, err := allocStack(ctx, args.MemoryManager, ac) 273 if err != nil { 274 return 0, nil, "", syserr.NewDynamic(fmt.Sprintf("Failed to allocate stack: %v", err), syserr.FromError(err).ToLinux()) 275 } 276 277 // Push the original filename to the stack, for AT_EXECFN. 278 if _, err := stack.PushNullTerminatedByteSlice([]byte(args.Filename)); err != nil { 279 return 0, nil, "", syserr.NewDynamic(fmt.Sprintf("Failed to push exec filename: %v", err), syserr.FromError(err).ToLinux()) 280 } 281 execfn := stack.Bottom 282 283 // Push 16 random bytes on the stack which AT_RANDOM will point to. 284 var b [16]byte 285 if _, err := rand.Read(b[:]); err != nil { 286 return 0, nil, "", syserr.NewDynamic(fmt.Sprintf("Failed to read random bytes: %v", err), syserr.FromError(err).ToLinux()) 287 } 288 if _, err = stack.PushNullTerminatedByteSlice(b[:]); err != nil { 289 return 0, nil, "", syserr.NewDynamic(fmt.Sprintf("Failed to push random bytes: %v", err), syserr.FromError(err).ToLinux()) 290 } 291 random := stack.Bottom 292 293 c := auth.CredentialsFromContext(ctx) 294 295 // Add generic auxv entries. 296 auxv := append(loaded.auxv, arch.Auxv{ 297 arch.AuxEntry{linux.AT_UID, hostarch.Addr(c.RealKUID.In(c.UserNamespace).OrOverflow())}, 298 arch.AuxEntry{linux.AT_EUID, hostarch.Addr(c.EffectiveKUID.In(c.UserNamespace).OrOverflow())}, 299 arch.AuxEntry{linux.AT_GID, hostarch.Addr(c.RealKGID.In(c.UserNamespace).OrOverflow())}, 300 arch.AuxEntry{linux.AT_EGID, hostarch.Addr(c.EffectiveKGID.In(c.UserNamespace).OrOverflow())}, 301 // The conditions that require AT_SECURE = 1 never arise. See 302 // kernel.Task.updateCredsForExecLocked. 303 arch.AuxEntry{linux.AT_SECURE, 0}, 304 arch.AuxEntry{linux.AT_CLKTCK, linux.CLOCKS_PER_SEC}, 305 arch.AuxEntry{linux.AT_EXECFN, execfn}, 306 arch.AuxEntry{linux.AT_RANDOM, random}, 307 arch.AuxEntry{linux.AT_PAGESZ, hostarch.PageSize}, 308 arch.AuxEntry{linux.AT_SYSINFO_EHDR, vdsoAddr}, 309 }...) 310 auxv = append(auxv, extraAuxv...) 311 312 sl, err := stack.Load(newArgv, args.Envv, auxv) 313 if err != nil { 314 return 0, nil, "", syserr.NewDynamic(fmt.Sprintf("Failed to load stack: %v", err), syserr.FromError(err).ToLinux()) 315 } 316 317 m := args.MemoryManager 318 m.SetArgvStart(sl.ArgvStart) 319 m.SetArgvEnd(sl.ArgvEnd) 320 m.SetEnvvStart(sl.EnvvStart) 321 m.SetEnvvEnd(sl.EnvvEnd) 322 m.SetAuxv(auxv) 323 m.SetExecutable(ctx, file) 324 m.SetVDSOSigReturn(uint64(vdsoAddr) + vdsoSigreturnOffset - vdsoPrelink) 325 326 ac.SetIP(uintptr(loaded.entry)) 327 ac.SetStack(uintptr(stack.Bottom)) 328 329 name := path.Base(args.Filename) 330 if len(name) > linux.TASK_COMM_LEN-1 { 331 name = name[:linux.TASK_COMM_LEN-1] 332 } 333 334 return loaded.os, ac, name, nil 335 }