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 const 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 %%ebx; push %%edx\n\t"
56         "test %[pv],%[pv] ; jz 1f ; ud2a ; .ascii \"xen\" ; 1: cpuid\n\t"
57         "mov %%eax,(%[regs]); mov %%ebx,4(%[regs])\n\t"
58         "mov %%ecx,8(%[regs]); mov %%edx,12(%[regs])\n\t"
59         "pop %%edx; pop %%ebx\n\t"
60         : "+a" (idx), "=c" (idx /* dummy */)
61         : "c" (0), [pv] "r" (pv_context), [regs] "SD" (regs)
62         : "memory" );
63 #else
64     asm volatile (
65         "test %[pv],%[pv] ; jz 1f ; ud2a ; .ascii \"xen\" ; 1: cpuid\n\t"
66         : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3])
67         : "0" (idx), "2" (0), [pv] "r" (pv_context) );
68 #endif
69 }
70 
check_for_xen(int pv_context)71 static int check_for_xen(int pv_context)
72 {
73     uint32_t regs[4];
74     char signature[13];
75     uint32_t base;
76 
77     for ( base = 0x40000000; base < 0x40010000; base += 0x100 )
78     {
79         cpuid(base, regs, pv_context);
80 
81         *(uint32_t *)(signature + 0) = regs[1];
82         *(uint32_t *)(signature + 4) = regs[2];
83         *(uint32_t *)(signature + 8) = regs[3];
84         signature[12] = '\0';
85 
86         if ( !strcmp("XenVMMXenVMM", signature) && (regs[0] >= (base + 2)) )
87             goto found;
88 
89         /* Higher base addresses are possible only with HVM. */
90         if ( pv_context )
91             break;
92     }
93 
94     return 0;
95 
96  found:
97     /*
98      * On CPUID faulting capable hardware even un-escaped CPUID will return
99      * the hypervisor leaves. Need to further distinguish modes.
100      */
101     if ( !pv_context )
102     {
103         /*
104          * XEN_CPUID_FEAT1_MMU_PT_UPDATE_PRESERVE_AD is a PV-only feature
105          * pre-dating CPUID faulting support in Xen. Hence we can use it to
106          * tell whether we shouldn't report "success" to our caller here.
107          */
108         cpuid(base + 2, regs, 0);
109         if ( regs[2] & (1u << 0) )
110             return 0;
111     }
112 
113     cpuid(base + 1, regs, pv_context);
114     if ( regs[0] )
115     {
116         int r = asprintf(&ver, "V%u.%u", (uint16_t)(regs[0] >> 16),
117                          (uint16_t)regs[0]);
118         if ( r < 0 )
119         {
120             perror("asprintf failed\n");
121             exit(EXIT_FAILURE);
122         }
123     }
124     return regs[0];
125 }
126 
127 static jmp_buf sigill_jmp;
sigill_handler(int sig)128 void sigill_handler(int sig)
129 {
130     longjmp(sigill_jmp, 1);
131 }
132 
usage(void)133 static void usage(void)
134 {
135     printf("Usage: xen_detect [options]\n");
136     printf("Options:\n");
137     printf("  -h, --help    Display this information\n");
138     printf("  -q, --quiet   Quiesce normal informational output\n");
139     printf("  -P, --pv      Exit status 1 if not running as PV guest\n");
140     printf("  -H, --hvm     Exit status 1 if not running as HVM or PVH guest.\n");
141     printf("  -N, --none    Exit status 1 if running on Xen (PV or HVM)\n");
142 }
143 
check_dir(const char * filename)144 static bool check_dir(const char *filename)
145 {
146     FILE *f;
147     struct stat stab;
148     bool res;
149 
150     f = fopen(filename, "r");
151     if ( !f )
152         return false;
153     res = !fstat(fileno(f), &stab) && S_ISDIR(stab.st_mode);
154     fclose(f);
155 
156     return res;
157 }
158 
read_file_content(const char * filename)159 static char *read_file_content(const char *filename)
160 {
161     FILE *f;
162     struct stat stab;
163     char *content = NULL;
164     int datalen;
165 
166     f = fopen(filename, "r");
167     if ( !f )
168         return NULL;
169 
170     if ( fstat(fileno(f), &stab) || !S_ISREG(stab.st_mode) ||
171          stab.st_size > INT_MAX || !stab.st_size )
172         goto out;
173 
174     content = malloc(stab.st_size + 1);
175     if ( !content )
176         goto out;
177 
178     /* For sysfs file, datalen is always PAGE_SIZE. 'read'
179      * will return the number of bytes of the actual content,
180      * rs <= datalen is expected.
181      */
182     datalen = fread(content, 1, stab.st_size, f);
183     content[datalen] = 0;
184     if ( ferror(f) )
185     {
186         free(content);
187         content = NULL;
188     }
189 
190  out:
191     fclose(f);
192     return content;
193 }
194 
check_sysfs(void)195 static enum guest_type check_sysfs(void)
196 {
197     char *str, *tmp;
198     enum guest_type res = XEN_NONE;
199 
200     if ( !check_dir("/sys/hypervisor") )
201         return 0;
202 
203     str = read_file_content("/sys/hypervisor/type");
204     if ( !str || strcmp(str, "xen\n") )
205         goto out;
206     free(str);
207 
208     str = read_file_content("/sys/hypervisor/guest_type");
209     if ( !str )
210         return 0;
211     str[strlen(str) - 1] = 0;
212     type = str;
213     if ( !strcmp(type, "PV") )
214         res = XEN_PV;
215     else
216         res = XEN_HVM;
217 
218     str = read_file_content("/sys/hypervisor/version/major");
219     if ( str )
220         str[strlen(str) - 1] = 0;
221     tmp = read_file_content("/sys/hypervisor/version/minor");
222     if ( tmp )
223         tmp[strlen(tmp) - 1] = 0;
224     if ( str && tmp )
225     {
226         int r = asprintf(&ver, "V%s.%s", str, tmp);
227         if ( r < 0 )
228         {
229             perror("asprintf failed\n");
230             exit(EXIT_FAILURE);
231         }
232     } else
233         ver = strdup("unknown version");
234     free(tmp);
235 
236  out:
237     free(str);
238     return res;
239 }
240 
main(int argc,char ** argv)241 int main(int argc, char **argv)
242 {
243     enum guest_type detected, expected = 0;
244     int ch, quiet = 0;
245 
246     const static char sopts[] = "hqPHN";
247     const static struct option lopts[] = {
248         { "help",  0, NULL, 'h' },
249         { "quiet", 0, NULL, 'q' },
250         { "pv",    0, NULL, 'P' },
251         { "hvm",   0, NULL, 'H' },
252         { "none",  0, NULL, 'N' },
253         { 0, 0, 0, 0}
254     };
255 
256     while ( (ch = getopt_long(argc, argv, sopts, lopts, NULL)) != -1 )
257     {
258         switch ( ch )
259         {
260         case 'q':
261             quiet = 1;
262             break;
263         case 'P':
264             expected = XEN_PV;
265             break;
266         case 'H':
267             expected = XEN_HVM;
268             break;
269         case 'N':
270             expected = XEN_NONE;
271             break;
272         default:
273             usage();
274             exit(1);
275         }
276     }
277 
278     detected = check_sysfs();
279     if ( detected )
280         goto out;
281 
282     /* Check for execution in HVM context. */
283     detected = XEN_HVM;
284     type = "HVM";
285     if ( check_for_xen(0) )
286         goto out;
287 
288     /*
289      * Set up a signal handler to test the paravirtualised CPUID instruction.
290      * If executed outside Xen PV context, the extended opcode will fault, we
291      * will longjmp via the signal handler, and print "Not running on Xen".
292      */
293     detected = XEN_PV;
294     type = "PV";
295     if ( !setjmp(sigill_jmp)
296          && (signal(SIGILL, sigill_handler) != SIG_ERR)
297          && check_for_xen(1) )
298         goto out;
299 
300     detected = XEN_NONE;
301 
302  out:
303     if ( quiet )
304         /* nothing */;
305     else if ( detected == XEN_NONE )
306         printf("Not running on Xen.\n");
307     else
308         printf("Running in %s context on Xen %s.\n", type, ver);
309 
310     free(ver);
311 
312     return expected && (expected != detected);
313 }
314