1 /******************************************************************************
2  * xen_detect.c
3  *
4  * Simple GNU C / POSIX application to detect execution on Xen VMM platform.
5  *
6  * Copyright (c) 2007, XenSource Inc.
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a copy
9  * of this software and associated documentation files (the "Software"), to
10  * deal in the Software without restriction, including without limitation the
11  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12  * sell copies of the Software, and to permit persons to whom the Software is
13  * furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24  * DEALINGS IN THE SOFTWARE.
25  */
26 
27 #define _GNU_SOURCE
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <limits.h>
31 #include <stdbool.h>
32 #include <stdint.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <setjmp.h>
37 #include <signal.h>
38 #include <unistd.h>
39 #include <getopt.h>
40 
41 enum guest_type {
42     XEN_PV = 1,
43     XEN_HVM = 2,
44     XEN_NONE = 3
45 };
46 
47 static char *type;
48 static char *ver;
49 
cpuid(uint32_t idx,uint32_t * regs,int pv_context)50 static void cpuid(uint32_t idx, uint32_t *regs, int pv_context)
51 {
52 #ifdef __i386__
53     /* Use the stack to avoid reg constraint failures with some gcc flags */
54     asm volatile (
55         "push %%eax; push %%ebx; push %%ecx; push %%edx\n\t"
56         "test %1,%1 ; jz 1f ; ud2a ; .ascii \"xen\" ; 1: cpuid\n\t"
57         "mov %%eax,(%2); mov %%ebx,4(%2)\n\t"
58         "mov %%ecx,8(%2); mov %%edx,12(%2)\n\t"
59         "pop %%edx; pop %%ecx; pop %%ebx; pop %%eax\n\t"
60         : : "a" (idx), "c" (pv_context), "S" (regs) : "memory" );
61 #else
62     asm volatile (
63         "test %5,%5 ; jz 1f ; ud2a ; .ascii \"xen\" ; 1: cpuid\n\t"
64         : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3])
65         : "0" (idx), "1" (pv_context), "2" (0) );
66 #endif
67 }
68 
check_for_xen(int pv_context)69 static int check_for_xen(int pv_context)
70 {
71     uint32_t regs[4];
72     char signature[13];
73     uint32_t base;
74 
75     for ( base = 0x40000000; base < 0x40010000; base += 0x100 )
76     {
77         cpuid(base, regs, pv_context);
78 
79         *(uint32_t *)(signature + 0) = regs[1];
80         *(uint32_t *)(signature + 4) = regs[2];
81         *(uint32_t *)(signature + 8) = regs[3];
82         signature[12] = '\0';
83 
84         if ( !strcmp("XenVMMXenVMM", signature) && (regs[0] >= (base + 2)) )
85             goto found;
86     }
87 
88     return 0;
89 
90  found:
91     cpuid(base + 1, regs, pv_context);
92     if ( regs[0] )
93     {
94         int r = asprintf(&ver, "V%u.%u", (uint16_t)(regs[0] >> 16),
95                          (uint16_t)regs[0]);
96         if ( r < 0 )
97         {
98             perror("asprintf failed\n");
99             exit(EXIT_FAILURE);
100         }
101     }
102     return regs[0];
103 }
104 
105 static jmp_buf sigill_jmp;
sigill_handler(int sig)106 void sigill_handler(int sig)
107 {
108     longjmp(sigill_jmp, 1);
109 }
110 
usage(void)111 static void usage(void)
112 {
113     printf("Usage: xen_detect [options]\n");
114     printf("Options:\n");
115     printf("  -h, --help    Display this information\n");
116     printf("  -q, --quiet   Quiesce normal informational output\n");
117     printf("  -P, --pv      Exit status 1 if not running as PV guest\n");
118     printf("  -H, --hvm     Exit status 1 if not running as HVM or PVH guest.\n");
119     printf("  -N, --none    Exit status 1 if running on Xen (PV or HVM)\n");
120 }
121 
check_dir(const char * filename)122 static bool check_dir(const char *filename)
123 {
124     FILE *f;
125     struct stat stab;
126     bool res;
127 
128     f = fopen(filename, "r");
129     if ( !f )
130         return false;
131     res = !fstat(fileno(f), &stab) && S_ISDIR(stab.st_mode);
132     fclose(f);
133 
134     return res;
135 }
136 
read_file_content(const char * filename)137 static char *read_file_content(const char *filename)
138 {
139     FILE *f;
140     struct stat stab;
141     char *content = NULL;
142     int datalen;
143 
144     f = fopen(filename, "r");
145     if ( !f )
146         return NULL;
147 
148     if ( fstat(fileno(f), &stab) || !S_ISREG(stab.st_mode) ||
149          stab.st_size > INT_MAX || !stab.st_size )
150         goto out;
151 
152     content = malloc(stab.st_size + 1);
153     if ( !content )
154         goto out;
155 
156     /* For sysfs file, datalen is always PAGE_SIZE. 'read'
157      * will return the number of bytes of the actual content,
158      * rs <= datalen is expected.
159      */
160     datalen = fread(content, 1, stab.st_size, f);
161     content[datalen] = 0;
162     if ( ferror(f) )
163     {
164         free(content);
165         content = NULL;
166     }
167 
168  out:
169     fclose(f);
170     return content;
171 }
172 
check_sysfs(void)173 static enum guest_type check_sysfs(void)
174 {
175     char *str, *tmp;
176     enum guest_type res = XEN_NONE;
177 
178     if ( !check_dir("/sys/hypervisor") )
179         return 0;
180 
181     str = read_file_content("/sys/hypervisor/type");
182     if ( !str || strcmp(str, "xen\n") )
183         goto out;
184     free(str);
185 
186     str = read_file_content("/sys/hypervisor/guest_type");
187     if ( !str )
188         return 0;
189     str[strlen(str) - 1] = 0;
190     type = str;
191     if ( !strcmp(type, "PV") )
192         res = XEN_PV;
193     else
194         res = XEN_HVM;
195 
196     str = read_file_content("/sys/hypervisor/version/major");
197     if ( str )
198         str[strlen(str) - 1] = 0;
199     tmp = read_file_content("/sys/hypervisor/version/minor");
200     if ( tmp )
201         tmp[strlen(tmp) - 1] = 0;
202     if ( str && tmp )
203     {
204         int r = asprintf(&ver, "V%s.%s", str, tmp);
205         if ( r < 0 )
206         {
207             perror("asprintf failed\n");
208             exit(EXIT_FAILURE);
209         }
210     } else
211         ver = strdup("unknown version");
212     free(tmp);
213 
214  out:
215     free(str);
216     return res;
217 }
218 
main(int argc,char ** argv)219 int main(int argc, char **argv)
220 {
221     enum guest_type detected, expected = 0;
222     int ch, quiet = 0;
223 
224     const static char sopts[] = "hqPHN";
225     const static struct option lopts[] = {
226         { "help",  0, NULL, 'h' },
227         { "quiet", 0, NULL, 'q' },
228         { "pv",    0, NULL, 'P' },
229         { "hvm",   0, NULL, 'H' },
230         { "none",  0, NULL, 'N' },
231         { 0, 0, 0, 0}
232     };
233 
234     while ( (ch = getopt_long(argc, argv, sopts, lopts, NULL)) != -1 )
235     {
236         switch ( ch )
237         {
238         case 'q':
239             quiet = 1;
240             break;
241         case 'P':
242             expected = XEN_PV;
243             break;
244         case 'H':
245             expected = XEN_HVM;
246             break;
247         case 'N':
248             expected = XEN_NONE;
249             break;
250         default:
251             usage();
252             exit(1);
253         }
254     }
255 
256     detected = check_sysfs();
257     if ( detected )
258         goto out;
259 
260     /* Check for execution in HVM context. */
261     detected = XEN_HVM;
262     type = "HVM";
263     if ( check_for_xen(0) )
264         goto out;
265 
266     /*
267      * Set up a signal handler to test the paravirtualised CPUID instruction.
268      * If executed outside Xen PV context, the extended opcode will fault, we
269      * will longjmp via the signal handler, and print "Not running on Xen".
270      */
271     detected = XEN_PV;
272     type = "PV";
273     if ( !setjmp(sigill_jmp)
274          && (signal(SIGILL, sigill_handler) != SIG_ERR)
275          && check_for_xen(1) )
276         goto out;
277 
278     detected = XEN_NONE;
279 
280  out:
281     if ( quiet )
282         /* nothing */;
283     else if ( detected == XEN_NONE )
284         printf("Not running on Xen.\n");
285     else
286         printf("Running in %s context on Xen %s.\n", type, ver);
287 
288     free(ver);
289 
290     return expected && (expected != detected);
291 }
292