github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/proc/exec_args.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 proc 16 17 import ( 18 "bytes" 19 "fmt" 20 "io" 21 22 "github.com/SagerNet/gvisor/pkg/abi/linux" 23 "github.com/SagerNet/gvisor/pkg/context" 24 "github.com/SagerNet/gvisor/pkg/errors/linuxerr" 25 "github.com/SagerNet/gvisor/pkg/hostarch" 26 "github.com/SagerNet/gvisor/pkg/sentry/fs" 27 "github.com/SagerNet/gvisor/pkg/sentry/fs/fsutil" 28 "github.com/SagerNet/gvisor/pkg/sentry/kernel" 29 "github.com/SagerNet/gvisor/pkg/usermem" 30 "github.com/SagerNet/gvisor/pkg/waiter" 31 ) 32 33 // LINT.IfChange 34 35 // execArgType enumerates the types of exec arguments that are exposed through 36 // proc. 37 type execArgType int 38 39 const ( 40 cmdlineExecArg execArgType = iota 41 environExecArg 42 ) 43 44 // execArgInode is a inode containing the exec args (either cmdline or environ) 45 // for a given task. 46 // 47 // +stateify savable 48 type execArgInode struct { 49 fsutil.SimpleFileInode 50 51 // arg is the type of exec argument this file contains. 52 arg execArgType 53 54 // t is the Task to read the exec arg line from. 55 t *kernel.Task 56 } 57 58 var _ fs.InodeOperations = (*execArgInode)(nil) 59 60 // newExecArgFile creates a file containing the exec args of the given type. 61 func newExecArgInode(ctx context.Context, t *kernel.Task, msrc *fs.MountSource, arg execArgType) *fs.Inode { 62 if arg != cmdlineExecArg && arg != environExecArg { 63 panic(fmt.Sprintf("unknown exec arg type %v", arg)) 64 } 65 f := &execArgInode{ 66 SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC), 67 arg: arg, 68 t: t, 69 } 70 return newProcInode(ctx, f, msrc, fs.SpecialFile, t) 71 } 72 73 // GetFile implements fs.InodeOperations.GetFile. 74 func (i *execArgInode) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { 75 return fs.NewFile(ctx, dirent, flags, &execArgFile{ 76 arg: i.arg, 77 t: i.t, 78 }), nil 79 } 80 81 // +stateify savable 82 type execArgFile struct { 83 fsutil.FileGenericSeek `state:"nosave"` 84 fsutil.FileNoIoctl `state:"nosave"` 85 fsutil.FileNoMMap `state:"nosave"` 86 fsutil.FileNoSplice `state:"nosave"` 87 fsutil.FileNotDirReaddir `state:"nosave"` 88 fsutil.FileNoopRelease `state:"nosave"` 89 fsutil.FileNoopFlush `state:"nosave"` 90 fsutil.FileNoopFsync `state:"nosave"` 91 fsutil.FileNoopWrite `state:"nosave"` 92 fsutil.FileUseInodeUnstableAttr `state:"nosave"` 93 waiter.AlwaysReady `state:"nosave"` 94 95 // arg is the type of exec argument this file contains. 96 arg execArgType 97 98 // t is the Task to read the exec arg line from. 99 t *kernel.Task 100 } 101 102 var _ fs.FileOperations = (*execArgFile)(nil) 103 104 // Read reads the exec arg from the process's address space.. 105 func (f *execArgFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { 106 if offset < 0 { 107 return 0, linuxerr.EINVAL 108 } 109 110 m, err := getTaskMM(f.t) 111 if err != nil { 112 return 0, err 113 } 114 defer m.DecUsers(ctx) 115 116 // Figure out the bounds of the exec arg we are trying to read. 117 var execArgStart, execArgEnd hostarch.Addr 118 switch f.arg { 119 case cmdlineExecArg: 120 execArgStart, execArgEnd = m.ArgvStart(), m.ArgvEnd() 121 case environExecArg: 122 execArgStart, execArgEnd = m.EnvvStart(), m.EnvvEnd() 123 default: 124 panic(fmt.Sprintf("unknown exec arg type %v", f.arg)) 125 } 126 if execArgStart == 0 || execArgEnd == 0 { 127 // Don't attempt to read before the start/end are set up. 128 return 0, io.EOF 129 } 130 131 start, ok := execArgStart.AddLength(uint64(offset)) 132 if !ok { 133 return 0, io.EOF 134 } 135 if start >= execArgEnd { 136 return 0, io.EOF 137 } 138 139 length := int(execArgEnd - start) 140 if dstlen := dst.NumBytes(); int64(length) > dstlen { 141 length = int(dstlen) 142 } 143 144 buf := make([]byte, length) 145 // N.B. Technically this should be usermem.IOOpts.IgnorePermissions = true 146 // until Linux 4.9 (272ddc8b3735 "proc: don't use FOLL_FORCE for reading 147 // cmdline and environment"). 148 copyN, err := m.CopyIn(ctx, start, buf, usermem.IOOpts{}) 149 if copyN == 0 { 150 // Nothing to copy. 151 return 0, err 152 } 153 buf = buf[:copyN] 154 155 // On Linux, if the NUL byte at the end of the argument vector has been 156 // overwritten, it continues reading the environment vector as part of 157 // the argument vector. 158 159 if f.arg == cmdlineExecArg && buf[copyN-1] != 0 { 160 // Linux will limit the return up to and including the first null character in argv 161 162 copyN = bytes.IndexByte(buf, 0) 163 if copyN == -1 { 164 copyN = len(buf) 165 } 166 // If we found a NUL character in argv, return upto and including that character. 167 if copyN < len(buf) { 168 buf = buf[:copyN] 169 } else { // Otherwise return into envp. 170 lengthEnvv := int(m.EnvvEnd() - m.EnvvStart()) 171 172 // Upstream limits the returned amount to one page of slop. 173 // https://elixir.bootlin.com/linux/v4.20/source/fs/proc/base.c#L208 174 // we'll return one page total between argv and envp because of the 175 // above page restrictions. 176 if lengthEnvv > hostarch.PageSize-len(buf) { 177 lengthEnvv = hostarch.PageSize - len(buf) 178 } 179 // Make a new buffer to fit the whole thing 180 tmp := make([]byte, length+lengthEnvv) 181 copyNE, err := m.CopyIn(ctx, m.EnvvStart(), tmp[copyN:], usermem.IOOpts{}) 182 if err != nil { 183 return 0, err 184 } 185 186 // Linux will return envp up to and including the first NUL character, so find it. 187 for i, c := range tmp[copyN:] { 188 if c == 0 { 189 copyNE = i 190 break 191 } 192 } 193 194 copy(tmp, buf) 195 buf = tmp[:copyN+copyNE] 196 197 } 198 199 } 200 201 n, dstErr := dst.CopyOut(ctx, buf) 202 if dstErr != nil { 203 return int64(n), dstErr 204 } 205 return int64(n), err 206 } 207 208 // LINT.ThenChange(../../fsimpl/proc/task.go)