From 71c411faf73cf8ee109e761f80dda1bc2572a81c Mon Sep 17 00:00:00 2001 From: Alejandro Liu Date: Wed, 23 Mar 2016 21:33:30 +0100 Subject: [PATCH] First implementation of Copy-On-Write --- src/GNUmakefile | 1 + src/cli/proot.c | 6 ++ src/cli/proot.h | 30 ++++-- src/extension/cow/cow.c | 196 ++++++++++++++++++++++++++++++++++++++ src/extension/extension.h | 2 +- 5 files changed, 226 insertions(+), 9 deletions(-) create mode 100644 src/extension/cow/cow.c diff --git a/src/GNUmakefile b/src/GNUmakefile index af318daf..5bd94d19 100644 --- a/src/GNUmakefile +++ b/src/GNUmakefile @@ -55,6 +55,7 @@ OBJECTS += \ extension/extension.o \ extension/kompat/kompat.o \ extension/fake_id0/fake_id0.o \ + extension/cow/cow.o \ loader/loader-wrapped.o define define_from_arch.h diff --git a/src/cli/proot.c b/src/cli/proot.c index e46d87e5..617033e1 100644 --- a/src/cli/proot.c +++ b/src/cli/proot.c @@ -163,6 +163,12 @@ static int handle_option_k(Tracee *tracee, const Cli *cli UNUSED, const char *va return 0; } +static int handle_option_c(Tracee *tracee, const Cli *cli UNUSED, const char *value) +{ + (void) initialize_extension(tracee, cow_callback, value); + return 0; +} + static int handle_option_i(Tracee *tracee, const Cli *cli UNUSED, const char *value) { (void) initialize_extension(tracee, fake_id0_callback, value); diff --git a/src/cli/proot.h b/src/cli/proot.h index a06abd71..584bb50e 100644 --- a/src/cli/proot.h +++ b/src/cli/proot.h @@ -57,6 +57,7 @@ static int handle_option_V(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_h(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_k(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_0(Tracee *tracee, const Cli *cli, const char *value); +static int handle_option_c(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_i(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_R(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_S(Tracee *tracee, const Cli *cli, const char *value); @@ -205,15 +206,28 @@ Copyright (C) 2014 STMicroelectronics, licensed under GPL v2 or later.", }, { .class = "Extension options", .arguments = { - { .name = "-i", .separator = ' ', .value = "string" }, - { .name = "--change-id", .separator = '=', .value = "string" }, + { .name = "-0", .separator = '\0', .value = NULL }, + { .name = "--root-id", .separator = '\0', .value = NULL }, + { .name = NULL, .separator = '\0', .value = NULL } }, + .handler = handle_option_0, + .description = "Make current user appear as \"root\" and fake its privileges.", + .detail = "\tSome programs will refuse to work if they are not run with \"root\"\n\ +\tprivileges, even if there is no technical reason for that. This\n\ +\tis typically the case with package managers. This option allows\n\ +\tusers to bypass this kind of limitation by faking the user/group\n\ +\tidentity, and by faking the success of some operations like\n\ +\tchanging the ownership of files, changing the root directory to\n\ +\t/, ... Note that this option is quite limited compared to\n\ +\tfakeroot.", + }, + { .class = "Extension options", + .arguments = { + { .name = "-c", .separator = '\0', .value = NULL }, + { .name = "--cow", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, - .handler = handle_option_i, - .description = "Make current user and group appear as *string* \"uid:gid\".", - .detail = "\tThis option makes the current user and group appear as uid and\n\ -\tgid. Likewise, files actually owned by the current user and\n\ -\tgroup appear as if they were owned by uid and gid instead.\n\ -\tNote that the -0 option is the same as -i 0:0.", + .handler = handle_option_c, + .description = "Implement Copy-on-Write semantics", + .detail = "\tThis option implments Copy-on-Write (COW) semantics", }, { .class = "Alias options", .arguments = { diff --git a/src/extension/cow/cow.c b/src/extension/cow/cow.c new file mode 100644 index 00000000..d3de8b5b --- /dev/null +++ b/src/extension/cow/cow.c @@ -0,0 +1,196 @@ +/* + * This file is part of PRoot. + * + * Copyright (C) 2016 Alejandro Liu + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA. + */ + +#include /* intptr_t, */ +#include /* strtoul(3), */ +#include /* KERNEL_VERSION, */ +#include /* assert(3), */ +#include /* uname(2), utsname, */ +#include /* str*(3), memcpy(3), */ +#include /* talloc_*, */ +#include /* AT_*, */ +#include /* linux.git:c0a3a20b */ +#include /* errno, */ +#include /* AT_, */ +#include /* FUTEX_PRIVATE_FLAG */ +#include /* MIN, */ +#include /* mmap, munmap */ +#include /* unlink, close, write, fchown */ + +#include "extension/extension.h" +#include "syscall/seccomp.h" +#include "syscall/sysnum.h" +#include "syscall/syscall.h" +#include "syscall/chain.h" +#include "tracee/tracee.h" +#include "tracee/reg.h" +#include "tracee/abi.h" +#include "tracee/mem.h" +#include "execve/auxv.h" +#include "cli/note.h" +#include "arch.h" + +typedef int Config; + +/* List of syscalls handled by this extensions. */ +static FilteredSysnum filtered_sysnums[] = { + { PR_open, FILTER_SYSEXIT }, + { PR_openat, FILTER_SYSEXIT }, + FILTERED_SYSNUM_END, +}; + + +static int check_flags(Tracee *tracee, Reg reg) { + int flags; + flags = peek_reg(tracee,CURRENT, reg); + switch (flags & O_ACCMODE) { + case O_WRONLY: + case O_RDWR: + return 1; + } + return 0; +} + +static void check_inode_and_copy(Tracee *tracee,char const *name) { + struct stat stb; + int sfd, nfd; + if (stat(name,&stb) == -1) return; + if (stb.st_nlink < 2) return; + if (-1 == (sfd = open(name,O_RDONLY))) return; + if (unlink(name) == -1) { + close(sfd); + return; + } + if (-1 == (nfd = open(name,O_CREAT|O_EXCL|O_WRONLY,stb.st_mode))) { + note(tracee, ERROR, USER, "Error opening %s",name); + close(sfd); + return; + } + void *addr; + if ((addr=mmap(NULL,stb.st_size,PROT_READ,MAP_PRIVATE, sfd,0))==MAP_FAILED) { + note(tracee, ERROR, USER, "Error mmaping %s",name); + close(nfd); + close(sfd); + return; + } + if (write(nfd,addr,stb.st_size) != stb.st_size) { + note(tracee, ERROR, USER, "Error writing %s",name); + } + munmap(addr,stb.st_size); + close(sfd); + fchown(nfd,stb.st_uid,stb.st_gid); + close(nfd); +} + +/** + * Adjust current @tracee's syscall parameters according to @config. + * This function always returns 0. + */ +static int handle_sysenter_end(Tracee *tracee, const Config *config UNUSED) +{ + word_t sysnum; + char old_path[PATH_MAX]; + int status; + + sysnum = get_sysnum(tracee, ORIGINAL); + switch (sysnum) { + case PR_open: + if (!check_flags(tracee, SYSARG_2)) return 0; + /* Extract the currrent path. */ + status = get_sysarg_path(tracee, old_path, SYSARG_1); + if (status < 0) return status; + //note(tracee, INFO, USER, "PR_open(%s)",old_path); + check_inode_and_copy(tracee,old_path); + return 0; + case PR_openat: + if (!check_flags(tracee, SYSARG_3)) return 0; + /* Extract the currrent path. */ + status = get_sysarg_path(tracee, old_path, SYSARG_2); + if (status < 0) return status; + //note(tracee, INFO, USER, "PR_openat(%s)",old_path); + check_inode_and_copy(tracee,old_path); + return 0; + default: + return 0; + } + + /* Never reached */ + assert(0); + return 0; + +} + +/** + * Handler for this @extension. It is triggered each time an @event + * occurred. See ExtensionEvent for the meaning of @data1 and @data2. + */ +int cow_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2 UNUSED) +{ + switch (event) { + case INITIALIZATION: { + Config *config; + extension->config = talloc(extension, Config); + if (extension->config == NULL) + return -1; + + config = talloc_get_type_abort(extension->config, Config); + *config = 0; + extension->filtered_sysnums = filtered_sysnums; + return 0; + } + + case INHERIT_PARENT: /* Inheritable for sub reconfiguration ... */ + return 1; + + case INHERIT_CHILD: { + /* Copy the parent configuration to the child. The + * structure should not be shared as uid/gid changes + * in one process should not affect other processes. + * This assertion is not true for POSIX threads + * sharing the same group, however Linux threads never + * share uid/gid information. As a consequence, the + * GlibC emulates the POSIX behavior on Linux by + * sending a signal to all group threads to cause them + * to invoke the system call too. Finally, PRoot + * doesn't have to worry about clone flags. + */ + + Extension *parent = (Extension *) data1; + extension->config = talloc_zero(extension, Config); + if (extension->config == NULL) + return -1; + + memcpy(extension->config, parent->config, sizeof(Config)); + return 0; + } + + case SYSCALL_ENTER_END: { + Tracee *tracee = TRACEE(extension); + Config *config = talloc_get_type_abort(extension->config, Config); + + return handle_sysenter_end(tracee, config); + } + + + default: + return 0; + } +} diff --git a/src/extension/extension.h b/src/extension/extension.h index 3edafa22..2a4e81a8 100644 --- a/src/extension/extension.h +++ b/src/extension/extension.h @@ -179,5 +179,5 @@ static inline int notify_extensions(Tracee *tracee, ExtensionEvent event, extern int kompat_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); extern int fake_id0_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); extern int care_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); - +extern int cow_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); #endif /* EXTENSION_H */