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  }