aboutsummaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/vdso/Makefile55
-rw-r--r--kernel/vdso/README.md40
-rw-r--r--kernel/vdso/vdso.c151
3 files changed, 246 insertions, 0 deletions
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 <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);
+}