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#ifndef SYS_getcpu
23# ifdef __x86_64__
24#  define SYS_getcpu 309
25# else
26#  define SYS_getcpu 318
27# endif
28#endif
29
30/* max length of lines in /proc/self/maps - anything longer is skipped here */
31#define MAPS_LINE_LEN 128
32
33int nerrs = 0;
34
35typedef int (*vgettime_t)(clockid_t, struct timespec *);
36
37vgettime_t vdso_clock_gettime;
38
39typedef long (*vgtod_t)(struct timeval *tv, struct timezone *tz);
40
41vgtod_t vdso_gettimeofday;
42
43typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
44
45getcpu_t vgetcpu;
46getcpu_t vdso_getcpu;
47
48static void *vsyscall_getcpu(void)
49{
50#ifdef __x86_64__
51	FILE *maps;
52	char line[MAPS_LINE_LEN];
53	bool found = false;
54
55	maps = fopen("/proc/self/maps", "r");
56	if (!maps) /* might still be present, but ignore it here, as we test vDSO not vsyscall */
57		return NULL;
58
59	while (fgets(line, MAPS_LINE_LEN, maps)) {
60		char r, x;
61		void *start, *end;
62		char name[MAPS_LINE_LEN];
63
64		/* sscanf() is safe here as strlen(name) >= strlen(line) */
65		if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
66			   &start, &end, &r, &x, name) != 5)
67			continue;
68
69		if (strcmp(name, "[vsyscall]"))
70			continue;
71
72		/* assume entries are OK, as we test vDSO here not vsyscall */
73		found = true;
74		break;
75	}
76
77	fclose(maps);
78
79	if (!found) {
80		printf("Warning: failed to find vsyscall getcpu\n");
81		return NULL;
82	}
83	return (void *) (0xffffffffff600800);
84#else
85	return NULL;
86#endif
87}
88
89
90static void fill_function_pointers()
91{
92	void *vdso = dlopen("linux-vdso.so.1",
93			    RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
94	if (!vdso)
95		vdso = dlopen("linux-gate.so.1",
96			      RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
97	if (!vdso) {
98		printf("[WARN]\tfailed to find vDSO\n");
99		return;
100	}
101
102	vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
103	if (!vdso_getcpu)
104		printf("Warning: failed to find getcpu in vDSO\n");
105
106	vgetcpu = (getcpu_t) vsyscall_getcpu();
107
108	vdso_clock_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
109	if (!vdso_clock_gettime)
110		printf("Warning: failed to find clock_gettime in vDSO\n");
111
112	vdso_gettimeofday = (vgtod_t)dlsym(vdso, "__vdso_gettimeofday");
113	if (!vdso_gettimeofday)
114		printf("Warning: failed to find gettimeofday in vDSO\n");
115
116}
117
118static long sys_getcpu(unsigned * cpu, unsigned * node,
119		       void* cache)
120{
121	return syscall(__NR_getcpu, cpu, node, cache);
122}
123
124static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
125{
126	return syscall(__NR_clock_gettime, id, ts);
127}
128
129static inline int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
130{
131	return syscall(__NR_gettimeofday, tv, tz);
132}
133
134static void test_getcpu(void)
135{
136	printf("[RUN]\tTesting getcpu...\n");
137
138	for (int cpu = 0; ; cpu++) {
139		cpu_set_t cpuset;
140		CPU_ZERO(&cpuset);
141		CPU_SET(cpu, &cpuset);
142		if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
143			return;
144
145		unsigned cpu_sys, cpu_vdso, cpu_vsys,
146			node_sys, node_vdso, node_vsys;
147		long ret_sys, ret_vdso = 1, ret_vsys = 1;
148		unsigned node;
149
150		ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
151		if (vdso_getcpu)
152			ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
153		if (vgetcpu)
154			ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
155
156		if (!ret_sys)
157			node = node_sys;
158		else if (!ret_vdso)
159			node = node_vdso;
160		else if (!ret_vsys)
161			node = node_vsys;
162
163		bool ok = true;
164		if (!ret_sys && (cpu_sys != cpu || node_sys != node))
165			ok = false;
166		if (!ret_vdso && (cpu_vdso != cpu || node_vdso != node))
167			ok = false;
168		if (!ret_vsys && (cpu_vsys != cpu || node_vsys != node))
169			ok = false;
170
171		printf("[%s]\tCPU %u:", ok ? "OK" : "FAIL", cpu);
172		if (!ret_sys)
173			printf(" syscall: cpu %u, node %u", cpu_sys, node_sys);
174		if (!ret_vdso)
175			printf(" vdso: cpu %u, node %u", cpu_vdso, node_vdso);
176		if (!ret_vsys)
177			printf(" vsyscall: cpu %u, node %u", cpu_vsys,
178			       node_vsys);
179		printf("\n");
180
181		if (!ok)
182			nerrs++;
183	}
184}
185
186static bool ts_leq(const struct timespec *a, const struct timespec *b)
187{
188	if (a->tv_sec != b->tv_sec)
189		return a->tv_sec < b->tv_sec;
190	else
191		return a->tv_nsec <= b->tv_nsec;
192}
193
194static bool tv_leq(const struct timeval *a, const struct timeval *b)
195{
196	if (a->tv_sec != b->tv_sec)
197		return a->tv_sec < b->tv_sec;
198	else
199		return a->tv_usec <= b->tv_usec;
200}
201
202static char const * const clocknames[] = {
203	[0] = "CLOCK_REALTIME",
204	[1] = "CLOCK_MONOTONIC",
205	[2] = "CLOCK_PROCESS_CPUTIME_ID",
206	[3] = "CLOCK_THREAD_CPUTIME_ID",
207	[4] = "CLOCK_MONOTONIC_RAW",
208	[5] = "CLOCK_REALTIME_COARSE",
209	[6] = "CLOCK_MONOTONIC_COARSE",
210	[7] = "CLOCK_BOOTTIME",
211	[8] = "CLOCK_REALTIME_ALARM",
212	[9] = "CLOCK_BOOTTIME_ALARM",
213	[10] = "CLOCK_SGI_CYCLE",
214	[11] = "CLOCK_TAI",
215};
216
217static void test_one_clock_gettime(int clock, const char *name)
218{
219	struct timespec start, vdso, end;
220	int vdso_ret, end_ret;
221
222	printf("[RUN]\tTesting clock_gettime for clock %s (%d)...\n", name, clock);
223
224	if (sys_clock_gettime(clock, &start) < 0) {
225		if (errno == EINVAL) {
226			vdso_ret = vdso_clock_gettime(clock, &vdso);
227			if (vdso_ret == -EINVAL) {
228				printf("[OK]\tNo such clock.\n");
229			} else {
230				printf("[FAIL]\tNo such clock, but __vdso_clock_gettime returned %d\n", vdso_ret);
231				nerrs++;
232			}
233		} else {
234			printf("[WARN]\t clock_gettime(%d) syscall returned error %d\n", clock, errno);
235		}
236		return;
237	}
238
239	vdso_ret = vdso_clock_gettime(clock, &vdso);
240	end_ret = sys_clock_gettime(clock, &end);
241
242	if (vdso_ret != 0 || end_ret != 0) {
243		printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
244		       vdso_ret, errno);
245		nerrs++;
246		return;
247	}
248
249	printf("\t%llu.%09ld %llu.%09ld %llu.%09ld\n",
250	       (unsigned long long)start.tv_sec, start.tv_nsec,
251	       (unsigned long long)vdso.tv_sec, vdso.tv_nsec,
252	       (unsigned long long)end.tv_sec, end.tv_nsec);
253
254	if (!ts_leq(&start, &vdso) || !ts_leq(&vdso, &end)) {
255		printf("[FAIL]\tTimes are out of sequence\n");
256		nerrs++;
257	}
258}
259
260static void test_clock_gettime(void)
261{
262	if (!vdso_clock_gettime) {
263		printf("[SKIP]\tNo vDSO, so skipping clock_gettime() tests\n");
264		return;
265	}
266
267	for (int clock = 0; clock < sizeof(clocknames) / sizeof(clocknames[0]);
268	     clock++) {
269		test_one_clock_gettime(clock, clocknames[clock]);
270	}
271
272	/* Also test some invalid clock ids */
273	test_one_clock_gettime(-1, "invalid");
274	test_one_clock_gettime(INT_MIN, "invalid");
275	test_one_clock_gettime(INT_MAX, "invalid");
276}
277
278static void test_gettimeofday(void)
279{
280	struct timeval start, vdso, end;
281	struct timezone sys_tz, vdso_tz;
282	int vdso_ret, end_ret;
283
284	if (!vdso_gettimeofday)
285		return;
286
287	printf("[RUN]\tTesting gettimeofday...\n");
288
289	if (sys_gettimeofday(&start, &sys_tz) < 0) {
290		printf("[FAIL]\tsys_gettimeofday failed (%d)\n", errno);
291		nerrs++;
292		return;
293	}
294
295	vdso_ret = vdso_gettimeofday(&vdso, &vdso_tz);
296	end_ret = sys_gettimeofday(&end, NULL);
297
298	if (vdso_ret != 0 || end_ret != 0) {
299		printf("[FAIL]\tvDSO returned %d, syscall errno=%d\n",
300		       vdso_ret, errno);
301		nerrs++;
302		return;
303	}
304
305	printf("\t%llu.%06ld %llu.%06ld %llu.%06ld\n",
306	       (unsigned long long)start.tv_sec, start.tv_usec,
307	       (unsigned long long)vdso.tv_sec, vdso.tv_usec,
308	       (unsigned long long)end.tv_sec, end.tv_usec);
309
310	if (!tv_leq(&start, &vdso) || !tv_leq(&vdso, &end)) {
311		printf("[FAIL]\tTimes are out of sequence\n");
312		nerrs++;
313	}
314
315	if (sys_tz.tz_minuteswest == vdso_tz.tz_minuteswest &&
316	    sys_tz.tz_dsttime == vdso_tz.tz_dsttime) {
317		printf("[OK]\ttimezones match: minuteswest=%d, dsttime=%d\n",
318		       sys_tz.tz_minuteswest, sys_tz.tz_dsttime);
319	} else {
320		printf("[FAIL]\ttimezones do not match\n");
321		nerrs++;
322	}
323
324	/* And make sure that passing NULL for tz doesn't crash. */
325	vdso_gettimeofday(&vdso, NULL);
326}
327
328int main(int argc, char **argv)
329{
330	fill_function_pointers();
331
332	test_clock_gettime();
333	test_gettimeofday();
334
335	/*
336	 * Test getcpu() last so that, if something goes wrong setting affinity,
337	 * we still run the other tests.
338	 */
339	test_getcpu();
340
341	return nerrs ? 1 : 0;
342}
343