github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/loader/interpreter.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 16 17 import ( 18 "bytes" 19 "io" 20 21 "github.com/nicocha30/gvisor-ligolo/pkg/context" 22 "github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr" 23 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs" 24 "github.com/nicocha30/gvisor-ligolo/pkg/usermem" 25 ) 26 27 const ( 28 // interpreterScriptMagic identifies an interpreter script. 29 interpreterScriptMagic = "#!" 30 31 // interpMaxLineLength is the maximum length for the first line of an 32 // interpreter script. 33 // 34 // From execve(2): "A maximum line length of 127 characters is allowed 35 // for the first line in a #! executable shell script." 36 interpMaxLineLength = 127 37 ) 38 39 // parseInterpreterScript returns the interpreter path and argv. 40 func parseInterpreterScript(ctx context.Context, filename string, fd *vfs.FileDescription, argv []string) (newpath string, newargv []string, err error) { 41 line := make([]byte, interpMaxLineLength) 42 n, err := fd.ReadFull(ctx, usermem.BytesIOSequence(line), 0) 43 // Short read is OK. 44 if err != nil && err != io.ErrUnexpectedEOF { 45 if err == io.EOF { 46 err = linuxerr.ENOEXEC 47 } 48 return "", []string{}, err 49 } 50 line = line[:n] 51 52 if !bytes.Equal(line[:2], []byte(interpreterScriptMagic)) { 53 return "", []string{}, linuxerr.ENOEXEC 54 } 55 // Ignore #!. 56 line = line[2:] 57 58 // Ignore everything after newline. 59 // Linux silently truncates the remainder of the line if it exceeds 60 // interpMaxLineLength. 61 i := bytes.IndexByte(line, '\n') 62 if i >= 0 { 63 line = line[:i] 64 } 65 66 // Skip any whitespace before the interpeter. 67 line = bytes.TrimLeft(line, " \t") 68 69 // Linux only looks for spaces or tabs delimiting the interpreter and 70 // arg. 71 // 72 // execve(2): "On Linux, the entire string following the interpreter 73 // name is passed as a single argument to the interpreter, and this 74 // string can include white space." 75 interp := line 76 var arg []byte 77 i = bytes.IndexAny(line, " \t") 78 if i >= 0 { 79 interp = line[:i] 80 arg = bytes.TrimLeft(line[i:], " \t") 81 } 82 83 if string(interp) == "" { 84 ctx.Infof("Interpreter script contains no interpreter: %v", line) 85 return "", []string{}, linuxerr.ENOEXEC 86 } 87 88 // Build the new argument list: 89 // 90 // 1. The interpreter. 91 newargv = append(newargv, string(interp)) 92 93 // 2. The optional interpreter argument. 94 if len(arg) > 0 { 95 newargv = append(newargv, string(arg)) 96 } 97 98 // 3. The original arguments. The original argv[0] is replaced with the 99 // full script filename. 100 if len(argv) > 0 { 101 argv[0] = filename 102 } else { 103 argv = []string{filename} 104 } 105 newargv = append(newargv, argv...) 106 107 return string(interp), newargv, nil 108 }