• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * ldt_gdt.c - Test cases for LDT and GDT access
4  * Copyright (c) 2011-2015 Andrew Lutomirski
5  */
6 
7 #define _GNU_SOURCE
8 
9 #include <stdio.h>
10 #include <sys/time.h>
11 #include <time.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <sys/syscall.h>
15 #include <dlfcn.h>
16 #include <string.h>
17 #include <errno.h>
18 #include <sched.h>
19 #include <stdbool.h>
20 #include <limits.h>
21 
22 #include "vdso_config.h"
23 
24 static const char **name;
25 
26 #ifndef SYS_getcpu
27 # ifdef __x86_64__
28 #  define SYS_getcpu 309
29 # else
30 #  define SYS_getcpu 318
31 # endif
32 #endif
33 
34 #ifndef __NR_clock_gettime64
35 #define __NR_clock_gettime64	403
36 #endif
37 
38 #ifndef __kernel_timespec
39 struct __kernel_timespec {
40 	long long	tv_sec;
41 	long long	tv_nsec;
42 };
43 #endif
44 
45 /* max length of lines in /proc/self/maps - anything longer is skipped here */
46 #define MAPS_LINE_LEN 128
47 
48 int nerrs = 0;
49 
50 typedef int (*vgettime_t)(clockid_t, struct timespec *);
51 
52 vgettime_t vdso_clock_gettime;
53 
54 typedef int (*vgettime64_t)(clockid_t, struct __kernel_timespec *);
55 
56 vgettime64_t vdso_clock_gettime64;
57 
58 typedef long (*vgtod_t)(struct timeval *tv, struct timezone *tz);
59 
60 vgtod_t vdso_gettimeofday;
61 
62 typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
63 
64 getcpu_t vgetcpu;
65 getcpu_t vdso_getcpu;
66 
vsyscall_getcpu(void)67 static void *vsyscall_getcpu(void)
68 {
69 #ifdef __x86_64__
70 	FILE *maps;
71 	char line[MAPS_LINE_LEN];
72 	bool found = false;
73 
74 	maps = fopen("/proc/self/maps", "r");
75 	if (!maps) /* might still be present, but ignore it here, as we test vDSO not vsyscall */
76 		return NULL;
77 
78 	while (fgets(line, MAPS_LINE_LEN, maps)) {
79 		char r, x;
80 		void *start, *end;
81 		char name[MAPS_LINE_LEN];
82 
83 		/* sscanf() is safe here as strlen(name) >= strlen(line) */
84 		if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
85 			   &start, &end, &r, &x, name) != 5)
86 			continue;
87 
88 		if (strcmp(name, "[vsyscall]"))
89 			continue;
90 
91 		/* assume entries are OK, as we test vDSO here not vsyscall */
92 		found = true;
93 		break;
94 	}
95 
96 	fclose(maps);
97 
98 	if (!found) {
99 		printf("Warning: failed to find vsyscall getcpu\n");
100 		return NULL;
101 	}
102 	return (void *) (0xffffffffff600800);
103 #else
104 	return NULL;
105 #endif
106 }
107 
108 
fill_function_pointers()109 static void fill_function_pointers()
110 {
111 	void *vdso = dlopen("linux-vdso.so.1",
112 			    RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
113 	if (!vdso)
114 		vdso = dlopen("linux-gate.so.1",
115 			      RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
116 	if (!vdso) {
117 		printf("[WARN]\tfailed to find vDSO\n");
118 		return;
119 	}
120 
121 	vdso_getcpu = (getcpu_t)dlsym(vdso, name[4]);
122 	if (!vdso_getcpu)
123 		printf("Warning: failed to find getcpu in vDSO\n");
124 
125 	vgetcpu = (getcpu_t) vsyscall_getcpu();
126 
127 	vdso_clock_gettime = (vgettime_t)dlsym(vdso, name[1]);
128 	if (!vdso_clock_gettime)
129 		printf("Warning: failed to find clock_gettime in vDSO\n");
130 
131 #if defined(VDSO_32BIT)
132 	vdso_clock_gettime64 = (vgettime64_t)dlsym(vdso, name[5]);
133 	if (!vdso_clock_gettime64)
134 		printf("Warning: failed to find clock_gettime64 in vDSO\n");
135 #endif
136 
137 	vdso_gettimeofday = (vgtod_t)dlsym(vdso, name[0]);
138 	if (!vdso_gettimeofday)
139 		printf("Warning: failed to find gettimeofday in vDSO\n");
140 
141 }
142 
sys_getcpu(unsigned * cpu,unsigned * node,void * cache)143 static long sys_getcpu(unsigned * cpu, unsigned * node,
144 		       void* cache)
145 {
146 	return syscall(__NR_getcpu, cpu, node, cache);
147 }
148 
sys_clock_gettime(clockid_t id,struct timespec * ts)149 static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
150 {
151 	return syscall(__NR_clock_gettime, id, ts);
152 }
153 
sys_clock_gettime64(clockid_t id,struct __kernel_timespec * ts)154 static inline int sys_clock_gettime64(clockid_t id, struct __kernel_timespec *ts)
155 {
156 	return syscall(__NR_clock_gettime64, id, ts);
157 }
158 
sys_gettimeofday(struct timeval * tv,struct timezone * tz)159 static inline int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
160 {
161 	return syscall(__NR_gettimeofday, tv, tz);
162 }
163 
test_getcpu(void)164 static void test_getcpu(void)
165 {
166 	printf("[RUN]\tTesting getcpu...\n");
167 
168 	for (int cpu = 0; ; cpu++) {
169 		cpu_set_t cpuset;
170 		CPU_ZERO(&cpuset);
171 		CPU_SET(cpu, &cpuset);
172 		if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
173 			return;
174 
175 		unsigned cpu_sys, cpu_vdso, cpu_vsys,
176 			node_sys, node_vdso, node_vsys;
177 		long ret_sys, ret_vdso = 1, ret_vsys = 1;
178 		unsigned node;
179 
180 		ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
181 		if (vdso_getcpu)
182 			ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
183 		if (vgetcpu)
184 			ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
185 
186 		if (!ret_sys)
187 			node = node_sys;
188 		else if (!ret_vdso)
189 			node = node_vdso;
190 		else if (!ret_vsys)
191 			node = node_vsys;
192 
193 		bool ok = true;
194 		if (!ret_sys && (cpu_sys != cpu || node_sys != node))
195 			ok = false;
196 		if (!ret_vdso && (cpu_vdso != cpu || node_vdso != node))
197 			ok = false;
198 		if (!ret_vsys && (cpu_vsys != cpu || node_vsys != node))
199 			ok = false;
200 
201 		printf("[%s]\tCPU %u:", ok ? "OK" : "FAIL", cpu);
202 		if (!ret_sys)
203 			printf(" syscall: cpu %u, node %u", cpu_sys, node_sys);
204 		if (!ret_vdso)
205 			printf(" vdso: cpu %u, node %u", cpu_vdso, node_vdso);
206 		if (!ret_vsys)
207 			printf(" vsyscall: cpu %u, node %u", cpu_vsys,
208 			       node_vsys);
209 		printf("\n");
210 
211 		if (!ok)
212 			nerrs++;
213 	}
214 }
215 
ts_leq(const struct timespec * a,const struct timespec * b)216 static bool ts_leq(const struct timespec *a, const struct timespec *b)
217 {
218 	if (a->tv_sec != b->tv_sec)
219 		return a->tv_sec < b->tv_sec;
220 	else
221 		return a->tv_nsec <= b->tv_nsec;
222 }
223 
ts64_leq(const struct __kernel_timespec * a,const struct __kernel_timespec * b)224 static bool ts64_leq(const struct __kernel_timespec *a,
225 		     const struct __kernel_timespec *b)
226 {
227 	if (a->tv_sec != b->tv_sec)
228 		return a->tv_sec < b->tv_sec;
229 	else
230 		return a->tv_nsec <= b->tv_nsec;
231 }
232 
tv_leq(const struct timeval * a,const struct timeval * b)233 static bool tv_leq(const struct timeval *a, const struct timeval *b)
234 {
235 	if (a->tv_sec != b->tv_sec)
236 		return a->tv_sec < b->tv_sec;
237 	else
238 		return a->tv_usec <= b->tv_usec;
239 }
240 
241 static char const * const clocknames[] = {
242 	[0] = "CLOCK_REALTIME",
243 	[1] = "CLOCK_MONOTONIC",
244 	[2] = "CLOCK_PROCESS_CPUTIME_ID",
245 	[3] = "CLOCK_THREAD_CPUTIME_ID",
246 	[4] = "CLOCK_MONOTONIC_RAW",
247 	[5] = "CLOCK_REALTIME_COARSE",
248 	[6] = "CLOCK_MONOTONIC_COARSE",
249 	[7] = "CLOCK_BOOTTIME",
250 	[8] = "CLOCK_REALTIME_ALARM",
251 	[9] = "CLOCK_BOOTTIME_ALARM",
252 	[10] = "CLOCK_SGI_CYCLE",
253 	[11] = "CLOCK_TAI",
254 };
255 
test_one_clock_gettime(int clock,const char * name)256 static void test_one_clock_gettime(int clock, const char *name)
257 {
258 	struct timespec start, vdso, end;
259 	int vdso_ret, end_ret;
260 
261 	printf("[RUN]\tTesting clock_gettime for clock %s (%d)...\n", name, clock);
262 
263 	if (sys_clock_gettime(clock, &start) < 0) {
264 		if (errno == EINVAL) {
265 			vdso_ret = vdso_clock_gettime(clock, &vdso);
266 			if (vdso_ret == -EINVAL) {
267 				printf("[OK]\tNo such clock.\n");
268 			} else {
269 				printf("[FAIL]\tNo such clock, but __vdso_clock_gettime returned %d\n", vdso_ret);
270 				nerrs++;
271 			}
272 		} else {
273 			printf("[WARN]\t clock_gettime(%d) syscall returned error %d\n", clock, errno);
274 		}
275 		return;
276 	}
277 
278 	vdso_ret = vdso_clock_gettime(clock, &vdso);
279 	end_ret = sys_clock_gettime(clock, &end);
280 
281 	if (vdso_ret != 0 || end_ret != 0) {
282 		printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
283 		       vdso_ret, errno);
284 		nerrs++;
285 		return;
286 	}
287 
288 	printf("\t%llu.%09ld %llu.%09ld %llu.%09ld\n",
289 	       (unsigned long long)start.tv_sec, start.tv_nsec,
290 	       (unsigned long long)vdso.tv_sec, vdso.tv_nsec,
291 	       (unsigned long long)end.tv_sec, end.tv_nsec);
292 
293 	if (!ts_leq(&start, &vdso) || !ts_leq(&vdso, &end)) {
294 		printf("[FAIL]\tTimes are out of sequence\n");
295 		nerrs++;
296 		return;
297 	}
298 
299 	printf("[OK]\tTest Passed.\n");
300 }
301 
test_clock_gettime(void)302 static void test_clock_gettime(void)
303 {
304 	if (!vdso_clock_gettime) {
305 		printf("[SKIP]\tNo vDSO, so skipping clock_gettime() tests\n");
306 		return;
307 	}
308 
309 	for (int clock = 0; clock < sizeof(clocknames) / sizeof(clocknames[0]);
310 	     clock++) {
311 		test_one_clock_gettime(clock, clocknames[clock]);
312 	}
313 
314 	/* Also test some invalid clock ids */
315 	test_one_clock_gettime(-1, "invalid");
316 	test_one_clock_gettime(INT_MIN, "invalid");
317 	test_one_clock_gettime(INT_MAX, "invalid");
318 }
319 
test_one_clock_gettime64(int clock,const char * name)320 static void test_one_clock_gettime64(int clock, const char *name)
321 {
322 	struct __kernel_timespec start, vdso, end;
323 	int vdso_ret, end_ret;
324 
325 	printf("[RUN]\tTesting clock_gettime64 for clock %s (%d)...\n", name, clock);
326 
327 	if (sys_clock_gettime64(clock, &start) < 0) {
328 		if (errno == EINVAL) {
329 			vdso_ret = vdso_clock_gettime64(clock, &vdso);
330 			if (vdso_ret == -EINVAL) {
331 				printf("[OK]\tNo such clock.\n");
332 			} else {
333 				printf("[FAIL]\tNo such clock, but __vdso_clock_gettime64 returned %d\n", vdso_ret);
334 				nerrs++;
335 			}
336 		} else {
337 			printf("[WARN]\t clock_gettime64(%d) syscall returned error %d\n", clock, errno);
338 		}
339 		return;
340 	}
341 
342 	vdso_ret = vdso_clock_gettime64(clock, &vdso);
343 	end_ret = sys_clock_gettime64(clock, &end);
344 
345 	if (vdso_ret != 0 || end_ret != 0) {
346 		printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
347 		       vdso_ret, errno);
348 		nerrs++;
349 		return;
350 	}
351 
352 	printf("\t%llu.%09lld %llu.%09lld %llu.%09lld\n",
353 	       (unsigned long long)start.tv_sec, start.tv_nsec,
354 	       (unsigned long long)vdso.tv_sec, vdso.tv_nsec,
355 	       (unsigned long long)end.tv_sec, end.tv_nsec);
356 
357 	if (!ts64_leq(&start, &vdso) || !ts64_leq(&vdso, &end)) {
358 		printf("[FAIL]\tTimes are out of sequence\n");
359 		nerrs++;
360 		return;
361 	}
362 
363 	printf("[OK]\tTest Passed.\n");
364 }
365 
test_clock_gettime64(void)366 static void test_clock_gettime64(void)
367 {
368 	if (!vdso_clock_gettime64) {
369 		printf("[SKIP]\tNo vDSO, so skipping clock_gettime64() tests\n");
370 		return;
371 	}
372 
373 	for (int clock = 0; clock < sizeof(clocknames) / sizeof(clocknames[0]);
374 	     clock++) {
375 		test_one_clock_gettime64(clock, clocknames[clock]);
376 	}
377 
378 	/* Also test some invalid clock ids */
379 	test_one_clock_gettime64(-1, "invalid");
380 	test_one_clock_gettime64(INT_MIN, "invalid");
381 	test_one_clock_gettime64(INT_MAX, "invalid");
382 }
383 
test_gettimeofday(void)384 static void test_gettimeofday(void)
385 {
386 	struct timeval start, vdso, end;
387 	struct timezone sys_tz, vdso_tz;
388 	int vdso_ret, end_ret;
389 
390 	if (!vdso_gettimeofday)
391 		return;
392 
393 	printf("[RUN]\tTesting gettimeofday...\n");
394 
395 	if (sys_gettimeofday(&start, &sys_tz) < 0) {
396 		printf("[FAIL]\tsys_gettimeofday failed (%d)\n", errno);
397 		nerrs++;
398 		return;
399 	}
400 
401 	vdso_ret = vdso_gettimeofday(&vdso, &vdso_tz);
402 	end_ret = sys_gettimeofday(&end, NULL);
403 
404 	if (vdso_ret != 0 || end_ret != 0) {
405 		printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
406 		       vdso_ret, errno);
407 		nerrs++;
408 		return;
409 	}
410 
411 	printf("\t%llu.%06ld %llu.%06ld %llu.%06ld\n",
412 	       (unsigned long long)start.tv_sec, start.tv_usec,
413 	       (unsigned long long)vdso.tv_sec, vdso.tv_usec,
414 	       (unsigned long long)end.tv_sec, end.tv_usec);
415 
416 	if (!tv_leq(&start, &vdso) || !tv_leq(&vdso, &end)) {
417 		printf("[FAIL]\tTimes are out of sequence\n");
418 		nerrs++;
419 	}
420 
421 	if (sys_tz.tz_minuteswest == vdso_tz.tz_minuteswest &&
422 	    sys_tz.tz_dsttime == vdso_tz.tz_dsttime) {
423 		printf("[OK]\ttimezones match: minuteswest=%d, dsttime=%d\n",
424 		       sys_tz.tz_minuteswest, sys_tz.tz_dsttime);
425 	} else {
426 		printf("[FAIL]\ttimezones do not match\n");
427 		nerrs++;
428 	}
429 
430 	/* And make sure that passing NULL for tz doesn't crash. */
431 	vdso_gettimeofday(&vdso, NULL);
432 }
433 
main(int argc,char ** argv)434 int main(int argc, char **argv)
435 {
436 	name = (const char **)&names[VDSO_NAMES];
437 
438 	fill_function_pointers();
439 
440 	test_clock_gettime();
441 	test_clock_gettime64();
442 	test_gettimeofday();
443 
444 	/*
445 	 * Test getcpu() last so that, if something goes wrong setting affinity,
446 	 * we still run the other tests.
447 	 */
448 	test_getcpu();
449 
450 	return nerrs ? 1 : 0;
451 }
452