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
