diff --git a/content/chapters/data/lab/content/arena.md b/content/chapters/data/lab/content/arena.md index 5ae004786c..30792f7867 100644 --- a/content/chapters/data/lab/content/arena.md +++ b/content/chapters/data/lab/content/arena.md @@ -62,6 +62,46 @@ test_brk ........................ passed ... 25 Total: 100/100 ``` +## Access Counter + +Navigate to the `support/access-counter/` directory. + +Your goal is to update the `src/access_counter.c` source code file to capture memory access exceptions (i.e. the `SIGSEGV` signal) and to update page permissions in order for the acces to eventually succeed. +Use `mprotect` to update the protection of the pages in stages: read, write and then exec. +Each time an update is made, the `counter` variable is increased; +this is used for testing. + +The `pages` array stores information about accessed pages. +Assume the `MAX_PAGES` size of the array is enough to store information. +When an existing page is accessed and causes a memory exception, the permission is update, in the stages mentioned above: read, write, and then exec. +When a new page is accessed, a new entry is filled in the `pages` array, initialized with read protection. +Use `mmap()` to reserve virtual pages. +Use anonymous mapping (i.e. the `MAP_ANONYMOUS`) flag. +Use any permissions required. + +To test it, enter the `tests/` directory and run: + +```console +make check +``` + +In case of a correct solution, you will get an output such as: + +```text +./run_all_tests.sh +test_acess_read ........................ passed ... 9 +test_acess_write ........................ passed ... 9 +test_acess_exec ........................ passed ... 10 +test_acess_read_write ........................ passed ... 12 +test_acess_read_exec ........................ passed ... 12 +test_acess_write_exec ........................ passed ... 12 +test_acess_exec_read ........................ passed ... 12 +test_acess_exec_write ........................ passed ... 12 +test_acess_write_read ........................ passed ... 12 + +Total: 100/100 +``` + ## Memory Support **Manual memory management** (MMM) is one of the most difficult tasks. diff --git a/content/chapters/data/lab/solution/access-counter/src/Makefile b/content/chapters/data/lab/solution/access-counter/src/Makefile new file mode 100644 index 0000000000..f467a82d82 --- /dev/null +++ b/content/chapters/data/lab/solution/access-counter/src/Makefile @@ -0,0 +1,14 @@ +CFLAGS ?= -Wall -Wextra +CPPFLAGS ?= -I../utils + +.PHONY: all clean + +all: access_counter.o ../utils/log/log.o + +access_counter.o: access_counter.c ../utils/utils.h ../utils/log/log.h + +../utils/log/log.o: ../utils/log/log.c ../utils/log/log.h + +clean: + -rm -f access_counter.o ../utils/log/log.o + -rm -f *~ diff --git a/content/chapters/data/lab/solution/access-counter/src/access_counter.c b/content/chapters/data/lab/solution/access-counter/src/access_counter.c new file mode 100644 index 0000000000..60e94ea8e4 --- /dev/null +++ b/content/chapters/data/lab/solution/access-counter/src/access_counter.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +struct page_info { + int prot; + void *start; +}; + +unsigned long counter; + +#define MAX_PAGES 16 +static struct page_info pages[MAX_PAGES]; +static size_t num_pages = 0; + +/* This is useful for filling the contents when execution rights are provided. */ +static void do_nothing(void) +{ +} + +/* + * The actual SIGSEVG handler. + */ +static void access_handler(int signum, siginfo_t *si, void *arg) +{ + long page_size = sysconf(_SC_PAGESIZE); + void *start; + int rc; + unsigned int i; + + log_debug("Enter handler"); + + counter++; + + if (signum != SIGSEGV) { + fprintf(stderr, "Unable to handle signal %d (%s)\n", signum, strsignal(signum)); + return; + } + + /* TODO 2: Obtain page strart address in start variable. */ + start = (void *) ((unsigned long) si->si_addr & ~0xFFFUL); + log_debug("start: %p", start); + + for (i = 0; i < num_pages; i++) + if (pages[i].start == start) + break; + + if (i >= num_pages && i < MAX_PAGES) { + pages[i].start = start; + pages[i].prot = PROT_NONE; + num_pages += 1; + } + + log_debug("i = %u", i); + + /* TODO 21: Update page proctections with mprotect(). */ + switch (pages[i].prot) { + case PROT_NONE: + rc = mprotect(start, page_size, PROT_READ); + DIE(rc < 0, "mprotect"); + pages[i].prot = PROT_READ; + break; + case PROT_READ: + rc = mprotect(start, page_size, PROT_READ | PROT_WRITE); + DIE(rc < 0, "mprotect"); + pages[i].prot = PROT_WRITE; + break; + case PROT_WRITE: + rc = mprotect(start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC); + DIE(rc < 0, "mprotect"); + /* Optimistically copy 64 bytes of code. Should be more than enough. */ + memcpy(start, do_nothing, 64); + pages[i].prot = PROT_EXEC; + break; + default: + break; + } +} + +void register_sigsegv_handler(void) +{ + struct sigaction sa; + int rc; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = access_handler; + sa.sa_flags = SA_SIGINFO; + + rc = sigaction (SIGSEGV, &sa, NULL); + DIE(rc < 0, "sigaction"); +} diff --git a/content/chapters/data/lab/solution/access-counter/src/access_counter.h b/content/chapters/data/lab/solution/access-counter/src/access_counter.h new file mode 100644 index 0000000000..fc2541bc5b --- /dev/null +++ b/content/chapters/data/lab/solution/access-counter/src/access_counter.h @@ -0,0 +1,7 @@ +#ifndef ACCESS_PRINTER_H_ +#define ACCESS_PRINTER_H_ 1 + +extern unsigned long counter; +void register_sigsegv_handler(void); + +#endif diff --git a/content/chapters/data/lab/support/access-counter/src/Makefile b/content/chapters/data/lab/support/access-counter/src/Makefile new file mode 100644 index 0000000000..f467a82d82 --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/src/Makefile @@ -0,0 +1,14 @@ +CFLAGS ?= -Wall -Wextra +CPPFLAGS ?= -I../utils + +.PHONY: all clean + +all: access_counter.o ../utils/log/log.o + +access_counter.o: access_counter.c ../utils/utils.h ../utils/log/log.h + +../utils/log/log.o: ../utils/log/log.c ../utils/log/log.h + +clean: + -rm -f access_counter.o ../utils/log/log.o + -rm -f *~ diff --git a/content/chapters/data/lab/support/access-counter/src/access_counter.c b/content/chapters/data/lab/support/access-counter/src/access_counter.c new file mode 100644 index 0000000000..92cb283526 --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/src/access_counter.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +struct page_info { + int prot; + void *start; +}; + +unsigned long counter; + +#define MAX_PAGES 16 +static struct page_info pages[MAX_PAGES]; +static size_t num_pages = 0; + +/* This is useful for filling the contents when execution rights are provided. */ +static void do_nothing(void) +{ +} + +/* + * The actual SIGSEVG handler. + */ +static void access_handler(int signum, siginfo_t *si, void *arg) +{ + long page_size = sysconf(_SC_PAGESIZE); + void *start; + int rc; + unsigned int i; + + log_debug("Enter handler"); + + counter++; + + if (signum != SIGSEGV) { + fprintf(stderr, "Unable to handle signal %d (%s)\n", signum, strsignal(signum)); + return; + } + + /* TODO: Obtain page strart address in start variable. */ + + for (i = 0; i < num_pages; i++) + if (pages[i].start == start) + break; + + if (i >= num_pages && i < MAX_PAGES) { + pages[i].start = start; + pages[i].prot = PROT_NONE; + num_pages += 1; + } + + log_debug("i = %u", i); + + /* TODO: Update page proctections with mprotect(). */ +} + +void register_sigsegv_handler(void) +{ + struct sigaction sa; + int rc; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = access_handler; + sa.sa_flags = SA_SIGINFO; + + rc = sigaction (SIGSEGV, &sa, NULL); + DIE(rc < 0, "sigaction"); +} diff --git a/content/chapters/data/lab/support/access-counter/src/access_counter.h b/content/chapters/data/lab/support/access-counter/src/access_counter.h new file mode 100644 index 0000000000..fc2541bc5b --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/src/access_counter.h @@ -0,0 +1,7 @@ +#ifndef ACCESS_PRINTER_H_ +#define ACCESS_PRINTER_H_ 1 + +extern unsigned long counter; +void register_sigsegv_handler(void); + +#endif diff --git a/content/chapters/data/lab/support/access-counter/tests/.gitignore b/content/chapters/data/lab/support/access-counter/tests/.gitignore new file mode 100644 index 0000000000..ee4c926823 --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/tests/.gitignore @@ -0,0 +1 @@ +/test diff --git a/content/chapters/data/lab/support/access-counter/tests/Makefile b/content/chapters/data/lab/support/access-counter/tests/Makefile new file mode 100644 index 0000000000..ee1604b7f3 --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/tests/Makefile @@ -0,0 +1,44 @@ +SRC_PATH ?= ../src +FULL_SRC_PATH = $(realpath $(SRC_PATH)) +CPPFLAGS = -I. -I$(realpath $(SRC_PATH)) -I../utils +CFLAGS = -Wall -Wextra +# Remove the line below to disable debugging support. +CFLAGS += -g -O0 + +SRCS = $(wildcard test*.c) +OBJS = $(patsubst %.c,%.o,$(SRCS)) +EXECS = $(patsubst %.c,%,$(SRCS)) + + +.PHONY: all src check lint clean + +all: src $(EXECS) + +$(EXECS): %:%.o graded_test.o $(SRC_PATH)/access_counter.o ../utils/log/log.o | src + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(OBJS): %.o:%.c graded_test.h + +graded_test.o: graded_test.c graded_test.h + +../utils/log/log.o: ../utils/log/log.c ../utils/log/log.h + +src: + make -C $(FULL_SRC_PATH) + +check: + make -C $(FULL_SRC_PATH) clean + make clean + make -i SRC_PATH=$(FULL_SRC_PATH) + ./run_all_tests.sh + +lint: + -cd .. && checkpatch.pl -f src/*.c tests/*.c tests/*/*.c + -cd .. && checkpatch.pl -f checker/*.sh tests/*.sh + -cd .. && cpplint --recursive src/ tests/ checker/ + -cd .. && shellcheck checker/*.sh tests/*.sh + +clean: + -rm -f *~ + -rm -f graded_test.o $(OBJS) + -rm -f $(EXECS) diff --git a/content/chapters/data/lab/support/access-counter/tests/graded_test.c b/content/chapters/data/lab/support/access-counter/tests/graded_test.c new file mode 100644 index 0000000000..1eb03d1001 --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/tests/graded_test.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include +#include + +#include "./graded_test.h" + +static void my_itoa(size_t num, char *a) +{ + unsigned char digit3; + unsigned char digit2; + unsigned char digit1; + + /* Print at most 3 decimal digits. */ + if (num > 999) + num = 999; + + digit1 = num % 10; + num /= 10; + digit2 = num % 10; + num /= 10; + digit3 = num % 10; + + if (digit3 == 0) + a[0] = ' '; + else + a[0] = '0' + digit3; + + if (digit2 == 0) + a[1] = ' '; + else + a[1] = '0' + digit2; + + a[2] = '0' + digit1; +} + +/* + * Print test result. Printed message should fit in 72 characters. + * + * Print format is: + * + * description ...................... passed ... NNN + * description ...................... failed ... NNN + * 32 chars 24 chars 6 3 3 + */ + +static void print_test(const char *description, int result, size_t points) +{ + /* Make these global linkage, so it's only allocated once. */ + static char print_buffer[74]; + static const char failed[] = "failed"; + static const char passed[] = "passed"; + size_t i; + size_t len; + + /* Collect description in print_buffer. */ + len = MIN(strlen(description), 32); + for (i = 0; i < len; i++) + print_buffer[i] = description[i]; + for (i = len; i < 32; i++) + print_buffer[i] = ' '; + print_buffer[32] = ' '; + + /* Collect dots in print_buffer. */ + for (i = 0; i < 24; i++) + print_buffer[33+i] = '.'; + print_buffer[57] = ' '; + + /* Collect passed / failed. */ + for (i = 0; i < 6; i++) { + if (result == 1) + print_buffer[58+i] = passed[i]; + else + print_buffer[58+i] = failed[i]; + } + print_buffer[64] = ' '; + + /* Collect dots in print_buffer. */ + for (i = 0; i < 3; i++) + print_buffer[65+i] = '.'; + print_buffer[68] = ' '; + + /* Collect number. */ + if (result == 1) { + my_itoa(points, &print_buffer[69]); + } else { + print_buffer[69] = ' '; + print_buffer[70] = ' '; + print_buffer[71] = '0'; + } + + /* Collect newline. */ + print_buffer[72] = '\n'; + + write(1, print_buffer, 73); +} + +void run_test(struct graded_test *test) +{ + int res; + + res = test->function(); + print_test(test->description, res, test->points); +#ifdef EXIT_IF_FAIL + exit(EXIT_FAILURE); +#endif +} + +void run_tests(struct graded_test *tests, size_t count) +{ + size_t i; + + for (i = 0; i < count; i++) + run_test(&tests[i]); +} diff --git a/content/chapters/data/lab/support/access-counter/tests/graded_test.h b/content/chapters/data/lab/support/access-counter/tests/graded_test.h new file mode 100644 index 0000000000..f56f2e07ca --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/tests/graded_test.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#ifndef GRADED_TEST_H_ +#define GRADED_TEST_H_ 1 + +/* test function prototype */ +typedef int (*test_f)(void); + +struct graded_test { + test_f function; /* test/evaluation function */ + char *description; /* test description */ + size_t points; /* points for each test */ +}; + +void run_test(struct graded_test *test); +void run_tests(struct graded_test *tests, size_t count); + +#endif /* GRADED_TEST_H_ */ diff --git a/content/chapters/data/lab/support/access-counter/tests/run_all_tests.sh b/content/chapters/data/lab/support/access-counter/tests/run_all_tests.sh new file mode 100755 index 0000000000..ab36fd53de --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/tests/run_all_tests.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +( +./test +) | tee results.txt + +total=$(grep '\( passed \| failed \)' results.txt | rev | cut -d ' ' -f 1 | rev | paste -s -d'+' | bc) +echo "" +echo -n "Total: " +echo -n " " +LC_ALL=C printf "%3d/100\n" "$total" + +rm results.txt diff --git a/content/chapters/data/lab/support/access-counter/tests/test.c b/content/chapters/data/lab/support/access-counter/tests/test.c new file mode 100644 index 0000000000..a074f0fc98 --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/tests/test.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include +#include +#include + +#include "access_counter.h" +#include "utils.h" +#include "graded_test.h" + +static long page_size; + +static void do_read(const void *addr) +{ + char a; + + a = * (char *) addr; +} + +static void do_write(void *addr) +{ + * (char *) addr = 'a'; +} + +static void do_exec(void *addr) +{ + ((void (*)(void)) addr)(); +} + +static int test_acess_read(void) +{ + void *addr; + + addr = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DIE(addr == MAP_FAILED, "mmap"); + + counter = 0; + do_read(addr); + + return counter == 1; +} + +static int test_acess_write(void) +{ + void *addr; + + addr = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DIE(addr == MAP_FAILED, "mmap"); + + counter = 0; + do_write(addr); + + return counter == 2; +} + +static int test_acess_exec(void) +{ + void *addr; + + addr = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DIE(addr == MAP_FAILED, "mmap"); + + counter = 0; + do_exec(addr); + + return counter == 3; +} + +static int test_acess_read_write(void) +{ + void *addr; + + addr = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DIE(addr == MAP_FAILED, "mmap"); + + do_read(addr); + counter = 0; + do_write(addr); + + return counter == 1; +} + +static int test_acess_read_exec(void) +{ + void *addr; + + addr = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DIE(addr == MAP_FAILED, "mmap"); + + do_read(addr); + counter = 0; + do_exec(addr); + + return counter == 2; +} + +static int test_acess_write_exec(void) +{ + void *addr; + + addr = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DIE(addr == MAP_FAILED, "mmap"); + + do_write(addr); + counter = 0; + do_exec(addr); + + return counter == 1; +} + +static int test_acess_exec_read(void) +{ + void *addr; + + addr = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DIE(addr == MAP_FAILED, "mmap"); + + do_exec(addr); + counter = 0; + do_read(addr); + + return counter == 0; +} + +static int test_acess_exec_write(void) +{ + void *addr; + + addr = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DIE(addr == MAP_FAILED, "mmap"); + + do_exec(addr); + counter = 0; + do_write(addr); + + return counter == 0; +} + +static int test_acess_write_read(void) +{ + void *addr; + + addr = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DIE(addr == MAP_FAILED, "mmap"); + + do_write(addr); + counter = 0; + do_read(addr); + + return counter == 0; +} + +static struct graded_test all_tests[] = { + { test_acess_read, "test_acess_read", 9 }, + { test_acess_write, "test_acess_write", 9 }, + { test_acess_exec, "test_acess_exec", 10 }, + { test_acess_read_write, "test_acess_read_write", 12 }, + { test_acess_read_exec, "test_acess_read_exec", 12 }, + { test_acess_write_exec, "test_acess_write_exec", 12 }, + { test_acess_exec_read, "test_acess_exec_read", 12 }, + { test_acess_exec_write, "test_acess_exec_write", 12 }, + { test_acess_write_read, "test_acess_write_read", 12 } +}; + +int main(void) +{ + page_size = sysconf(_SC_PAGESIZE); + + register_sigsegv_handler(); + + /* Update to LOG_DEBUG to print debug messages. */ + log_set_level(LOG_INFO); + + run_tests(all_tests, sizeof(all_tests) / sizeof(all_tests[0])); + + return 0; +} + diff --git a/content/chapters/data/lab/support/access-counter/utils/log b/content/chapters/data/lab/support/access-counter/utils/log new file mode 120000 index 0000000000..6b4efecdf4 --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/utils/log @@ -0,0 +1 @@ +../../../../../../common/utils/log/ \ No newline at end of file diff --git a/content/chapters/data/lab/support/access-counter/utils/utils.h b/content/chapters/data/lab/support/access-counter/utils/utils.h new file mode 120000 index 0000000000..284faf5aec --- /dev/null +++ b/content/chapters/data/lab/support/access-counter/utils/utils.h @@ -0,0 +1 @@ +../../../../../../common/utils/utils.h \ No newline at end of file