github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/devices/nvproxy/uvm.go (about) 1 // Copyright 2023 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 nvproxy 16 17 import ( 18 "fmt" 19 20 "golang.org/x/sys/unix" 21 "github.com/nicocha30/gvisor-ligolo/pkg/abi/nvgpu" 22 "github.com/nicocha30/gvisor-ligolo/pkg/context" 23 "github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr" 24 "github.com/nicocha30/gvisor-ligolo/pkg/fdnotifier" 25 "github.com/nicocha30/gvisor-ligolo/pkg/hostarch" 26 "github.com/nicocha30/gvisor-ligolo/pkg/marshal" 27 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch" 28 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel" 29 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs" 30 "github.com/nicocha30/gvisor-ligolo/pkg/usermem" 31 "github.com/nicocha30/gvisor-ligolo/pkg/waiter" 32 ) 33 34 // uvmDevice implements vfs.Device for /dev/nvidia-uvm. 35 // 36 // +stateify savable 37 type uvmDevice struct { 38 nvp *nvproxy 39 } 40 41 // Open implements vfs.Device.Open. 42 func (dev *uvmDevice) Open(ctx context.Context, mnt *vfs.Mount, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { 43 hostFD, err := unix.Openat(-1, "/dev/nvidia-uvm", int((opts.Flags&unix.O_ACCMODE)|unix.O_NOFOLLOW), 0) 44 if err != nil { 45 ctx.Warningf("nvproxy: failed to open host /dev/nvidia-uvm: %v", err) 46 return nil, err 47 } 48 fd := &uvmFD{ 49 nvp: dev.nvp, 50 hostFD: int32(hostFD), 51 } 52 if err := fd.vfsfd.Init(fd, opts.Flags, mnt, vfsd, &vfs.FileDescriptionOptions{ 53 UseDentryMetadata: true, 54 }); err != nil { 55 unix.Close(hostFD) 56 return nil, err 57 } 58 if err := fdnotifier.AddFD(int32(hostFD), &fd.queue); err != nil { 59 unix.Close(hostFD) 60 return nil, err 61 } 62 fd.memmapFile.fd = fd 63 return &fd.vfsfd, nil 64 } 65 66 // uvmFD implements vfs.FileDescriptionImpl for /dev/nvidia-uvm. 67 // 68 // uvmFD is not savable; we do not implement save/restore of host GPU state. 69 type uvmFD struct { 70 vfsfd vfs.FileDescription 71 vfs.FileDescriptionDefaultImpl 72 vfs.DentryMetadataFileDescriptionImpl 73 vfs.NoLockFD 74 75 nvp *nvproxy 76 hostFD int32 77 memmapFile uvmFDMemmapFile 78 79 queue waiter.Queue 80 } 81 82 // Release implements vfs.FileDescriptionImpl.Release. 83 func (fd *uvmFD) Release(context.Context) { 84 fdnotifier.RemoveFD(fd.hostFD) 85 fd.queue.Notify(waiter.EventHUp) 86 unix.Close(int(fd.hostFD)) 87 } 88 89 // EventRegister implements waiter.Waitable.EventRegister. 90 func (fd *uvmFD) EventRegister(e *waiter.Entry) error { 91 fd.queue.EventRegister(e) 92 if err := fdnotifier.UpdateFD(fd.hostFD); err != nil { 93 fd.queue.EventUnregister(e) 94 return err 95 } 96 return nil 97 } 98 99 // EventUnregister implements waiter.Waitable.EventUnregister. 100 func (fd *uvmFD) EventUnregister(e *waiter.Entry) { 101 fd.queue.EventUnregister(e) 102 if err := fdnotifier.UpdateFD(fd.hostFD); err != nil { 103 panic(fmt.Sprint("UpdateFD:", err)) 104 } 105 } 106 107 // Readiness implements waiter.Waitable.Readiness. 108 func (fd *uvmFD) Readiness(mask waiter.EventMask) waiter.EventMask { 109 return fdnotifier.NonBlockingPoll(fd.hostFD, mask) 110 } 111 112 // Epollable implements vfs.FileDescriptionImpl.Epollable. 113 func (fd *uvmFD) Epollable() bool { 114 return true 115 } 116 117 // Ioctl implements vfs.FileDescriptionImpl.Ioctl. 118 func (fd *uvmFD) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error) { 119 cmd := args[1].Uint() 120 argPtr := args[2].Pointer() 121 122 t := kernel.TaskFromContext(ctx) 123 if t == nil { 124 panic("Ioctl should be called from a task context") 125 } 126 127 ui := uvmIoctlState{ 128 fd: fd, 129 ctx: ctx, 130 t: t, 131 cmd: cmd, 132 ioctlParamsAddr: argPtr, 133 } 134 135 switch cmd { 136 case nvgpu.UVM_INITIALIZE: 137 return uvmInitialize(&ui) 138 case nvgpu.UVM_DEINITIALIZE: 139 return uvmIoctlInvoke[byte](&ui, nil) 140 case nvgpu.UVM_CREATE_RANGE_GROUP: 141 return uvmIoctlSimple[nvgpu.UVM_CREATE_RANGE_GROUP_PARAMS](&ui) 142 case nvgpu.UVM_DESTROY_RANGE_GROUP: 143 return uvmIoctlSimple[nvgpu.UVM_DESTROY_RANGE_GROUP_PARAMS](&ui) 144 case nvgpu.UVM_REGISTER_GPU_VASPACE: 145 return uvmIoctlHasRMCtrlFD[nvgpu.UVM_REGISTER_GPU_VASPACE_PARAMS](&ui) 146 case nvgpu.UVM_UNREGISTER_GPU_VASPACE: 147 return uvmIoctlSimple[nvgpu.UVM_UNREGISTER_GPU_VASPACE_PARAMS](&ui) 148 case nvgpu.UVM_REGISTER_CHANNEL: 149 return uvmIoctlHasRMCtrlFD[nvgpu.UVM_REGISTER_CHANNEL_PARAMS](&ui) 150 case nvgpu.UVM_UNREGISTER_CHANNEL: 151 return uvmIoctlSimple[nvgpu.UVM_UNREGISTER_CHANNEL_PARAMS](&ui) 152 case nvgpu.UVM_MAP_EXTERNAL_ALLOCATION: 153 return uvmIoctlHasRMCtrlFD[nvgpu.UVM_MAP_EXTERNAL_ALLOCATION_PARAMS](&ui) 154 case nvgpu.UVM_FREE: 155 return uvmIoctlSimple[nvgpu.UVM_FREE_PARAMS](&ui) 156 case nvgpu.UVM_REGISTER_GPU: 157 return uvmIoctlHasRMCtrlFD[nvgpu.UVM_REGISTER_GPU_PARAMS](&ui) 158 case nvgpu.UVM_UNREGISTER_GPU: 159 return uvmIoctlSimple[nvgpu.UVM_UNREGISTER_GPU_PARAMS](&ui) 160 case nvgpu.UVM_PAGEABLE_MEM_ACCESS: 161 return uvmIoctlSimple[nvgpu.UVM_PAGEABLE_MEM_ACCESS_PARAMS](&ui) 162 case nvgpu.UVM_MAP_DYNAMIC_PARALLELISM_REGION: 163 return uvmIoctlSimple[nvgpu.UVM_MAP_DYNAMIC_PARALLELISM_REGION_PARAMS](&ui) 164 case nvgpu.UVM_ALLOC_SEMAPHORE_POOL: 165 return uvmIoctlSimple[nvgpu.UVM_ALLOC_SEMAPHORE_POOL_PARAMS](&ui) 166 case nvgpu.UVM_VALIDATE_VA_RANGE: 167 return uvmIoctlSimple[nvgpu.UVM_VALIDATE_VA_RANGE_PARAMS](&ui) 168 case nvgpu.UVM_CREATE_EXTERNAL_RANGE: 169 return uvmIoctlSimple[nvgpu.UVM_CREATE_EXTERNAL_RANGE_PARAMS](&ui) 170 default: 171 ctx.Warningf("nvproxy: unknown uvm ioctl %d", cmd) 172 return 0, linuxerr.EINVAL 173 } 174 } 175 176 // uvmIoctlState holds the state of a call to uvmFD.Ioctl(). 177 type uvmIoctlState struct { 178 fd *uvmFD 179 ctx context.Context 180 t *kernel.Task 181 cmd uint32 182 ioctlParamsAddr hostarch.Addr 183 } 184 185 func uvmIoctlSimple[Params any, PParams marshalPtr[Params]](ui *uvmIoctlState) (uintptr, error) { 186 var ioctlParams Params 187 if _, err := (PParams)(&ioctlParams).CopyIn(ui.t, ui.ioctlParamsAddr); err != nil { 188 return 0, err 189 } 190 n, err := uvmIoctlInvoke(ui, &ioctlParams) 191 if err != nil { 192 return n, err 193 } 194 if _, err := (PParams)(&ioctlParams).CopyOut(ui.t, ui.ioctlParamsAddr); err != nil { 195 return n, err 196 } 197 return n, nil 198 } 199 200 func uvmInitialize(ui *uvmIoctlState) (uintptr, error) { 201 var ioctlParams nvgpu.UVM_INITIALIZE_PARAMS 202 if _, err := ioctlParams.CopyIn(ui.t, ui.ioctlParamsAddr); err != nil { 203 return 0, err 204 } 205 sentryIoctlParams := ioctlParams 206 // This is necessary to share the host UVM FD between sentry and 207 // application processes. 208 sentryIoctlParams.Flags = ioctlParams.Flags | nvgpu.UVM_INIT_FLAGS_MULTI_PROCESS_SHARING_MODE 209 n, err := uvmIoctlInvoke(ui, &sentryIoctlParams) 210 if err != nil { 211 return n, err 212 } 213 outIoctlParams := sentryIoctlParams 214 // Only expose the MULTI_PROCESS_SHARING_MODE flag if it was present in 215 // ioctlParams. 216 outIoctlParams.Flags &^= ^ioctlParams.Flags & nvgpu.UVM_INIT_FLAGS_MULTI_PROCESS_SHARING_MODE 217 if _, err := outIoctlParams.CopyOut(ui.t, ui.ioctlParamsAddr); err != nil { 218 return n, err 219 } 220 return n, nil 221 } 222 223 type hasRMCtrlFDPtr[T any] interface { 224 *T 225 marshal.Marshallable 226 nvgpu.HasRMCtrlFD 227 } 228 229 func uvmIoctlHasRMCtrlFD[Params any, PParams hasRMCtrlFDPtr[Params]](ui *uvmIoctlState) (uintptr, error) { 230 var ioctlParams Params 231 if _, err := (PParams)(&ioctlParams).CopyIn(ui.t, ui.ioctlParamsAddr); err != nil { 232 return 0, err 233 } 234 235 rmCtrlFD := (PParams)(&ioctlParams).GetRMCtrlFD() 236 if rmCtrlFD < 0 { 237 n, err := uvmIoctlInvoke(ui, &ioctlParams) 238 if err != nil { 239 return n, err 240 } 241 if _, err := (PParams)(&ioctlParams).CopyOut(ui.t, ui.ioctlParamsAddr); err != nil { 242 return n, err 243 } 244 return n, nil 245 } 246 247 ctlFileGeneric, _ := ui.t.FDTable().Get(rmCtrlFD) 248 if ctlFileGeneric == nil { 249 return 0, linuxerr.EINVAL 250 } 251 defer ctlFileGeneric.DecRef(ui.ctx) 252 ctlFile, ok := ctlFileGeneric.Impl().(*frontendFD) 253 if !ok { 254 return 0, linuxerr.EINVAL 255 } 256 257 sentryIoctlParams := ioctlParams 258 (PParams)(&sentryIoctlParams).SetRMCtrlFD(ctlFile.hostFD) 259 n, err := uvmIoctlInvoke(ui, &sentryIoctlParams) 260 if err != nil { 261 return n, err 262 } 263 264 outIoctlParams := sentryIoctlParams 265 (PParams)(&outIoctlParams).SetRMCtrlFD(rmCtrlFD) 266 if _, err := (PParams)(&outIoctlParams).CopyOut(ui.t, ui.ioctlParamsAddr); err != nil { 267 return n, err 268 } 269 270 return n, nil 271 }