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