From 7a721c4374f192ba025b27f790cf14339ca2088e Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Mon, 22 Sep 2025 21:39:30 +0200 Subject: kernel/vdso: Add an example on vDSO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miquel Sabaté Solà --- kernel/vdso/Makefile | 55 ++++++++++++++++++ kernel/vdso/README.md | 40 +++++++++++++ kernel/vdso/vdso.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 kernel/vdso/Makefile create mode 100644 kernel/vdso/README.md create mode 100644 kernel/vdso/vdso.c (limited to 'kernel/vdso') diff --git a/kernel/vdso/Makefile b/kernel/vdso/Makefile new file mode 100644 index 0000000..0fdfccb --- /dev/null +++ b/kernel/vdso/Makefile @@ -0,0 +1,55 @@ +## +# By default everything is silent. If you want to change this behavior, simply +# assign V=1 when calling make. + +V = +ifeq ($(strip $(V)),) + E = @echo + Q = @ +else + E = @\# + Q = +endif + +## +# Compile options. You can use CROSS_COMPILE just like on the Linux Kernel. + +CC = $(CROSS_COMPILE)gcc +LD = $(CROSS_COMPILE)ld +CCFLAGS = -Werror -Wall -Wextra -Wcast-align -Wcast-qual -Winit-self \ + -Wmissing-include-dirs -Wredundant-decls -Wshadow -Wsign-conversion \ + -Wswitch-default -Wundef -Wunreachable-code -Wmissing-noreturn \ + -D_FORTIFY_SOURCE=3 -fstrict-flex-arrays=3 -fsanitize=bounds-strict +LDFLAGS = + + +# You can pass an optional `DEBUG` variable to manipulate the build type. +DEBUG = +ifeq ($(strip $(DEBUG)),) + CCFLAGS += -O3 +else + CCFLAGS += -g +endif + +## +# Paths. + +SRC = $(wildcard *.c) +EXES = $(SRC:.c=) + +## +# Targets + +.PHONY: all +all: clean $(EXES) + +.PHONY: build +build: $(EXES) + +.c: + $(E) " CC " $(*F) + $(Q) $(CC) $(CCFLAGS) $< -o $@ + +.PHONY: clean +clean: + $(Q) rm -f $(EXES) diff --git a/kernel/vdso/README.md b/kernel/vdso/README.md new file mode 100644 index 0000000..573c564 --- /dev/null +++ b/kernel/vdso/README.md @@ -0,0 +1,40 @@ +## vDSO + +The "vDSO" (virtual dynamic shared object) is a small shared library that the +kernel automatically maps into the address space of all user-space +applications. This way, user space can perform some system calls via this +interface and avoid the cost of entering kernel space. + +This is important for +system calls that the community has detected to be performed most often by all +applications. Note that the regular joe doesn't have to deal with any of this as +glibc and the likes will already take care of this. + +For much more, this is all better documented via `man vdso`. + +### This example + +No arguments are required, just: + +``` +$ make +$ ./vdso +=> vDSO base address: 0x7fab930dc000S +=> The following system calls are available via vDSO: + __vdso_gettimeofday (address: 0x7fab930dc890) + __vdso_clock_getres (address: 0x7fab930dcf70) + __vdso_time (address: 0x7fab930dcb80) + __vdso_sgx_enter_enclave (address: 0x7fab930dd530) + __vdso_getrandom (address: 0x7fab930dd020) + __vdso_clock_gettime (address: 0x7fab930dcbb0) + __vdso_getcpu (address: 0x7fab930dcfe0) +=> After running 'getcpu' via vDSO: cpu: 14 - core: 0 +``` + +That is, this program: + +1. Fetches the base address for the vDSO. +2. Parses the ELF file that is the vDSO and obtains the name of all functions + starting with `__vdso_*` (i.e. those that the kernel expects to be called). +3. As an example, it calls the `getcpu` system call by calling the parsed + address for it directly. diff --git a/kernel/vdso/vdso.c b/kernel/vdso/vdso.c new file mode 100644 index 0000000..79c2f05 --- /dev/null +++ b/kernel/vdso/vdso.c @@ -0,0 +1,151 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include + +// Needed for 'getauxval'. +#include + +/* + * 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 + + +// 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); +} -- cgit v1.2.3