aboutsummaryrefslogtreecommitdiff
path: root/kernel/vdso/vdso.c
blob: 79c2f0578e21ae7d5d7d731ca7e88999d69e08f2 (plain)
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
150
151
#define _GNU_SOURCE

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Needed for 'getauxval'.
#include <sys/auxv.h>

/*
 * In projects like github.com/mssola/fbos I already dealt with ELF parsing
 * myself and it was the nicest experience. Plus, in here I have to do some
 * heave usage of some ELF sections. For this reason I will not do things
 * manually and use the standard library instead.
 */
#include <elf.h>


// Terminate the current program with a failure exit code while also printing
// the given `message` into stderr.
__attribute__((noreturn)) void die(const char *const message)
{
	fprintf(stderr, message);
	exit(EXIT_FAILURE);
}

// Assert that the given address 'addr' points to a 64-bit little-endian ELF
// file.
void ensure_proper_elf_header(const char *const addr)
{
	if (addr[0] != 0x7F || memcmp(&addr[1], "ELF", 3) != 0) {
		die("Bad ELF format\n");
	}
	if (addr[4] != 2) {
		die("64-bit format is mandatory\n");
	}
	if (addr[5] != 1) {
		die("Little-endian only\n");
	}
}

// Returns true if the given name looks like a '__vdso_' function name.
bool is_vdso_function(const char *const name)
{
	if (strlen(name) < 9) {
		return false;
	}

	return name[0] == '_' &&
		name[1] == '_' &&
		name[2] == 'v' &&
		name[3] == 'd' &&
		name[4] == 's' &&
		name[5] == 'o' &&
		name[6] == '_';
}

int main(void)
{
	/*
	 * From the vDSO man page: "The base address of the vDSO (if one exists) is
	 * passed by the kernel to each program in the initial auxiliary vector (see
	 * getauxval(3)), via the AT_SYSINFO_EHDR tag".
	 */
	void *vdso = (void *) getauxval(AT_SYSINFO_EHDR);
	if (!vdso) {
		die("Could not get base address for vDSO\n");
	}
	printf("=> vDSO base address: %pS\n", vdso);

	// Basic assertion :)
	ensure_proper_elf_header((const char *) vdso);

	// The ELF header is the first thing starting from the fetched address.
    const Elf64_Ehdr *ehdr = (const Elf64_Ehdr *) vdso;

	/*
	 * Grab the base address for the Section Header Table for the ELF
	 * binary. From there, we will search section and fetch the address for the
	 * dynamic symbol table and the dynamic string table. This will be used
	 * later for looking up the address for each given function name.
	 */
    const Elf64_Shdr *shdr = (const Elf64_Shdr *)((const char *) vdso + ehdr->e_shoff);
    const Elf64_Shdr *symbols_header = NULL;
    const Elf64_Shdr *strings_header = NULL;

    for (int i = 0; i < ehdr->e_shnum; i++) {
        if (shdr[i].sh_type == SHT_DYNSYM) {
            symbols_header = &shdr[i];
        } else if (shdr[i].sh_type == SHT_STRTAB) {
            const char *shstrtab = (const char *) vdso + shdr[ehdr->e_shstrndx].sh_offset;
            const char *strtab_name = shstrtab + shdr[i].sh_name;

            if (strcmp(strtab_name, ".dynstr") == 0) {
                strings_header = &shdr[i];
            }
        }
    }
    if (!symbols_header || !strings_header) {
        die("Could not find .dynsym or .dynstr sections\n");
    }

	/*
	 * Now that we know where to pick the strings and their corresponding
	 * addresses, let's use their offsets to pair it with the original base
	 * 'vdso' address. This will give us a pointer to the proper tables.
	 */
    const Elf64_Sym *symbols_table = (const Elf64_Sym *)((const char *) vdso + symbols_header->sh_offset);
    const char *strings_table = (const char *)((const char *) vdso + strings_header->sh_offset);
    int num_symbols = symbols_header->sh_size / symbols_header->sh_entsize;

	/*
	 * As an example we will find the 'getcpu' system call which is exported on
	 * a Linux x86_64 system.
	 */
	int (*vdso_getcpu)(unsigned int *, unsigned int*) = NULL;

	printf("=> The following system calls are available via vDSO:\n");
    for (int i = 0; i < num_symbols; i++) {
		// The name of the function directly sits at the strings table plus the
		// offset given on the symbols table.
        const char *name = strings_table + symbols_table[i].st_name;

		if (is_vdso_function(name)) {
			void *address = (void *)((char *) vdso + symbols_table[i].st_value);
			printf("\t%s (address: %p)\n", name, address);

			if (strcmp(name, "__vdso_getcpu") == 0) {
				vdso_getcpu = (int (*)(unsigned int *, unsigned int *)) address;
			}
		}
    }

	/*
	 * As an example, run the 'getcpu' system call via the vDSO mechanism.
	 */
	if (vdso_getcpu) {
		unsigned int cpu, core;
		int res = vdso_getcpu(&cpu, &core);
		if (res < 0) {
			perror("");
			exit(EXIT_FAILURE);
		}
		printf("=> After running 'getcpu' via vDSO: cpu: %d - core: %d\n", cpu, core);
	} else {
		printf("=> No 'getcpu' fun this time around!\n");
	}

	exit(EXIT_SUCCESS);
}