diff --git a/TODO.md b/TODO.md index 59f68d8..2b5d6d4 100644 --- a/TODO.md +++ b/TODO.md @@ -16,7 +16,6 @@ # Bugfixes -- Rework filesystem component to pre-process paths to ensure proper sandboxing and not allow arbitrary remote file access - Do a huge audit at some point - `nn_unicode_charWidth` appears to be bugged, look into that. diff --git a/src/components/filesystem.c b/src/components/filesystem.c index 77f09e4..ff93acc 100644 --- a/src/components/filesystem.c +++ b/src/components/filesystem.c @@ -102,16 +102,6 @@ void *nn_fs_unwrapFD(nn_filesystem *fs, nn_size_t fd) { return file; } -nn_bool_t nn_fs_illegalPath(const char *path) { - // absolute disaster - const char *illegal = "\"\\:*?<>|"; - - for(nn_size_t i = 0; illegal[i] != '\0'; i++) { - if(nn_strchr(path, illegal[i]) != NULL) return true; - } - return false; -} - void nn_fs_readCost(nn_filesystem *fs, nn_size_t bytes, nn_component *component) { nn_filesystemControl control = fs->control; nn_computer *computer = nn_getComputerOfComponent(component); @@ -201,13 +191,14 @@ void nn_fs_size(nn_filesystem *fs, nn_componentMethod *_, nn_component *componen nn_setCError(computer, "bad path (string expected)"); return; } - if(nn_fs_illegalPath(path)) { + char canonical[NN_MAX_PATH]; + if(nn_path_canonical(path, canonical)) { nn_setCError(computer, "bad path (illegal path)"); return; } nn_lock(&fs->ctx, fs->lock); - nn_size_t byteSize = fs->table.size(fs->table.userdata, path); + nn_size_t byteSize = fs->table.size(fs->table.userdata, canonical); nn_unlock(&fs->ctx, fs->lock); nn_return(computer, nn_values_integer(byteSize)); @@ -220,13 +211,14 @@ void nn_fs_remove(nn_filesystem *fs, nn_componentMethod *_, nn_component *compon nn_setCError(computer, "bad path (string expected)"); return; } - if(nn_fs_illegalPath(path)) { + char canonical[NN_MAX_PATH]; + if(nn_path_canonical(path, canonical)) { nn_setCError(computer, "bad path (illegal path)"); return; } nn_lock(&fs->ctx, fs->lock); - nn_size_t removed = fs->table.remove(fs->table.userdata, path); + nn_size_t removed = fs->table.remove(fs->table.userdata, canonical); nn_unlock(&fs->ctx, fs->lock); nn_return_boolean(computer, removed > 0); @@ -240,13 +232,14 @@ void nn_fs_lastModified(nn_filesystem *fs, nn_componentMethod *_, nn_component * nn_setCError(computer, "bad path (string expected)"); return; } - if(nn_fs_illegalPath(path)) { + char canonical[NN_MAX_PATH]; + if(nn_path_canonical(path, canonical)) { nn_setCError(computer, "bad path (illegal path)"); return; } nn_lock(&fs->ctx, fs->lock); - nn_size_t t = fs->table.lastModified(fs->table.userdata, path); + nn_size_t t = fs->table.lastModified(fs->table.userdata, canonical); nn_unlock(&fs->ctx, fs->lock); // OpenOS does BULLSHIT with this thing, dividing it by 1000 and expecting it to be @@ -264,7 +257,8 @@ void nn_fs_rename(nn_filesystem *fs, nn_componentMethod *_, nn_component *compon nn_setCError(computer, "bad path #1 (string expected)"); return; } - if(nn_fs_illegalPath(from)) { + char canonicalFrom[NN_MAX_PATH]; + if(nn_path_canonical(from, canonicalFrom)) { nn_setCError(computer, "bad path #1 (illegal path)"); return; } @@ -275,13 +269,14 @@ void nn_fs_rename(nn_filesystem *fs, nn_componentMethod *_, nn_component *compon nn_setCError(computer, "bad path #2 (string expected)"); return; } - if(nn_fs_illegalPath(to)) { + char canonicalTo[NN_MAX_PATH]; + if(nn_path_canonical(to, canonicalTo)) { nn_setCError(computer, "bad path #2 (illegal path)"); return; } nn_lock(&fs->ctx, fs->lock); - nn_size_t movedCount = fs->table.rename(fs->table.userdata, from, to); + nn_size_t movedCount = fs->table.rename(fs->table.userdata, canonicalFrom, canonicalTo); nn_unlock(&fs->ctx, fs->lock); nn_return(computer, nn_values_boolean(movedCount > 0)); @@ -296,13 +291,14 @@ void nn_fs_exists(nn_filesystem *fs, nn_componentMethod *_, nn_component *compon nn_setCError(computer, "bad path (string expected)"); return; } - if(nn_fs_illegalPath(path)) { + char canonical[NN_MAX_PATH]; + if(nn_path_canonical(path, canonical)) { nn_setCError(computer, "bad path (illegal path)"); return; } nn_lock(&fs->ctx, fs->lock); - nn_return_boolean(computer, fs->table.exists(fs->table.userdata, path)); + nn_return_boolean(computer, fs->table.exists(fs->table.userdata, canonical)); nn_unlock(&fs->ctx, fs->lock); } @@ -313,13 +309,14 @@ void nn_fs_isDirectory(nn_filesystem *fs, nn_componentMethod *_, nn_component *c nn_setCError(computer, "bad path (string expected)"); return; } - if(nn_fs_illegalPath(path)) { + char canonical[NN_MAX_PATH]; + if(nn_path_canonical(path, canonical)) { nn_setCError(computer, "bad path (illegal path)"); return; } nn_lock(&fs->ctx, fs->lock); - nn_return_boolean(computer, fs->table.isDirectory(fs->table.userdata, path)); + nn_return_boolean(computer, fs->table.isDirectory(fs->table.userdata, canonical)); nn_unlock(&fs->ctx, fs->lock); } @@ -330,13 +327,14 @@ void nn_fs_makeDirectory(nn_filesystem *fs, nn_componentMethod *_, nn_component nn_setCError(computer, "bad path (string expected)"); return; } - if(nn_fs_illegalPath(path)) { + char canonical[NN_MAX_PATH]; + if(nn_path_canonical(path, canonical)) { nn_setCError(computer, "bad path (illegal path)"); return; } nn_lock(&fs->ctx, fs->lock); - nn_return_boolean(computer, fs->table.makeDirectory(fs->table.userdata, path)); + nn_return_boolean(computer, fs->table.makeDirectory(fs->table.userdata, canonical)); nn_unlock(&fs->ctx, fs->lock); nn_fs_createCost(fs, 1, component); @@ -349,7 +347,8 @@ void nn_fs_list(nn_filesystem *fs, nn_componentMethod *_, nn_component *componen nn_setCError(computer, "bad path (string expected)"); return; } - if(nn_fs_illegalPath(path)) { + char canonical[NN_MAX_PATH]; + if(nn_path_canonical(path, canonical)) { nn_setCError(computer, "bad path (illegal path)"); return; } @@ -358,7 +357,7 @@ void nn_fs_list(nn_filesystem *fs, nn_componentMethod *_, nn_component *componen nn_size_t fileCount = 0; nn_lock(&fs->ctx, fs->lock); - char **files = fs->table.list(alloc, fs->table.userdata, path, &fileCount); + char **files = fs->table.list(alloc, fs->table.userdata, canonical, &fileCount); nn_unlock(&fs->ctx, fs->lock); if(files != NULL) { @@ -380,7 +379,8 @@ void nn_fs_open(nn_filesystem *fs, nn_componentMethod *_, nn_component *componen nn_setCError(computer, "bad path (string expected)"); return; } - if(nn_fs_illegalPath(path)) { + char canonical[NN_MAX_PATH]; + if(nn_path_canonical(path, canonical)) { nn_setCError(computer, "bad path (illegal path)"); return; } @@ -406,7 +406,7 @@ void nn_fs_open(nn_filesystem *fs, nn_componentMethod *_, nn_component *componen return; } } - void *file = fs->table.open(fs->table.userdata, path, mode); + void *file = fs->table.open(fs->table.userdata, canonical, mode); if(file == NULL) { nn_unlock(&fs->ctx, fs->lock); nn_setCError(computer, "no such file or directory"); diff --git a/src/components/volatileFilesystem.c b/src/components/volatileFilesystem.c index 12ad62d..52cc741 100644 --- a/src/components/volatileFilesystem.c +++ b/src/components/volatileFilesystem.c @@ -6,8 +6,8 @@ typedef struct nn_vfnode { struct nn_vfilesystem *fs; - struct nn_vfnode *parent; char name[NN_MAX_PATH]; + struct nn_vfnode *parent; nn_bool_t isDirectory; union { // if directory @@ -57,8 +57,8 @@ nn_vfnode *nn_vf_allocFile(nn_vfilesystem *fs, const char *name) { if(node == NULL) return NULL; *node = (nn_vfnode) { .fs = fs, - .parent = NULL, .lastModified = nn_vf_now(fs), + .parent = NULL, .isDirectory = false, .data = NULL, .len = 0, @@ -81,8 +81,8 @@ nn_vfnode *nn_vf_allocDirectory(nn_vfilesystem *fs, const char *name) { } *node = (nn_vfnode) { .fs = fs, - .parent = NULL, .lastModified = nn_vf_now(fs), + .parent = NULL, .isDirectory = false, .entries = buffer, .len = 0, @@ -121,6 +121,44 @@ nn_size_t nn_vf_spaceUsedByNode(nn_vfnode *node) { } } +nn_vfnode *nn_vf_find(nn_vfnode *parent, const char *name) { + if(parent->isDirectory) return NULL; + for(nn_size_t i = 0; i < parent->len; i++) { + nn_vfnode *entry = parent->entries[i]; + + if(nn_strcmp(entry->name, name) == 0) { + return entry; + } + } + return NULL; +} + +nn_bool_t nn_vf_ensureFileCapacity(nn_vfnode *file, nn_size_t capacity) { + if(file->isDirectory) return false; + nn_Alloc *alloc = &file->fs->ctx.allocator; + + if(file->cap >= capacity) return true; // already at that point + + char *newData = nn_resize(alloc, file->data, file->cap, capacity); + if(newData == NULL) { + return false; // OOM + } + file->data = newData; + file->cap = capacity; + + return true; +} + +// this is used to compute exponential backoff +// TODO: add an option to select either a slower growth rate or +// linear backoff to reduce memory usage at the cost of speed +nn_size_t nn_vf_getIdealCapacity(nn_vfnode *file, nn_size_t spaceNeeded) { + nn_size_t cap = file->cap; + if(cap == 0) cap = 1; + while(cap < spaceNeeded) cap *= 2; // this would mean a file with 1,048,577 bytes takes up 2,097,152 bytes, potentially wasting 1,048,575 bytes + return cap; +} + // methods void nn_vfs_deinit(nn_vfilesystem *fs) { diff --git a/src/sandbox.lua b/src/sandbox.lua index b359cfc..d709c53 100644 --- a/src/sandbox.lua +++ b/src/sandbox.lua @@ -408,7 +408,7 @@ sandbox = { lower = string.lower, wtrunc = function (str,space) space = space - 1 - return str:sub(1,(space >= utf8.len(str)) and (#str) or (utf8.offset(str,space+1)-1)) + return unicode.sub(str, 1, space) end, }), checkArg = checkArg, diff --git a/src/utils.c b/src/utils.c index aabd258..f9a1593 100644 --- a/src/utils.c +++ b/src/utils.c @@ -388,7 +388,14 @@ nn_bool_t nn_path_isValid(const char *path) { return nn_strlen(path) < NN_MAX_PATH; // less then because we depend on the terminator } -nn_bool_t nn_path_canonical(const char path[NN_MAX_PATH], char canonical[NN_MAX_PATH]) { +static nn_bool_t nni_path_isAllDots(const char *path, nn_size_t len) { + for(nn_size_t i = 0; i < len; i++) { + if(path[i] != '.') return false; + } + return true; +} + +nn_bool_t nn_path_canonical(const char *path, char canonical[NN_MAX_PATH]) { // attempts to convert a random barely legal path // if this shit is ever bugged and a sandbox escape is done // by tricking it into sneaking some .. in there @@ -396,23 +403,49 @@ nn_bool_t nn_path_canonical(const char path[NN_MAX_PATH], char canonical[NN_MAX_ if(!nn_path_isValid(path)) { // HELL NO - return false; + return true; } - // tmp shit because lazy, maybe the optimizer be with us - char junk[NN_MAX_PATH]; - // 0'd out because it fills it up with terminators, simplifying the rest of the code // in theory this is suboptimal, however, I'm lazy nn_memset(canonical, 0, NN_MAX_PATH); - size_t ptr = 0; + nn_size_t ptr = 0; + nn_size_t i = 0; // TODO: burn it with fire and get banned from programming - for(nn_size_t i = 0; path[i]; i++) { - // all of this is very slow + while(true) { + while(path[i] == '/') i++; // just do not ask + if(path[i] == 0) break; - while(path[i] == '/') continue; // just do not ask + const char *subpath = path + i; + nn_size_t len = nn_path_firstSlash(subpath); + + if(len == 0) { + len = nn_strlen(path) - i; + } + + if(nni_path_isAllDots(subpath, len)) { + // we don't actually resolve them because they shouldn't be there + // to begin with + i += len; + continue; + } + + if(ptr == 0) { + // at the start + nn_memcpy(canonical, subpath, len); + ptr = len; + i += len; + continue; + } + // just append to it + canonical[ptr] = '/'; + ptr++; + nn_memcpy(canonical + ptr, subpath, len); + ptr += len; + i += len; + continue; } - return true; + return false; }