Some time ago on the VMware usenet server, some users
had asked for a way to detect that Linux is being run
in a VMware guest.

I finally posted such code.  I'm including it here
for fun.  It works for user (application) code.  So for
example if you want a shell script or user program to take
an alternate action if it realizes it is running in a VM,
then you could use this code.

BTW, I'm making some good progress on assembling the collective
virtualization paper.  Should be done with a first go, very soon.
We did have some good discussions in the past, and it's
been fun putting it together in one doc.

-Kevin
/*
 *  vmbeware: Detect that your user level Linux application is being
 *            run in VMware.
 *  Copyright (C) 1999  Kevin P. Lawton (http://www.FreeMWare.org)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *  (To keep things small, I refer you to
 *    http://www.gnu.org/copyleft/lesser.html)
 */


#include <stdio.h>
#include <sys/utsname.h>
#include <string.h>


// Notes:
//   - This program (or library) is useful if you need for a
//     user level Linux application to change a strategy or
//     for a shell program to do something different, depending
//     upon whether your Linux OS is being run in a virtualized
//     VMware environment.
//
//   - Feel free to strip out fprintf() statements.  They may be
//     annoying, if you're linking with this code.
//
//   - Feel free to strip out the main() function, so you can
//     link this with your code.  I have it in here for demonstration,
//     or if you want to use this as a standalone program, which
//     sets a return status code for a shell.
//
//   - I have 2 methods in here.  The 1st one only works on
//     Linux >= 2.1.*.  The 2nd one works pre and post 2.1.*
//     Linux guests that I tried this on.  So I default to that
//     one.


#define DETECT_METHOD 2



int vmbeware(void);



  int
main()
{
  int r;

  r = vmbeware();
  fprintf(stderr, "returns %d\n", r);
  return(r);
}




#if DETECT_METHOD == 1
  // Method 1.  This method only works on Linux >= 2.1.*.  VMware
  // shortens the segment limit as part of it's virtualization
  // strategies, if the segment spans the entire 32-bit space.
  // Linux >= 2.1.* uses segments which span the full 32-bit address
  // space, and instead uses the paging system to handle protection
  // access of user vs kernel code instead.  So we can
  // just look to see if VMware shortened our segment limits.

  int
vmbeware(void)
{
  // Returns:
  //   negative = error
  //   0 = Linux is host OS (native)
  //   1 = Linux is guest OS (in VMware)

  struct utsname name;
  unsigned long limit_ds;
  int r;
  unsigned v1, v2, v3;

  // Get Linux release
  r = uname(&name);
  if (r != 0) {
    fprintf(stderr, "vmbeware: uname returns error.\n");
    return(-1);
    }
  // Convert to decimal
  fprintf(stderr, "release is %s\n", name.release);
  if (sscanf(name.release, "%u.%u.%u\n", &v1, &v2, &v3) != 3) {
    fprintf(stderr, "vmbeware: sscanf returns error.\n");
    return(-1);
    }

  // Linux < 2.1.0?
  if ( ((v1*256) + v2) < ((2*256) + 1) ) {
    fprintf(stderr, "vmbeware: this method only works on Linux >= 2.1.*.\n");
    return(-1);
    }


  // Check out segment limit
  asm volatile (
    "push %%ds \n"
    "pop  %%eax \n"
    "lsl  %%eax, %0 \n"
    : "=r" (limit_ds)
    :
    : "eax" // gets clobbered
    );

  fprintf(stderr, "LSL of DS: returned %08x\n", limit_ds);
  if (limit_ds != 0xffffffff)
    return(1); // shorted segment, in VMware
  return(0); // normal segment, not in VMware
}
#endif



#if DETECT_METHOD == 2
  // Method 2.  This method works for both pre and post Linux 2.1.*
  // guests.  VMware introduces an extra segment descriptor near the
  // end of the GDT, which is accessible by ring3 code.  (Its a
  // ring3 accessible LDT segment)  We can detect it with the
  // LAR and LSL instructions, which only set ZF, but don't
  // generate exceptions.

  int
vmbeware(void)
{
  // Returns:
  //   0 = Linux is host OS (native)
  //   1 = Linux is guest OS (in VMware)

  unsigned d;
  unsigned long cs_selector, ds_selector, zf, selector, val32;

  asm volatile (
    "push %%cs \n"
    "pop  %0 \n"
    : "=r" (cs_selector)
    );
  cs_selector &= 0xffff;
  fprintf(stderr, "CS selector was %x\n", cs_selector);

  asm volatile (
    "push %%ds \n"
    "pop  %0 \n"
    : "=r" (ds_selector)
    );
  ds_selector &= 0xffff;
  fprintf(stderr, "DS selector was %x\n", ds_selector);


  // Check GDT
  fprintf(stderr, "Searching GDT\n");
  for (d=0; d<(1<<13); d++) {
    // selector/TI/RPL
    selector = (d<<3) | (0<<2) | 3;
    val32 = 0;
    zf = 0;
    asm volatile (
      "lar  %%eax, %0 \n"
      "mov  $0x0, %1 \n"
      "jnz  gdt_notzf \n"
      "mov  $0x1, %1 \n"
      "gdt_notzf:\n"
      : "=r" (val32), "=r" (zf)
      : "eax" (selector)
      );
    if (zf) {
      if ( (selector!=cs_selector) && (selector!=ds_selector) ) {
        fprintf(stderr, "  found visible descriptor at selector %x\n",
                selector);
        fprintf(stderr, "LAR returned %08x\n", val32);
        asm volatile (
          "lsl  %%eax, %0 \n"
          : "=r" (val32)
          : "eax" (selector)
          );
        fprintf(stderr, "LSL returned %08x\n", val32);
        return(1);
        }
      }
    }

  return(0);
}
#endif

Reply via email to