commit d9cdf19

aabacchus  ·  2022-06-20 02:58:29 +0000 UTC
parent fdb52a7
add simple file sandboxing below cwd

Uses realpath(3) libc function to find the canonical and longest part of the
pathname which exists, then checking if it starts with cwd.

Prints a warning to stderr if an attempt is made to access a file outside the
sandbox.
1 files changed,  +60, -2
+60, -2
  1@@ -1,9 +1,17 @@
  2+#define _XOPEN_SOURCE 500
  3 #include <stdio.h>
  4 #include <dirent.h>
  5+#include <errno.h>
  6+#include <limits.h>
  7 #include <string.h>
  8+#include <stdlib.h>
  9 #include <sys/stat.h>
 10 #include <unistd.h>
 11 
 12+#ifndef PATH_MAX
 13+#define PATH_MAX 4096
 14+#endif
 15+
 16 #include "../uxn.h"
 17 #include "file.h"
 18 
 19@@ -27,6 +35,7 @@ typedef struct {
 20 		FILE_READ,
 21 		FILE_WRITE,
 22 		DIR_READ } state;
 23+	int outside_sandbox;
 24 } UxnFile;
 25 
 26 static UxnFile uxn_file[POLYFILEY];
 27@@ -44,6 +53,7 @@ reset(UxnFile *c)
 28 	}
 29 	c->de = NULL;
 30 	c->state = IDLE;
 31+	c->outside_sandbox = 0;
 32 }
 33 
 34 static Uint16
 35@@ -84,6 +94,49 @@ file_read_dir(UxnFile *c, char *dest, Uint16 len)
 36 	return p - dest;
 37 }
 38 
 39+static char *
 40+retry_realpath(const char *file_name)
 41+{
 42+	char r[PATH_MAX] = {'\0'}, p[PATH_MAX] = {'\0'}, *x;
 43+	if(file_name == NULL) {
 44+		errno = EINVAL;
 45+		return NULL;
 46+	} else if(strlen(file_name) >= PATH_MAX) {
 47+		errno = ENAMETOOLONG;
 48+		return NULL;
 49+	}
 50+	if(file_name[0] != '/') {
 51+		/* TODO: use a macro instead of '/' for absolute path first character so that other systems can work */
 52+		/* if a relative path, prepend cwd */
 53+		getcwd(p, sizeof(p));
 54+		strcat(p, "/"); /* TODO: use a macro instead of '/' for the path delimiter */
 55+	}
 56+	strcat(p, file_name);
 57+	while(realpath(p, r) == NULL) {
 58+		if(errno != ENOENT)
 59+			return NULL;
 60+		x = strrchr(p, '/'); /* TODO: path delimiter macro */
 61+		if(x)
 62+			*x = '\0';
 63+		else
 64+			return NULL;
 65+	}
 66+	return strdup(r);
 67+}
 68+
 69+static void
 70+file_check_sandbox(UxnFile *c)
 71+{
 72+	char *x, *rp, cwd[PATH_MAX] = {'\0'};
 73+	x = getcwd(cwd, sizeof(cwd));
 74+	rp = retry_realpath(c->current_filename);
 75+	if(rp == NULL || (x && strncmp(cwd, rp, strlen(cwd)) != 0)) {
 76+		c->outside_sandbox = 1;
 77+		fprintf(stderr, "file warning: blocked attempt to access %s outside of sandbox\n", c->current_filename);
 78+	}
 79+	free(rp);
 80+}
 81+
 82 static Uint16
 83 file_init(UxnFile *c, char *filename, size_t max_len)
 84 {
 85@@ -92,8 +145,10 @@ file_init(UxnFile *c, char *filename, size_t max_len)
 86 	reset(c);
 87 	if(len > max_len) len = max_len;
 88 	while(len) {
 89-		if((*p++ = *filename++) == '\0')
 90+		if((*p++ = *filename++) == '\0') {
 91+			file_check_sandbox(c);
 92 			return 0;
 93+		}
 94 		len--;
 95 	}
 96 	c->current_filename[0] = '\0';
 97@@ -103,6 +158,7 @@ file_init(UxnFile *c, char *filename, size_t max_len)
 98 static Uint16
 99 file_read(UxnFile *c, void *dest, Uint16 len)
100 {
101+	if(c->outside_sandbox) return 0;
102 	if(c->state != FILE_READ && c->state != DIR_READ) {
103 		reset(c);
104 		if((c->dir = opendir(c->current_filename)) != NULL)
105@@ -121,6 +177,7 @@ static Uint16
106 file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags)
107 {
108 	Uint16 ret = 0;
109+	if(c->outside_sandbox) return 0;
110 	if(c->state != FILE_WRITE) {
111 		reset(c);
112 		if((c->f = fopen(c->current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL)
113@@ -137,6 +194,7 @@ static Uint16
114 file_stat(UxnFile *c, void *dest, Uint16 len)
115 {
116 	char *basename = strrchr(c->current_filename, '/');
117+	if(c->outside_sandbox) return 0;
118 	if(basename != NULL)
119 		basename++;
120 	else
121@@ -147,7 +205,7 @@ file_stat(UxnFile *c, void *dest, Uint16 len)
122 static Uint16
123 file_delete(UxnFile *c)
124 {
125-	return unlink(c->current_filename);
126+	return c->outside_sandbox ? 0 : unlink(c->current_filename);
127 }
128 
129 /* IO */