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.
This commit is contained in:
parent
fdb52a7480
commit
d9cdf192a8
|
@ -1,9 +1,17 @@
|
||||||
|
#define _XOPEN_SOURCE 500
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifndef PATH_MAX
|
||||||
|
#define PATH_MAX 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "../uxn.h"
|
#include "../uxn.h"
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
|
||||||
|
@ -27,6 +35,7 @@ typedef struct {
|
||||||
FILE_READ,
|
FILE_READ,
|
||||||
FILE_WRITE,
|
FILE_WRITE,
|
||||||
DIR_READ } state;
|
DIR_READ } state;
|
||||||
|
int outside_sandbox;
|
||||||
} UxnFile;
|
} UxnFile;
|
||||||
|
|
||||||
static UxnFile uxn_file[POLYFILEY];
|
static UxnFile uxn_file[POLYFILEY];
|
||||||
|
@ -44,6 +53,7 @@ reset(UxnFile *c)
|
||||||
}
|
}
|
||||||
c->de = NULL;
|
c->de = NULL;
|
||||||
c->state = IDLE;
|
c->state = IDLE;
|
||||||
|
c->outside_sandbox = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Uint16
|
static Uint16
|
||||||
|
@ -84,6 +94,49 @@ file_read_dir(UxnFile *c, char *dest, Uint16 len)
|
||||||
return p - dest;
|
return p - dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
retry_realpath(const char *file_name)
|
||||||
|
{
|
||||||
|
char r[PATH_MAX] = {'\0'}, p[PATH_MAX] = {'\0'}, *x;
|
||||||
|
if(file_name == NULL) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
} else if(strlen(file_name) >= PATH_MAX) {
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if(file_name[0] != '/') {
|
||||||
|
/* TODO: use a macro instead of '/' for absolute path first character so that other systems can work */
|
||||||
|
/* if a relative path, prepend cwd */
|
||||||
|
getcwd(p, sizeof(p));
|
||||||
|
strcat(p, "/"); /* TODO: use a macro instead of '/' for the path delimiter */
|
||||||
|
}
|
||||||
|
strcat(p, file_name);
|
||||||
|
while(realpath(p, r) == NULL) {
|
||||||
|
if(errno != ENOENT)
|
||||||
|
return NULL;
|
||||||
|
x = strrchr(p, '/'); /* TODO: path delimiter macro */
|
||||||
|
if(x)
|
||||||
|
*x = '\0';
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return strdup(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
file_check_sandbox(UxnFile *c)
|
||||||
|
{
|
||||||
|
char *x, *rp, cwd[PATH_MAX] = {'\0'};
|
||||||
|
x = getcwd(cwd, sizeof(cwd));
|
||||||
|
rp = retry_realpath(c->current_filename);
|
||||||
|
if(rp == NULL || (x && strncmp(cwd, rp, strlen(cwd)) != 0)) {
|
||||||
|
c->outside_sandbox = 1;
|
||||||
|
fprintf(stderr, "file warning: blocked attempt to access %s outside of sandbox\n", c->current_filename);
|
||||||
|
}
|
||||||
|
free(rp);
|
||||||
|
}
|
||||||
|
|
||||||
static Uint16
|
static Uint16
|
||||||
file_init(UxnFile *c, char *filename, size_t max_len)
|
file_init(UxnFile *c, char *filename, size_t max_len)
|
||||||
{
|
{
|
||||||
|
@ -92,8 +145,10 @@ file_init(UxnFile *c, char *filename, size_t max_len)
|
||||||
reset(c);
|
reset(c);
|
||||||
if(len > max_len) len = max_len;
|
if(len > max_len) len = max_len;
|
||||||
while(len) {
|
while(len) {
|
||||||
if((*p++ = *filename++) == '\0')
|
if((*p++ = *filename++) == '\0') {
|
||||||
|
file_check_sandbox(c);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
c->current_filename[0] = '\0';
|
c->current_filename[0] = '\0';
|
||||||
|
@ -103,6 +158,7 @@ file_init(UxnFile *c, char *filename, size_t max_len)
|
||||||
static Uint16
|
static Uint16
|
||||||
file_read(UxnFile *c, void *dest, Uint16 len)
|
file_read(UxnFile *c, void *dest, Uint16 len)
|
||||||
{
|
{
|
||||||
|
if(c->outside_sandbox) return 0;
|
||||||
if(c->state != FILE_READ && c->state != DIR_READ) {
|
if(c->state != FILE_READ && c->state != DIR_READ) {
|
||||||
reset(c);
|
reset(c);
|
||||||
if((c->dir = opendir(c->current_filename)) != NULL)
|
if((c->dir = opendir(c->current_filename)) != NULL)
|
||||||
|
@ -121,6 +177,7 @@ static Uint16
|
||||||
file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags)
|
file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags)
|
||||||
{
|
{
|
||||||
Uint16 ret = 0;
|
Uint16 ret = 0;
|
||||||
|
if(c->outside_sandbox) return 0;
|
||||||
if(c->state != FILE_WRITE) {
|
if(c->state != FILE_WRITE) {
|
||||||
reset(c);
|
reset(c);
|
||||||
if((c->f = fopen(c->current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL)
|
if((c->f = fopen(c->current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL)
|
||||||
|
@ -137,6 +194,7 @@ static Uint16
|
||||||
file_stat(UxnFile *c, void *dest, Uint16 len)
|
file_stat(UxnFile *c, void *dest, Uint16 len)
|
||||||
{
|
{
|
||||||
char *basename = strrchr(c->current_filename, '/');
|
char *basename = strrchr(c->current_filename, '/');
|
||||||
|
if(c->outside_sandbox) return 0;
|
||||||
if(basename != NULL)
|
if(basename != NULL)
|
||||||
basename++;
|
basename++;
|
||||||
else
|
else
|
||||||
|
@ -147,7 +205,7 @@ file_stat(UxnFile *c, void *dest, Uint16 len)
|
||||||
static Uint16
|
static Uint16
|
||||||
file_delete(UxnFile *c)
|
file_delete(UxnFile *c)
|
||||||
{
|
{
|
||||||
return unlink(c->current_filename);
|
return c->outside_sandbox ? 0 : unlink(c->current_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IO */
|
/* IO */
|
||||||
|
|
Loading…
Reference in New Issue