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
|
#include <camellia/flags.h>
#include <camellia/syscalls.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <camellia/fs/dir.h>
#include <camellia/fs/misc.h>
static int dir_seglen2(const char *path, size_t len) {
/* returns the length of the first segment of the path, including the trailing / (if any). */
for (size_t i = 0; i < len; i++) {
if (path[i] == '/')
return i + 1;
}
return len;
}
/** @return the length of the path w/o suffixes */
static size_t suffix_parse(const char *path, size_t len, bool *ro_ptr) {
bool ro = false;
if (len >= 3 && !memcmp(path + len - 3, ":ro", 3)) {
ro = true;
len -= 3;
}
if (ro_ptr) *ro_ptr = ro;
return len;
}
/** Check if a path is a prefix of another path. */
// TODO move to libc; tests
static bool prefix_match(const char *prefix, size_t plen, const char *full, size_t flen) {
if (flen < plen) return false;
if (flen == plen)
return memcmp(full, prefix, flen) == 0;
return plen >= 1
&& prefix[plen - 1] == '/' /* prefixes must point to one of the parent directories */
&& memcmp(full, prefix, plen) == 0;
}
void fs_whitelist(const char **whitelist) {
const size_t buflen = 1024;
char *buf = malloc(buflen);
if (!buf) exit(1);
for (;;) {
struct ufs_request res;
handle_t reqh = _syscall_fs_wait(buf, buflen, &res);
if (reqh < 0) break;
char *ipath = res.id; /* the path of the open()ed directory */
switch (res.op) {
case VFSOP_OPEN: {
bool error = false;
bool passthru = false;
bool inject = false;
for (const char **entry = whitelist; *entry; entry++) {
bool ro = false;
size_t entry_len = suffix_parse(*entry, strlen(*entry), &ro);
/* If *entry is a prefix of the opened path, pass the open() through. */
if (prefix_match(*entry, entry_len, buf, res.len)) {
passthru = true;
if (ro && OPEN_WRITEABLE(res.flags))
error = true;
break;
}
/* If the path is a prefix of *entry, we might need to inject a directory. */
if (prefix_match(buf, res.len, *entry, entry_len)) {
inject = true;
}
}
if (error) {
_syscall_fs_respond(reqh, NULL, -EACCES, 0);
} else if (passthru) {
forward_open(reqh, buf, res.len, res.flags);
} else if (inject) {
// TODO all the inject points could be precomputed
ipath = malloc(res.len + 1);
memcpy(ipath, buf, res.len);
ipath[res.len] = '\0';
_syscall_fs_respond(reqh, ipath, 0, 0);
} else {
_syscall_fs_respond(reqh, NULL, -1, 0);
}
break;
}
case VFSOP_READ:
case VFSOP_GETSIZE: {
struct dirbuild db;
size_t ilen = strlen(ipath);
char *target = res.op == VFSOP_READ ? buf : NULL;
dir_start(&db, res.offset, target, buflen);
for (const char **entry = whitelist; *entry; entry++) {
// TODO could be precomputed too
size_t elen = suffix_parse(*entry, strlen(*entry), NULL);
if (ilen < elen && !memcmp(ipath, *entry, ilen))
dir_appendl(&db, *entry + ilen, dir_seglen2(*entry + ilen, elen - ilen));
}
_syscall_fs_respond(reqh, target, dir_finish(&db), 0);
break;
}
case VFSOP_CLOSE: {
free(ipath);
_syscall_fs_respond(reqh, NULL, 0, 0);
break;
}
default: {
_syscall_fs_respond(reqh, NULL, -1, 0);
break;
}
}
}
exit(0);
}
|