1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* 32-bit test to check vDSO mremap.
*
* Copyright (c) 2016 Dmitry Safonov
* Suggested-by: Andrew Lutomirski
*/
/*
* Can be built statically:
* gcc -Os -Wall -static -m32 test_mremap_vdso.c
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <sys/auxv.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include "../kselftest.h"
#define PAGE_SIZE 4096
static int try_to_remap(void *vdso_addr, unsigned long size)
{
void *dest_addr, *new_addr;
/* Searching for memory location where to remap */
dest_addr = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (dest_addr == MAP_FAILED) {
ksft_print_msg("WARN: mmap failed (%d): %m\n", errno);
return 0;
}
ksft_print_msg("Moving vDSO: [%p, %#lx] -> [%p, %#lx]\n",
vdso_addr, (unsigned long)vdso_addr + size,
dest_addr, (unsigned long)dest_addr + size);
fflush(stdout);
new_addr = mremap(vdso_addr, size, size,
MREMAP_FIXED|MREMAP_MAYMOVE, dest_addr);
if ((unsigned long)new_addr == (unsigned long)-1) {
munmap(dest_addr, size);
if (errno == EINVAL) {
ksft_print_msg("vDSO partial move failed, will try with bigger size\n");
return -1; /* Retry with larger */
}
ksft_print_msg("[FAIL]\tmremap failed (%d): %m\n", errno);
return 1;
}
return 0;
}
#define VDSO_NAME "[vdso]"
#define VMFLAGS "VmFlags:"
#define MSEAL_FLAGS "sl"
#define MAX_LINE_LEN 512
bool vdso_sealed(FILE *maps)
{
char line[MAX_LINE_LEN];
bool has_vdso = false;
while (fgets(line, sizeof(line), maps)) {
if (strstr(line, VDSO_NAME))
has_vdso = true;
if (has_vdso && !strncmp(line, VMFLAGS, strlen(VMFLAGS))) {
if (strstr(line, MSEAL_FLAGS))
return true;
return false;
}
}
return false;
}
int main(int argc, char **argv, char **envp)
{
pid_t child;
FILE *maps;
ksft_print_header();
ksft_set_plan(1);
maps = fopen("/proc/self/smaps", "r");
if (!maps) {
ksft_test_result_skip(
"Could not open /proc/self/smaps, errno=%d\n",
errno);
return 0;
}
if (vdso_sealed(maps)) {
ksft_test_result_skip("vdso is sealed\n");
return 0;
}
fclose(maps);
child = fork();
if (child == -1)
ksft_exit_fail_msg("failed to fork (%d): %m\n", errno);
if (child == 0) {
unsigned long vdso_size = PAGE_SIZE;
unsigned long auxval;
int ret = -1;
auxval = getauxval(AT_SYSINFO_EHDR);
ksft_print_msg("AT_SYSINFO_EHDR is %#lx\n", auxval);
if (!auxval || auxval == -ENOENT) {
ksft_print_msg("WARN: getauxval failed\n");
return 0;
}
/* Simpler than parsing ELF header */
while (ret < 0) {
ret = try_to_remap((void *)auxval, vdso_size);
vdso_size += PAGE_SIZE;
}
#ifdef __i386__
/* Glibc is likely to explode now - exit with raw syscall */
asm volatile ("int $0x80" : : "a" (__NR_exit), "b" (!!ret));
#else /* __x86_64__ */
syscall(SYS_exit, ret);
#endif
} else {
int status;
if (waitpid(child, &status, 0) != child ||
!WIFEXITED(status))
ksft_test_result_fail("mremap() of the vDSO does not work on this kernel!\n");
else if (WEXITSTATUS(status) != 0)
ksft_test_result_fail("Child failed with %d\n", WEXITSTATUS(status));
else
ksft_test_result_pass("%s\n", __func__);
}
ksft_finished();
}
|