forked from capablevms/cheri-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sentry.c
167 lines (146 loc) · 5.03 KB
/
sentry.c
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
* An example showing how a sentry can be used for more advanced
* security model. In CHERIv8 every function is actually a sentry.
* However sentries can allow for additional security when used with
* a level of indirection. In this example an assembly trampoline is
* used to load additional data, that isn't available to the user.
*/
#include "include/common.h"
#include "include/instructions.h"
#include "include/regs.h"
#include <cheri/cheric.h>
#include <err.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
struct data
{
int a;
int b;
char c[256];
};
void *setup_sentry(void *, void (*f)(uint32_t *, void *));
void oop_sentry(struct data *, int);
void simple_sentry(int);
// This is the function that will be called through a sentry and
// the first argument is passed by the trampoline assembly.
void oop_sentry(struct data *this, int arg)
{
printf("OOP: %p %d \n", this, arg);
}
// This function will also be called through a sentry but no
// arguments are added.
void simple_sentry(int arg)
{
printf("Simple: %d \n", arg);
}
// A function to generate the code that will put the
// this structure as first argument and move the rest of the
// arguments to the next registers.
void gen_oop(uint32_t *code, void *function)
{
intptr_t *code_data = (intptr_t *) code;
// The additional data needs to be stored somewhere in memory
// where it can not be accessed by the user of the function.
// This is why it is stored in the same place where the code will be.
// As the code is not modifiable and not readable this data is also
// protected.
code_data[255] = (intptr_t) function;
code_data[254] = (intptr_t) malloc(sizeof(struct data));
uint32_t idx = 0;
code[idx++] = auipcc(cs2, 1);
code[idx++] = clc_128(cs3, cs2, ((-16) + (2 << 20)));
// Move the arguments to the next register.
// This will break if we have more then 6 arguments but that should be fine.
code[idx++] = addi(a1, a0, 0);
code[idx++] = addi(a2, a1, 0);
code[idx++] = addi(a3, a2, 0);
code[idx++] = addi(a4, a3, 0);
code[idx++] = addi(a5, a4, 0);
code[idx++] = addi(a6, a5, 0);
code[idx++] = addi(a7, a6, 0);
code[idx++] = clc_128(ca0, cs2, ((-32) + (2 << 20)));
code[idx++] = cjalr(cnull, cs3);
}
void gen_simple(uint32_t *code, void *function)
{
intptr_t *code_data = (intptr_t *) code;
code_data[255] = (intptr_t) function;
uint32_t idx = 0;
code[idx++] = auipcc(cs2, 1);
code[idx++] = clc_128(cs3, cs2, ((-16) + (2 << 20)));
code[idx++] = cjalr(cnull, cs3);
}
void gen_override(uint32_t *code, void *function)
{
intptr_t *code_data = (intptr_t *) code;
code_data[255] = (intptr_t) function;
// Here we again generate code for calling the passed function,
// but the code modifies the first argument to be different then
// the one passed. I am not sure how this is useful.
uint32_t idx = 0;
code[idx++] = auipcc(cs2, 1);
code[idx++] = clc_128(cs3, cs2, ((-16) + (2 << 20)));
code[idx++] = addi(a0, cnull, 42);
code[idx++] = cjalr(cnull, cs3);
}
// Here we allocate the memory for the code that will be generated.
void *setup_sentry(void *function, void (*generate_code)(uint32_t *, void *))
{
// As the function is returned as a sentry the pointer to it cannot be read or written to.
// This means that there is no need to remove write protection as only the function itself
// can change its own code.
uint32_t *code =
mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
generate_code(code, function);
// We set the flag to run the code in CHERI mode and seal it so it can only be called.
return cheri_sealentry(cheri_setflags(code, 1));
}
void *setup_normal(void *function, void (*generate_code)(uint32_t *, void *))
{
uint32_t *code =
mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
generate_code(code, function);
// Here the resulting capability isn't sealed to show that unsealed capabilities can be
// called from the same code.
return cheri_setflags(code, 1);
}
int main()
{
puts("0: Simple sentry");
puts("1: OOP sentry");
puts("2: Override sentry");
puts("3: Normal function");
puts("4: Fail");
printf("Mode: ");
uint32_t mode = 0;
if (scanf("%u", &mode) == 0)
{
error("Invalid input");
}
void (*fpointer)(int);
switch (mode)
{
case 0:
fpointer = setup_sentry(simple_sentry, gen_simple);
break;
case 1:
fpointer = setup_sentry(oop_sentry, gen_oop);
break;
case 2:
fpointer = setup_sentry(simple_sentry, gen_override);
break;
case 3:
fpointer = setup_normal(simple_sentry, gen_simple);
break;
case 4: // This case should fail
fpointer = setup_sentry(simple_sentry, gen_simple);
fpointer = cheri_incoffset(fpointer, 16);
break;
default:
err(1, "Invalid option selected\n");
}
fpointer(99);
printf("END\n");
}