Skip to content

Commit

Permalink
data/lab/arena: Add access-counter challenge
Browse files Browse the repository at this point in the history
A challenge to exercise mprotect() and an intro in SIGSEGV handling.

Signed-off-by: Razvan Deaconescu <[email protected]>
  • Loading branch information
razvand committed Nov 6, 2023
1 parent 1cdd478 commit 5cedee1
Show file tree
Hide file tree
Showing 15 changed files with 627 additions and 0 deletions.
40 changes: 40 additions & 0 deletions content/chapters/data/lab/content/arena.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions content/chapters/data/lab/solution/access-counter/src/Makefile
Original file line number Diff line number Diff line change
@@ -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 *~
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>

#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");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef ACCESS_PRINTER_H_
#define ACCESS_PRINTER_H_ 1

extern unsigned long counter;
void register_sigsegv_handler(void);

#endif
14 changes: 14 additions & 0 deletions content/chapters/data/lab/support/access-counter/src/Makefile
Original file line number Diff line number Diff line change
@@ -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 *~
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>

#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");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef ACCESS_PRINTER_H_
#define ACCESS_PRINTER_H_ 1

extern unsigned long counter;
void register_sigsegv_handler(void);

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/test
44 changes: 44 additions & 0 deletions content/chapters/data/lab/support/access-counter/tests/Makefile
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit 5cedee1

Please sign in to comment.