| /* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- |
| * |
| * This file is part of PRoot. |
| * |
| * Copyright (C) 2014 STMicroelectronics |
| * |
| * 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 <assert.h> /* assert(3), */ |
| #include <limits.h> /* PATH_MAX, */ |
| #include <string.h> /* strlen(3), */ |
| #include <errno.h> /* errno(3), E* */ |
| |
| #include "syscall/syscall.h" |
| #include "syscall/chain.h" |
| #include "extension/extension.h" |
| #include "tracee/tracee.h" |
| #include "tracee/reg.h" |
| #include "tracee/mem.h" |
| |
| /** |
| * Copy in @path a C string (PATH_MAX bytes max.) from the @tracee's |
| * memory address space pointed to by the @reg argument of the |
| * current syscall. This function returns -errno if an error occured, |
| * otherwise it returns the size in bytes put into the @path. |
| */ |
| int get_sysarg_path(const Tracee *tracee, char path[PATH_MAX], Reg reg) |
| { |
| int size; |
| word_t src; |
| |
| src = peek_reg(tracee, CURRENT, reg); |
| |
| /* Check if the parameter is not NULL. Technically we should |
| * not return an -EFAULT for this special value since it is |
| * allowed for some syscall, utimensat(2) for instance. */ |
| if (src == 0) { |
| path[0] = '\0'; |
| return 0; |
| } |
| |
| /* Get the path from the tracee's memory space. */ |
| size = read_path(tracee, path, src); |
| if (size < 0) |
| return size; |
| |
| path[size] = '\0'; |
| return size; |
| } |
| |
| /** |
| * Copy @size bytes of the data pointed to by @tracer_ptr into a |
| * @tracee's memory block and make the @reg argument of the current |
| * syscall points to this new block. This function returns -errno if |
| * an error occured, otherwise 0. |
| */ |
| static int set_sysarg_data(Tracee *tracee, const void *tracer_ptr, word_t size, Reg reg) |
| { |
| word_t tracee_ptr; |
| int status; |
| |
| /* Allocate space into the tracee's memory to host the new data. */ |
| tracee_ptr = alloc_mem(tracee, size); |
| if (tracee_ptr == 0) |
| return -EFAULT; |
| |
| /* Copy the new data into the previously allocated space. */ |
| status = write_data(tracee, tracee_ptr, tracer_ptr, size); |
| if (status < 0) |
| return status; |
| |
| /* Make this argument point to the new data. */ |
| poke_reg(tracee, reg, tracee_ptr); |
| |
| return 0; |
| } |
| |
| /** |
| * Copy @path to a @tracee's memory block and make the @reg argument |
| * of the current syscall points to this new block. This function |
| * returns -errno if an error occured, otherwise 0. |
| */ |
| int set_sysarg_path(Tracee *tracee, const char path[PATH_MAX], Reg reg) |
| { |
| return set_sysarg_data(tracee, path, strlen(path) + 1, reg); |
| } |
| |
| void translate_syscall(Tracee *tracee) |
| { |
| const bool is_enter_stage = IS_IN_SYSENTER(tracee); |
| int status; |
| |
| assert(tracee->exe != NULL); |
| |
| status = fetch_regs(tracee); |
| if (status < 0) |
| return; |
| |
| if (is_enter_stage) { |
| /* Never restore original register values at the end |
| * of this stage. */ |
| tracee->restore_original_regs = false; |
| |
| print_current_regs(tracee, 3, "sysenter start"); |
| |
| /* Translate the syscall only if it was actually |
| * requested by the tracee, it is not a syscall |
| * chained by PRoot. */ |
| if (tracee->chain.syscalls == NULL) { |
| save_current_regs(tracee, ORIGINAL); |
| status = translate_syscall_enter(tracee); |
| save_current_regs(tracee, MODIFIED); |
| } |
| else { |
| status = notify_extensions(tracee, SYSCALL_CHAINED_ENTER, 0, 0); |
| tracee->restart_how = PTRACE_SYSCALL; |
| } |
| |
| /* Remember the tracee status for the "exit" stage and |
| * avoid the actual syscall if an error was reported |
| * by the translation/extension. */ |
| if (status < 0) { |
| set_sysnum(tracee, PR_void); |
| poke_reg(tracee, SYSARG_RESULT, (word_t) status); |
| tracee->status = status; |
| } |
| else |
| tracee->status = 1; |
| |
| /* Restore tracee's stack pointer now if it won't hit |
| * the sysexit stage (i.e. when seccomp is enabled and |
| * there's nothing else to do). */ |
| if (tracee->restart_how == PTRACE_CONT) { |
| tracee->status = 0; |
| poke_reg(tracee, STACK_POINTER, peek_reg(tracee, ORIGINAL, STACK_POINTER)); |
| } |
| } |
| else { |
| /* By default, restore original register values at the |
| * end of this stage. */ |
| tracee->restore_original_regs = true; |
| |
| print_current_regs(tracee, 5, "sysexit start"); |
| |
| /* Translate the syscall only if it was actually |
| * requested by the tracee, it is not a syscall |
| * chained by PRoot. */ |
| if (tracee->chain.syscalls == NULL) |
| translate_syscall_exit(tracee); |
| else |
| (void) notify_extensions(tracee, SYSCALL_CHAINED_EXIT, 0, 0); |
| |
| /* Reset the tracee's status. */ |
| tracee->status = 0; |
| |
| /* Insert the next chained syscall, if any. */ |
| if (tracee->chain.syscalls != NULL) |
| chain_next_syscall(tracee); |
| } |
| |
| (void) push_regs(tracee); |
| |
| if (is_enter_stage) |
| print_current_regs(tracee, 5, "sysenter end" ); |
| else |
| print_current_regs(tracee, 4, "sysexit end"); |
| } |