Technically speaking, the GLX spec requires that GLX drawables be accessible from other processes, so when using a conformant GLX implementation with VirtualGL's GLX back end, it is absolutely possible to snoop other users' Pbuffers.  I am attaching example code that demonstrates how to do that if you know the Pbuffer ID.

In practice, this works with nVidia's drivers, because those drivers use DRI2 (which allocates GPU buffers through the X server.)  It doesn't work with Mesa-based drivers, because those drivers use DRI3 (which allocates GPU buffers in the X client.) DRI3-based drivers aren't technically conformant with the GLX spec, because they disallow sharing GLX drawables across process boundaries.  However, that was always a really esoteric and unreliably supported use case, and no modern OpenGL applications of which I'm aware try to do that.  Thus, DRI3-based drivers gain a bit of security at the expense of sacrificing conformance with a GLX feature that few applications ever used.

Pbuffer snooping can be thwarted either by denying the snooper access to the 3D X server or denying the snooper access to the /dev/nvidia* devices, which is why vglserver_config allows both to be configured separately.  (NOTE: If you restrict framebuffer device access to the vglusers group, then the account under which the display manager runs-- gdm, for instance-- has to be in that group.)

Now as to the EGL back end, it uses renderbuffer objects to emulate GLX Pbuffers, and to the best of my understanding, RBOs cannot be shared among multiple processes.  In fact, because RBOs are confined to a specific OpenGL context, it took some effort to implement the EGL back end in such a way that the RBOs could be shared among multiple contexts in the same process (a critical feature of GLX Pbuffers.)  Thus, securing the framebuffer devices is probably a belt-and-suspenders measure if you are only using the EGL back end.

There is a bit of history behind that as well.  Back in the dawn of time (the early 2000s) before EGL existed, Sun Microsystems had an API called GLP that was specific to their framebuffers.  GLP allowed you to create Pbuffer contexts using a framebuffer device rather than a 3D X server, and it was possible to snoop other users' Pbuffers through the GLP device.  Thus, it was necessary to provide sysadmins with the ability to secure the GLP devices. When I implemented the EGL back end, I wanted to give sysadmins the same ability with EGL devices.  The truth is that I haven't dug into the issue enough to know whether a snooping exploit is possible with the EGL back end.  It probably isn't, but I wanted to allow for the possibility that it is.

I will look into clarifying the documentation.


On 7/5/24 9:50 PM, Takanori Nakane wrote:
Dear VirtualGL community,

I would like to know security implications of the EGL backend.
The document https://rawcdn.githack.com/VirtualGL/virtualgl/3.1.1/doc/index.html#hd006003 says:

> When using the EGL back end, the only way to share the application server’s GPU(s) among multiple users is to grant those users access to the device(s) associated with the GPU(s).
> ...
> Yes
Only users in the vglusers group can run OpenGL applications on the VirtualGL server (the configuration script will create the vglusers group if it doesn’t already exist.) This limits the possibility that an unauthorized user could snoop the 3D framebuffer device(s) and thus see (or alter) the output of a 3D application that is being used with VirtualGL.
> No
Any authenticated user can run OpenGL applications on the VirtualGL server. If it is necessary for users outside of the vglusers group to log in locally to this server and run OpenGL applications, then this option must be selected.

My confusion here is whether a user can snoop and interfere with other users' 3D applications. My intuition is no, because processes are isolated by kernel (just as one user cannot touch another user's CUDA application). But then, what is the point of limiting GPU access to the vglusers group? Is the restriction an *additional* layer of caution just in case there is a security flaw in the GPU driver or kernel?

For the GLX background, this old thread https://virtualgl-users.narkive.com/KHab71sF/security-issues-for-virtualgl clarifies the situation.
When sharing an X server (:0),
1. A *remote* user can snoop *local* X user's keystrokes but not other *remote* user's.
2. A *remote* user can snoop other users' (local or remote) 3D rendering.
(I think it might be useful if this is clarified in the documentation)

Best regards,

Takanori Nakane

--
You received this message because you are subscribed to the Google Groups "VirtualGL 
User Discussion/Support" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/virtualgl-users/59cdc747-e9c7-4fa3-aaa3-dba8afe14ee5%40virtualgl.org.
#include <stdio.h>
#include <stdlib.h>
#include <GL/glx.h>

#define THROW(m) { \
  fprintf(stderr, "ERROR in line %d:\n%s\n", __LINE__, m); \
  retval = -1; \
  goto bailout; \
}

int main(int argc, char **argv)
{
  Display *dpy = NULL;
  GLXPbuffer pb;
  int retval = 0, w, h, fbcid, attribs[3] = { GLX_FBCONFIG_ID, 0, 0 },
    n;
  GLXFBConfig *configs = NULL;
  GLXContext ctx = 0;
  FILE *file = NULL;
  GLubyte *buf = NULL;
  int i;

  if (argc < 2 || sscanf(argv[1], "%x", &pb) < 1 || pb < 1) {
    fprintf(stderr, "USAGE: %s <Pbuffer ID>\n", argv[0]);
    return 1;
  }

  fprintf(stderr, "Pbuffer:\n  Drawable ID: 0x%.8lx\n", (unsigned long)pb);

  if (!(dpy = XOpenDisplay(NULL)))
    THROW("Could not open X display");
  glXQueryDrawable(dpy, pb, GLX_WIDTH, &w);
  glXQueryDrawable(dpy, pb, GLX_HEIGHT, &h);
  glXQueryDrawable(dpy, pb, GLX_FBCONFIG_ID, &fbcid);

  fprintf(stderr, "  Dimensions: %d x %d\n  GLXFBConfig ID: 0x%.3x\n", w, h,
          fbcid);

  attribs[1] = fbcid;
  if (!(configs = glXChooseFBConfig(dpy, DefaultScreen(dpy), attribs, &n)) ||
      n < 1)
    THROW("Could not obtain GLXFBConfig");
  if (!(ctx = glXCreateNewContext(dpy, configs[0], GLX_RGBA_TYPE, NULL, True)))
    THROW("Could not create GLX context");
  if (!glXMakeCurrent(dpy, pb, ctx))
    THROW("Could not make GLX context current");

  if (!(buf = (GLubyte *)malloc(w * h * 3)))
    THROW("Could not allocate image buffer");

  glPixelStorei(GL_PACK_ALIGNMENT, 1);
  glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, buf);

  if (!(file = fopen("getpb.ppm", "wb")))
    THROW("Could not open output file");
  fprintf(file, "P6\n%d %d\n255\n", w, h);
  for (i = 0; i < h; i++) {
    if (fwrite(&buf[(h - i - 1) * w * 3], 1, w * 3, file) < 1)
      THROW("Could not write to output file");
  }

  bailout:
  if (file) fclose(file);
  free(buf);
  if (dpy && ctx) glXDestroyContext(dpy, ctx);
  if (configs) XFree(configs);
  if (dpy) XCloseDisplay(dpy);
  return retval;
}

Reply via email to