mirror of
https://github.com/NeoFlock/neonucleus.git
synced 2025-09-24 09:03:32 +02:00
huge security fix
This commit is contained in:
parent
4a5562549c
commit
59eb01b890
1
TODO.md
1
TODO.md
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
# Bugfixes
|
# 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
|
- Do a huge audit at some point
|
||||||
- `nn_unicode_charWidth` appears to be bugged, look into that.
|
- `nn_unicode_charWidth` appears to be bugged, look into that.
|
||||||
|
|
||||||
|
@ -102,16 +102,6 @@ void *nn_fs_unwrapFD(nn_filesystem *fs, nn_size_t fd) {
|
|||||||
return file;
|
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) {
|
void nn_fs_readCost(nn_filesystem *fs, nn_size_t bytes, nn_component *component) {
|
||||||
nn_filesystemControl control = fs->control;
|
nn_filesystemControl control = fs->control;
|
||||||
nn_computer *computer = nn_getComputerOfComponent(component);
|
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)");
|
nn_setCError(computer, "bad path (string expected)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(nn_fs_illegalPath(path)) {
|
char canonical[NN_MAX_PATH];
|
||||||
|
if(nn_path_canonical(path, canonical)) {
|
||||||
nn_setCError(computer, "bad path (illegal path)");
|
nn_setCError(computer, "bad path (illegal path)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nn_lock(&fs->ctx, fs->lock);
|
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_unlock(&fs->ctx, fs->lock);
|
||||||
|
|
||||||
nn_return(computer, nn_values_integer(byteSize));
|
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)");
|
nn_setCError(computer, "bad path (string expected)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(nn_fs_illegalPath(path)) {
|
char canonical[NN_MAX_PATH];
|
||||||
|
if(nn_path_canonical(path, canonical)) {
|
||||||
nn_setCError(computer, "bad path (illegal path)");
|
nn_setCError(computer, "bad path (illegal path)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nn_lock(&fs->ctx, fs->lock);
|
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_unlock(&fs->ctx, fs->lock);
|
||||||
nn_return_boolean(computer, removed > 0);
|
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)");
|
nn_setCError(computer, "bad path (string expected)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(nn_fs_illegalPath(path)) {
|
char canonical[NN_MAX_PATH];
|
||||||
|
if(nn_path_canonical(path, canonical)) {
|
||||||
nn_setCError(computer, "bad path (illegal path)");
|
nn_setCError(computer, "bad path (illegal path)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nn_lock(&fs->ctx, fs->lock);
|
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);
|
nn_unlock(&fs->ctx, fs->lock);
|
||||||
|
|
||||||
// OpenOS does BULLSHIT with this thing, dividing it by 1000 and expecting it to be
|
// 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)");
|
nn_setCError(computer, "bad path #1 (string expected)");
|
||||||
return;
|
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)");
|
nn_setCError(computer, "bad path #1 (illegal path)");
|
||||||
return;
|
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)");
|
nn_setCError(computer, "bad path #2 (string expected)");
|
||||||
return;
|
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)");
|
nn_setCError(computer, "bad path #2 (illegal path)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nn_lock(&fs->ctx, fs->lock);
|
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_unlock(&fs->ctx, fs->lock);
|
||||||
nn_return(computer, nn_values_boolean(movedCount > 0));
|
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)");
|
nn_setCError(computer, "bad path (string expected)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(nn_fs_illegalPath(path)) {
|
char canonical[NN_MAX_PATH];
|
||||||
|
if(nn_path_canonical(path, canonical)) {
|
||||||
nn_setCError(computer, "bad path (illegal path)");
|
nn_setCError(computer, "bad path (illegal path)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nn_lock(&fs->ctx, fs->lock);
|
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);
|
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)");
|
nn_setCError(computer, "bad path (string expected)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(nn_fs_illegalPath(path)) {
|
char canonical[NN_MAX_PATH];
|
||||||
|
if(nn_path_canonical(path, canonical)) {
|
||||||
nn_setCError(computer, "bad path (illegal path)");
|
nn_setCError(computer, "bad path (illegal path)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nn_lock(&fs->ctx, fs->lock);
|
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);
|
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)");
|
nn_setCError(computer, "bad path (string expected)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(nn_fs_illegalPath(path)) {
|
char canonical[NN_MAX_PATH];
|
||||||
|
if(nn_path_canonical(path, canonical)) {
|
||||||
nn_setCError(computer, "bad path (illegal path)");
|
nn_setCError(computer, "bad path (illegal path)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nn_lock(&fs->ctx, fs->lock);
|
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_unlock(&fs->ctx, fs->lock);
|
||||||
|
|
||||||
nn_fs_createCost(fs, 1, component);
|
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)");
|
nn_setCError(computer, "bad path (string expected)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(nn_fs_illegalPath(path)) {
|
char canonical[NN_MAX_PATH];
|
||||||
|
if(nn_path_canonical(path, canonical)) {
|
||||||
nn_setCError(computer, "bad path (illegal path)");
|
nn_setCError(computer, "bad path (illegal path)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -358,7 +357,7 @@ void nn_fs_list(nn_filesystem *fs, nn_componentMethod *_, nn_component *componen
|
|||||||
|
|
||||||
nn_size_t fileCount = 0;
|
nn_size_t fileCount = 0;
|
||||||
nn_lock(&fs->ctx, fs->lock);
|
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);
|
nn_unlock(&fs->ctx, fs->lock);
|
||||||
|
|
||||||
if(files != NULL) {
|
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)");
|
nn_setCError(computer, "bad path (string expected)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(nn_fs_illegalPath(path)) {
|
char canonical[NN_MAX_PATH];
|
||||||
|
if(nn_path_canonical(path, canonical)) {
|
||||||
nn_setCError(computer, "bad path (illegal path)");
|
nn_setCError(computer, "bad path (illegal path)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -406,7 +406,7 @@ void nn_fs_open(nn_filesystem *fs, nn_componentMethod *_, nn_component *componen
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void *file = fs->table.open(fs->table.userdata, path, mode);
|
void *file = fs->table.open(fs->table.userdata, canonical, mode);
|
||||||
if(file == NULL) {
|
if(file == NULL) {
|
||||||
nn_unlock(&fs->ctx, fs->lock);
|
nn_unlock(&fs->ctx, fs->lock);
|
||||||
nn_setCError(computer, "no such file or directory");
|
nn_setCError(computer, "no such file or directory");
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
typedef struct nn_vfnode {
|
typedef struct nn_vfnode {
|
||||||
struct nn_vfilesystem *fs;
|
struct nn_vfilesystem *fs;
|
||||||
struct nn_vfnode *parent;
|
|
||||||
char name[NN_MAX_PATH];
|
char name[NN_MAX_PATH];
|
||||||
|
struct nn_vfnode *parent;
|
||||||
nn_bool_t isDirectory;
|
nn_bool_t isDirectory;
|
||||||
union {
|
union {
|
||||||
// if directory
|
// if directory
|
||||||
@ -57,8 +57,8 @@ nn_vfnode *nn_vf_allocFile(nn_vfilesystem *fs, const char *name) {
|
|||||||
if(node == NULL) return NULL;
|
if(node == NULL) return NULL;
|
||||||
*node = (nn_vfnode) {
|
*node = (nn_vfnode) {
|
||||||
.fs = fs,
|
.fs = fs,
|
||||||
.parent = NULL,
|
|
||||||
.lastModified = nn_vf_now(fs),
|
.lastModified = nn_vf_now(fs),
|
||||||
|
.parent = NULL,
|
||||||
.isDirectory = false,
|
.isDirectory = false,
|
||||||
.data = NULL,
|
.data = NULL,
|
||||||
.len = 0,
|
.len = 0,
|
||||||
@ -81,8 +81,8 @@ nn_vfnode *nn_vf_allocDirectory(nn_vfilesystem *fs, const char *name) {
|
|||||||
}
|
}
|
||||||
*node = (nn_vfnode) {
|
*node = (nn_vfnode) {
|
||||||
.fs = fs,
|
.fs = fs,
|
||||||
.parent = NULL,
|
|
||||||
.lastModified = nn_vf_now(fs),
|
.lastModified = nn_vf_now(fs),
|
||||||
|
.parent = NULL,
|
||||||
.isDirectory = false,
|
.isDirectory = false,
|
||||||
.entries = buffer,
|
.entries = buffer,
|
||||||
.len = 0,
|
.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
|
// methods
|
||||||
|
|
||||||
void nn_vfs_deinit(nn_vfilesystem *fs) {
|
void nn_vfs_deinit(nn_vfilesystem *fs) {
|
||||||
|
@ -408,7 +408,7 @@ sandbox = {
|
|||||||
lower = string.lower,
|
lower = string.lower,
|
||||||
wtrunc = function (str,space)
|
wtrunc = function (str,space)
|
||||||
space = space - 1
|
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,
|
end,
|
||||||
}),
|
}),
|
||||||
checkArg = checkArg,
|
checkArg = checkArg,
|
||||||
|
53
src/utils.c
53
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
|
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
|
// attempts to convert a random barely legal path
|
||||||
// if this shit is ever bugged and a sandbox escape is done
|
// if this shit is ever bugged and a sandbox escape is done
|
||||||
// by tricking it into sneaking some .. in there
|
// 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)) {
|
if(!nn_path_isValid(path)) {
|
||||||
// HELL NO
|
// 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
|
// 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
|
// in theory this is suboptimal, however, I'm lazy
|
||||||
nn_memset(canonical, 0, NN_MAX_PATH);
|
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
|
// TODO: burn it with fire and get banned from programming
|
||||||
for(nn_size_t i = 0; path[i]; i++) {
|
while(true) {
|
||||||
// all of this is very slow
|
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;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user