RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / user / exechello.c
2838 bytes ยท raw
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
/*
 * exechello.c -- stage 20: "hello" built with the stock recipe,
 * `musl-gcc -static` and no other flags. That makes a classic ET_EXEC
 * file linked at 0x400000 (4 MiB), the address every Linux linker uses.
 * Until stage 20 the kernel itself lived down there; now it doesn't.
 *
 * Checks: we really are at 0x400000, and the heap (brk) and mmap memory
 * land outside our image. Exit code = number of failed checks.
 */
#define _GNU_SOURCE
#include <elf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>

static int fails;
#define CHECK(cond, what)                                     \
	do {                                                  \
		if (!(cond)) {                                \
			fails++;                              \
			printf("exechello: FAIL %s\n", what); \
		}                                             \
	} while (0)

extern const Elf64_Ehdr __ehdr_start; /* first byte of the image */
extern char _end[];                   /* one past the last byte (.bss end) */
extern char _start[];

/* Does [p, p + len) overlap our image? */
static int in_image(const void *p, unsigned long len)
{
	unsigned long a = (unsigned long)p, lo = (unsigned long)&__ehdr_start;
	return a < (unsigned long)_end && a + len > lo;
}

int main(void)
{
	puts("hello from a plain static executable");
	CHECK((unsigned long)&__ehdr_start == 0x400000, "image starts at 0x400000");
	CHECK(__ehdr_start.e_type == ET_EXEC, "ET_EXEC file");
	CHECK(getauxval(AT_ENTRY) == (unsigned long)_start, "AT_ENTRY is _start");
	CHECK(getauxval(AT_PHDR) == 0x400000 + __ehdr_start.e_phoff, "AT_PHDR");

	/* brk: starts at or after the image's end and grows from there.
	 * (musl's sbrk() only takes 0, so grow it with the raw syscall.) */
	char *brk0 = sbrk(0);
	CHECK(brk0 != (char *)-1 && brk0 >= _end, "brk starts after the image");
	char *more = brk0;
	CHECK((char *)syscall(SYS_brk, brk0 + 64 * 1024) == brk0 + 64 * 1024, "brk grows by 64 KiB");
	{
		memset(more, 0x5a, 64 * 1024); /* writable, no fault */
		CHECK(!in_image(more, 64 * 1024), "brk memory outside the image");
	}

	/* mmap and malloc (small: musl's heap; big: its own mmap). */
	void *m = mmap(NULL, 1 << 20, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	CHECK(m != MAP_FAILED && !in_image(m, 1 << 20), "mmap outside the image");
	CHECK(m == MAP_FAILED || (char *)m > brk0 + 64 * 1024, "mmap above the heap");
	for (int i = 0; i < 64; i++) {
		size_t n = i % 2 ? 40 : 300 * 1024;
		char *p = malloc(n);
		CHECK(p && !in_image(p, n), "malloc outside the image");
		if (p)
			memset(p, i, n);
		free(p);
	}
	printf("image %p..%p, brk %p, mmap %p\n", (void *)&__ehdr_start, (void *)_end, (void *)brk0, m);
	printf("exechello: %d failed\n", fails);
	return fails;
}