github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/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  	"github.com/ttpreport/gvisor-ligolo/pkg/abi/nvgpu"
    21  	"github.com/ttpreport/gvisor-ligolo/pkg/context"
    22  	"github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr"
    23  	"github.com/ttpreport/gvisor-ligolo/pkg/fdnotifier"
    24  	"github.com/ttpreport/gvisor-ligolo/pkg/hostarch"
    25  	"github.com/ttpreport/gvisor-ligolo/pkg/marshal"
    26  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/arch"
    27  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel"
    28  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs"
    29  	"github.com/ttpreport/gvisor-ligolo/pkg/usermem"
    30  	"github.com/ttpreport/gvisor-ligolo/pkg/waiter"
    31  	"golang.org/x/sys/unix"
    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  }