From a835fa4792fa2afd84d7e27bd242add06763db72 Mon Sep 17 00:00:00 2001
From: dzwdz
Date: Sun, 2 Oct 2022 15:49:24 +0200
Subject: user/libc: rework fs_whitelist; fix minor bugs

---
 src/user/lib/fs/misc.c      |  81 +-------------------------------
 src/user/lib/fs/whitelist.c | 109 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 110 insertions(+), 80 deletions(-)
 create mode 100644 src/user/lib/fs/whitelist.c

(limited to 'src')

diff --git a/src/user/lib/fs/misc.c b/src/user/lib/fs/misc.c
index 7a66b80..f660f6f 100644
--- a/src/user/lib/fs/misc.c
+++ b/src/user/lib/fs/misc.c
@@ -22,8 +22,7 @@ bool fork2_n_mount(const char *path) {
 }
 
 static int dir_seglen(const char *path) {
-	/* in human terms:
-	 * if path contains /, return its position + 1
+	/* if path contains /, return its position + 1
 	 * otherwise, return strlen */
 	int len = 0;
 	while (path[len]) {
@@ -85,84 +84,6 @@ void fs_passthru(const char *prefix) {
 	exit(0);
 }
 
-void fs_whitelist(const char **list) {
-	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;
-		size_t blen;
-		bool passthru, inject;
-		struct dirbuild db;
-
-		switch (res.op) {
-			case VFSOP_OPEN:
-				passthru = false;
-				inject = false;
-
-				for (const char **iter = list; *iter; iter++) {
-					size_t len = strlen(*iter);
-					bool ro = false;
-					if (len >= 3 && !memcmp(*iter + len - 3, ":ro", 3)) {
-						ro = true;
-						len -= 3;
-					}
-					if (len <= res.len && !memcmp(buf, *iter, len)) {
-						if (ro) res.flags |= OPEN_RO;
-						passthru = true;
-						break;
-					}
-					if (res.len < len &&
-						buf[res.len - 1] == '/' &&
-						!memcmp(buf, *iter, res.len))
-					{
-						inject = true;
-					}
-				}
-				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:
-				blen = strlen(ipath);
-				char *target = res.op == VFSOP_READ ? buf : NULL;
-				dir_start(&db, res.offset, target, buflen);
-				for (const char **iter = list; *iter; iter++) {
-					// TODO could be precomputed too
-					size_t len = strlen(*iter); // inefficient, whatever
-					if (blen < len && !memcmp(ipath, *iter, blen))
-						dir_appendl(&db, *iter + blen, dir_seglen(*iter + blen));
-				}
-				_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);
-}
-
 void fs_union(const char **list) {
 	struct ufs_request res;
 
diff --git a/src/user/lib/fs/whitelist.c b/src/user/lib/fs/whitelist.c
new file mode 100644
index 0000000..676b36f
--- /dev/null
+++ b/src/user/lib/fs/whitelist.c
@@ -0,0 +1,109 @@
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <stdlib.h>
+#include <string.h>
+#include <user/lib/fs/dir.h>
+#include <user/lib/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 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)) {
+						if (ro) res.flags |= OPEN_RO;
+						passthru = 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 (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);
+}
-- 
cgit v1.2.3