diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4a7ec61 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +components = fssb.o \ + arguments.o \ + utils.o \ + proxyfile.o + +all: $(components) + cc -o fssb $(components) -lcrypto + +fssb.o: fssb.c +arguments.o: arguments.c +utils.o: utils.c +proxyfile.o: proxyfile.c + +clean: + rm -rf *.o + rm -rf fssb diff --git a/arguments.c b/arguments.c new file mode 100644 index 0000000..ba7047b --- /dev/null +++ b/arguments.c @@ -0,0 +1,214 @@ +/** + * arguments.h - Argument handling. Part of the FSSB project. + * + * Copyright (C) 2016 Adhityaa Chandrasekar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "arguments.h" + +#define INIT_HELP_ALLOC 8 + +/** + * insert_help - inserts a line of help into the list + * @arg: the argument + * @desc: help text that's supposed to accompany the argument + */ +void insert_help(char *arg, char *desc, int num_vals) { + if(help_list_count >= help_list_allocated) { + help_list_allocated *= 2; + help_list = (help *)realloc(help_list, help_list_allocated); + } + strcpy(help_list[help_list_count].arg, arg); + strcpy(help_list[help_list_count].desc, desc); + help_list[help_list_count].num_vals = num_vals; + help_list_count++; +} + +/** + * build_help - builds the list of arguments and descriptions. + */ +void build_help() { + help_list_count = 0; + + help_list = (help *)malloc(sizeof(help)*INIT_HELP_ALLOC); + help_list_allocated = INIT_HELP_ALLOC; + + insert_help("-h", "show this help and exit", 0); + insert_help("-r", "remove all temporary files at the end", 0); + insert_help("-o", "logging output file", 1); + insert_help("-m", "print file to proxyfile map at the end", 0); +} + +/** + * check_args_validity - ensure all args are recognized + * @argc: number of args given to the tracer + * @argv: argument list + * + * Exits with 1 if there is/are unrecognized argument(s). + */ +void check_args_validity(int argc, char **argv) +{ + int i, j; + + for(i = 1; i < argc; i++) { + /* it's the child's arguments from here on */ + if(strcmp(argv[i], "--") == 0) + break; + + int recognized = 0; + for(j = 0; j < help_list_count; j++) { + if(strcmp(argv[i], help_list[j].arg) == 0) { + recognized = 1; + i += help_list[j].num_vals; + } + } + if(!recognized) { + fprintf(stderr, "fssb: error: invalid option '%s'\n\n", argv[i]); + print_help(); + exit(1); + } + } +} + +/** + * help_requested - determine if the help text is requested by the user + * @argc: number of args given to the tracer + * @argv: argument list + * + * Returns 1 if help is requested, 0 otherwise. + */ +int help_requested(int argc, char **argv) +{ + int i, other_args = 0, show_help = 0; + + for(i = 1; i < argc; i++) { + if(strcmp(argv[i], "-h") == 0) + show_help = 1; + else + other_args = 1; + } + + if(!show_help) + return 0; + + if(other_args) + fprintf(stderr, "`-h` must be the only argument if it is used.\n\n"); + + return 1; +} + +/** + * print_help - print the help manual + */ +void print_help() { + fprintf(stdout, "Usage: fssb [OPTIONS] -- COMMAND\n"); + fprintf(stdout, "\n\ +FSSB is a filesystem sandbox for Linux. It's useful if you want to run a\n\ +program but also protect your files and directories from modification.\n\n"); + + int i; + for(i = 0; i < help_list_count; i++) { + fprintf(stdout, " %s", help_list[i].arg); + for(int j = 0; j < help_list[i].num_vals; j++) + fprintf(stdout, " ARG"); + for(int j = 0; j < 15 - 4*help_list[i].num_vals; j++) + fprintf(stdout, " "); + fprintf(stdout, "%s\n", help_list[i].desc); + } + + fprintf(stdout, "\n\ +You can find a more complete at https://github.com/adtac/fssb\n"); +} + +/** + * set_parameters - reads the command line arguments and sets the values + * @argc: number of args given to the tracer + * @argv: argument list + * @cleanup: whether to cleanup all temp files at exit + * @log_file: file to log all output to + */ +void set_parameters(int argc, + char **argv, + int *cleanup, + FILE **log_file, + int *print_map) +{ + /* default values */ + *cleanup = 0; + *log_file = stdout; + *print_map = 0; + + int i; + for(i = 0; i < argc; i++) { + if(strcmp(argv[i], "-r") == 0) + *cleanup = 1; + + if(strcmp(argv[i], "-m") == 0) + *print_map = 1; + + if(strcmp(argv[i], "-o") == 0) { + struct stat sb; + if(i == argc - 1) { + fprintf(stderr, "fssb: error: no logging file specified\n"); + exit(1); + } + *log_file = fopen(argv[i + 1], "w"); + if(*log_file == NULL) { + fprintf(stderr, "fssb: error: cannot create log file %s\n", + argv[i + 1]); + exit(1); + } + i++; + } + } +} + +/** + * get_child_args_start_pos - get the position where the child args start + * @argc: number of args given to the tracer + * @argv: argument list + * + * Returns the point in the array where the child arguments begin. + */ +int get_child_args_start_pos(int argc, char **argv) +{ + int pos = 0; + + while(pos < argc) { + if(strcmp(argv[pos], "--") == 0) + break; + pos++; + } + + if(pos == argc) { + fprintf(stderr, "fssb: error: no `--` found in arguments\n"); + fprintf(stderr, "usage: fssb -- \n"); + exit(1); + } + + if(pos == argc - 1) { + fprintf(stderr, "fssb: error: nothing found after `--`\n"); + fprintf(stderr, "usage: fssb -- \n"); + exit(1); + } + + return pos + 1; +} diff --git a/arguments.h b/arguments.h new file mode 100644 index 0000000..bf7e827 --- /dev/null +++ b/arguments.h @@ -0,0 +1,47 @@ +/** + * arguments.h - Argument handling. Part of the FSSB project. + * + * Copyright (C) 2016 Adhityaa Chandrasekar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _ARGUMENT_H +#define _ARGUMENT_H + +typedef struct { + char arg[3], desc[128]; + int num_vals; +} help; + +extern void build_help(); + +extern void check_args_validity(int argc, char **argv); + +extern int help_requested(int argc, char **argv); + +extern void print_help(); + +extern void set_parameters(int argc, + char **argv, + int *cleanup, + FILE **log_file, + int *print_map); + +extern int get_child_args_start_pos(int argc, char **argv); + +help *help_list; +int help_list_count, help_list_allocated; + +#endif /* _ARGUMENT_H */ diff --git a/fssb.c b/fssb.c new file mode 100644 index 0000000..71f9c7e --- /dev/null +++ b/fssb.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syscalls.h" +#include "proxyfile.h" +#include "arguments.h" +#include "utils.h" + +/* Hopefully we don't need a 90-digit number. */ +char SANDBOX_DIR[100]; +int PROXY_FILE_LEN; + +proxyfile_list *list; + +FILE *log_file; + +int cleanup, print_list; + +int finish_and_return(int child, int syscall, int *retval) { + if(syscall == -1) { + char buf[2]; + fgets(buf, sizeof(buf), stdin); + } + + if(syscall_breakpoint(child) != 0) + return 0; + + *retval = get_reg(child, eax); + return 1; +} + +int handle_syscalls(pid_t child) { + if(syscall_breakpoint(child) != 0) + return 0; + + int syscall; + syscall = get_reg(child, orig_eax); + + if(syscall == SC_EXIT || syscall == SC_EXIT_GROUP) { + int exit_code = get_syscall_arg(child, 0); + fprintf(stderr, "fssb: child exited with %d\n", exit_code); + fprintf(stderr, "fssb: sandbox directory: %s\n", SANDBOX_DIR); + } + + if(syscall == SC_OPEN) { + /* get open(...) args */ + long word = get_syscall_arg(child, 0); + char *file = get_string(child, word); + int flags = get_syscall_arg(child, 1); + + /* log message */ + fprintf(log_file, "open(\"%s\", %d)\n", file, flags); + + /* name switch */ + char *new_name; + char *original_bytes; + int overwritten_size; + int switch_name = 0; + proxyfile *cur = NULL; + + if(flags & O_APPEND || flags & O_CREAT || flags & O_WRONLY) { + fprintf(log_file, "opening with write access\n"); + + /* if the file already exists, use it */ + cur = search_proxyfile(list, file); + if(cur == NULL) + cur = new_proxyfile(list, file); + + new_name = cur->proxy_path; + switch_name = 1; + } + if(flags == O_RDONLY) { + fprintf(log_file, "opening with read access\n"); + + proxyfile *cur = search_proxyfile(list, file); + if(cur != NULL) { + new_name = cur->proxy_path; + switch_name = 1; + } + } + + if(switch_name) { + original_bytes = write_string(child, + word, + new_name, + &overwritten_size); + } + + int retval; + if(finish_and_return(child, syscall, &retval) == 0) + return 0; + + if(switch_name) { + /* restore the memory */ + write_bytes(child, word, original_bytes, overwritten_size); + } + + /* + if(retval > fdlist_size) { + fdlist_size *= 2; + fdlist = realloc(fdlist, sizeof(struct proxy_file)*fdlist_size); + continue; + } + */ + + if(cur == NULL || cur->file_path != file) + free(file); + } + + return 1; +} + +void trace(pid_t child) { + int status; + waitpid(child, &status, 0); + + assert(WIFSTOPPED(status)); + ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD); + + while(handle_syscalls(child)); +} + +int process_child(int argc, char **argv) { + int i; + char *args[argc+1]; + for(i=0; i < argc; i++) + args[i] = argv[i]; + args[argc] = NULL; /* execvp needs NULL terminated list */ + + ptrace(PTRACE_TRACEME); + kill(getpid(), SIGSTOP); + return execvp(args[0], args); +} + +void init() { + struct stat sb; + + int i; + for(i = 1; ; i++) { + sprintf(SANDBOX_DIR, "/tmp/fssb.%d/", i); + if(stat(SANDBOX_DIR, &sb)) + break; + } + mkdir(SANDBOX_DIR, 0775); + + PROXY_FILE_LEN = strlen(SANDBOX_DIR) + 32; + + fprintf(stderr, "fssb: sandbox directory: %s\n", SANDBOX_DIR); + + list = new_proxyfile_list(); + list->SANDBOX_DIR = SANDBOX_DIR; + list->PROXY_FILE_LEN = PROXY_FILE_LEN; +} + +int main(int argc, char **argv) { + build_help(); + check_args_validity(argc, argv); + if(help_requested(argc, argv)) { + print_help(); + return 0; + } + + /* everything else must run a program */ + int pos = get_child_args_start_pos(argc, argv); + int child_argc = argc - pos; + char **child_argv = argv + pos; + + set_parameters(pos - 1, argv, &cleanup, &log_file, &print_list); + + init(); + + pid_t child = fork(); + if(child > 0) + trace(child); + else if(child == 0) + process_child(child_argc, child_argv); + else { + fprintf(stderr, "fssb: error: cannot fork\n"); + return 1; + } + + if(cleanup) + rmdir(SANDBOX_DIR); + else if(print_map) + print_map(list, log_file); + + return 0; +} diff --git a/proxyfile.c b/proxyfile.c new file mode 100644 index 0000000..5413b1d --- /dev/null +++ b/proxyfile.c @@ -0,0 +1,86 @@ +/** + * proxyfile.h - Proxy file operations. Part of the FSSB project. + * + * Copyright (C) 2016 Adhityaa Chandrasekar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "proxyfile.h" +#include "utils.h" + +#define PROXYFILE_LIST_ALLOC 128 + +proxyfile_list *new_proxyfile_list() +{ + proxyfile_list *retval = (proxyfile_list *)malloc(sizeof(proxyfile_list)); + retval->list = (proxyfile *)malloc(PROXYFILE_LIST_ALLOC*sizeof(proxyfile)); + retval->alloc = PROXYFILE_LIST_ALLOC; + retval->used = 0; + + return retval; +} + +proxyfile *new_proxyfile(proxyfile_list *list, char *file_path) +{ + if(list->used + 1 > list->alloc) { + list->alloc *= 2; + list->list = (proxyfile *)realloc(list->list, + list->alloc*sizeof(proxyfile)); + } + + proxyfile *cur = list->list + list->used; + + cur->file_path = file_path; /* no need to copy char-by-char */ + + cur->md5 = md5sum(file_path); /* just assign the already assigned addr */ + + cur->proxy_path = (char *)malloc((list->PROXY_FILE_LEN + 1)*sizeof(char)); + strcpy(cur->proxy_path, list->SANDBOX_DIR); + strcat(cur->proxy_path, cur->md5); + + list->used++; + + return cur; +} + +proxyfile *search_proxyfile(proxyfile_list *list, char *file_path) { + char *md5 = md5sum(file_path); + + int i; + for(i = 0; i < list->used; i++) { + if(strcmp((list->list + i)->md5, md5) == 0) + return list->list + i; + } + + return NULL; +} + +void print_map(proxyfile_list *list, FILE *log_file) { + fprintf(log_file, "\n"); + fprintf(log_file, "==============\n"); + fprintf(log_file, " File mapping \n"); + fprintf(log_file, "==============\n"); + fprintf(log_file, "\n"); + + int i; + for(i = 0; i < list->used; i++) { + proxyfile *cur = list->list + i; + fprintf(log_file, "%s = %s\n", cur->md5, cur->file_path); + } +} diff --git a/proxyfile.h b/proxyfile.h new file mode 100644 index 0000000..afa1e67 --- /dev/null +++ b/proxyfile.h @@ -0,0 +1,45 @@ +/** + * proxyfile.h - Proxy file operations. Part of the FSSB project. + * + * Copyright (C) 2016 Adhityaa Chandrasekar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _PROXYFILE_H +#define _PROXYFILE_H + +#include + +typedef struct { + char *file_path, *md5, *proxy_path; + int fd; +} proxyfile; + +typedef struct { + proxyfile *list; + int alloc, used; + int PROXY_FILE_LEN; + char *SANDBOX_DIR; +} proxyfile_list; + +extern proxyfile_list *new_proxyfile_list(); + +extern proxyfile *new_proxyfile(proxyfile_list *list, char *file_path); + +extern proxyfile *search_proxyfile(proxyfile_list *list, char *file_path); + +extern void print_map(proxyfile_list *list, FILE *log_file); + +#endif /* _PROXYFILE_H */ diff --git a/syscalls.h b/syscalls.h new file mode 100644 index 0000000..296e920 --- /dev/null +++ b/syscalls.h @@ -0,0 +1,6 @@ +#define SC_READ 0 +#define SC_WRITE 1 +#define SC_OPEN 2 +#define SC_CLOSE 3 +#define SC_EXIT 60 +#define SC_EXIT_GROUP 231 diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..6e6950a --- /dev/null +++ b/utils.c @@ -0,0 +1,215 @@ +/** + * utils.c - Utility functions. Part of the FSSB project. + * + * Copyright (C) 2016 Adhityaa Chandrasekar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +/** + * syscall_breakpoint - break at the entry or exit of a syscall + * @child: PID of the child process + * + * This will stop the process until called again. + * + * Return 0 if the child has been stopped, 1 if it has exited. + */ +int syscall_breakpoint(pid_t child) +{ + int status; + + while(1) { + ptrace(PTRACE_SYSCALL, child, 0, 0); + waitpid(child, &status, 0); + + if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) + return 0; + if (WIFEXITED(status)) + return 1; + } +} + +/** + * get_syscall_arg - get the nth argument of the syscall. + * @child: PID of the child process + * @n: which argument (max 6 args for any syscall) + * + * Returns the long corresponding the argument. If this is a string, a pointer + * to it is returned. If n is greater than 6, -1L is returned. + */ +long get_syscall_arg(pid_t child, int n) +{ + switch(n) { +#ifdef __amd64__ + /* x86_64 has {rdi, rsi, rdx, r10, r8, r9} */ + case 0: return get_reg(child, rdi); + case 1: return get_reg(child, rsi); + case 2: return get_reg(child, rdx); + case 3: return get_reg(child, r10); + case 4: return get_reg(child, r8); + case 5: return get_reg(child, r9); +#else + /* x86 has {ebx, ecx, edx, esi, edi, ebp} */ + case 0: return get_reg(child, ebx); + case 1: return get_reg(child, ecx); + case 2: return get_reg(child, edx); + case 3: return get_reg(child, esi); + case 4: return get_reg(child, edi); + case 5: return get_reg(child, ebp); +#endif + default: return -1L; + } +} + +/** + * get_string - returns the string at the given address of the child process + * @child: PID of the child process + * @addr: memory address location + * + * Note: the string has to be null-terminated. + * + * Returns a (char *) pointer. + */ +char *get_string(pid_t child, unsigned long addr) +{ + char *str = (char *)malloc(1024); + int alloc = 1024, copied = 0; + unsigned long word; + + while(1) { + if(copied + sizeof(word) > alloc) { /* too big (that's what she said) */ + alloc *= 2; + str = (char *)realloc(str, alloc); + } + + word = ptrace(PTRACE_PEEKDATA, child, addr + copied); + if(errno) { + str[copied] = 0; + break; + } + memcpy(str + copied, &word, sizeof(word)); + + /* If we've already encountered null, break and return */ + if(memchr(&word, 0, sizeof(word)) != NULL) + break; + + copied += sizeof(word); + } + + return str; +} + +/** + * write_string - writes a string to the given address of the child process + * @child: PID of the child process + * @addr: memory address location + * @str: string to be written + * @overwritten_size: stores the number of bytes overwritten + * + * Returns a pointer to a memory address of bytes that needs to be restored. + */ +unsigned char *write_string(pid_t child, + unsigned long addr, + char *str, + int *overwritten_size) +{ + unsigned char *original_bytes = malloc(1024); + int alloc = 1024; + *overwritten_size = 0; + + while(1) { + unsigned long word; + + word = ptrace(PTRACE_PEEKDATA, child, addr + *overwritten_size); + if(*overwritten_size + sizeof(word) > alloc) { + alloc *= 2; + original_bytes = (unsigned char *)realloc(original_bytes, alloc); + } + memcpy(original_bytes + *overwritten_size, &word, sizeof(word)); + + memcpy(&word, str + *overwritten_size, sizeof(word)); + ptrace(PTRACE_POKEDATA, child, addr + *overwritten_size, word); + + *overwritten_size += sizeof(word); + + if(memchr(&word, 0, sizeof(word)) != NULL) + break; + } + + return original_bytes; +} + +/** + * write_bytes - write n bytes to the given memory address + * @child: PID of the child process + * @addr: memory address location + * @bytes: bytes to be written + * @n: stores the number of bytes overwritten + */ +void write_bytes(pid_t child, + unsigned long addr, + unsigned char *bytes, + int n) { + int cur; + for(cur = 0; cur < n; ) { + unsigned long word; + memcpy(&word, bytes + cur, sizeof(word)); + ptrace(PTRACE_POKETEXT, child, addr + cur, word); + cur += sizeof(word); + } +} + +/** + * md5sum - return a MD5 hash of the given string + * @str: the string + * + * Returns a 32-character string containing the MD5 hash. Remember to free + * this after use. + */ +char *md5sum(char *str) +{ + char *retval = (char *)malloc(33); + + unsigned char d[17]; + MD5(str, strlen(str), d); + + for(int i = 0; i < 16; i++) { + unsigned char x = d[i] >> 4; + if(x < 10) + retval[2*i] = '0' + x; + else + retval[2*i] = 'a' + x - 10; + + x = d[i] & 0xf; + if(x < 10) + retval[2*i+1] = '0' + x; + else + retval[2*i+1] = 'a' + x - 10; + } + + retval[32] = 0; + + return retval; +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..7ad079f --- /dev/null +++ b/utils.h @@ -0,0 +1,63 @@ +/** + * utils.c - Defines for various utlities. Part of the FSSB project. + * + * Copyright (C) 2016 Adhityaa Chandrasekar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _UTILS_H +#define _UTILS_H + +#include +#include +#include + +/* A hack for x86_64 systems. */ +#ifdef __amd64__ +#define eax rax +#define orig_eax orig_rax +#endif + +/* Get the offset of `field` in struct `str`. */ +#ifndef offsetof +#define offsetof(str, field) __builtin_offsetof(str, field) +#endif + +/* Return the register `reg` of `child`. */ +#ifndef get_reg +#define get_reg(child, reg) ptrace(PTRACE_PEEKUSER, \ + child, \ + offsetof(struct user, regs.reg)) +#endif + +extern int syscall_breakpoint(pid_t child); + +extern long get_syscall_arg(pid_t child, int n); + +extern char *get_string(pid_t child, unsigned long addr); + +extern unsigned char *write_string(pid_t child, + unsigned long addr, + char *str, + int *overwritten_bytes); + +extern void write_bytes(pid_t child, + unsigned long addr, + unsigned char *bytes, + int n); + +extern char *md5sum(char *str); + +#endif /* _UTILS_H */