blob: 81937bdd97f18fa19118b66e9205cb4b6010f108 [file] [edit]
/* -*- 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 <errno.h> /* errno(3), E* */
#include <sys/utsname.h> /* struct utsname, */
#include <linux/net.h> /* SYS_*, */
#include <string.h> /* strlen(3), */
#include "syscall/syscall.h"
#include "syscall/sysnum.h"
#include "syscall/socket.h"
#include "syscall/chain.h"
#include "syscall/heap.h"
#include "execve/execve.h"
#include "tracee/tracee.h"
#include "tracee/reg.h"
#include "tracee/mem.h"
#include "tracee/abi.h"
#include "path/path.h"
#include "ptrace/ptrace.h"
#include "ptrace/wait.h"
#include "extension/extension.h"
#include "arch.h"
/**
* Translate the output arguments of the current @tracee's syscall in
* the @tracee->pid process area. This function sets the result of
* this syscall to @tracee->status if an error occured previously
* during the translation, that is, if @tracee->status is less than 0.
*/
void translate_syscall_exit(Tracee *tracee)
{
word_t syscall_number;
word_t syscall_result;
int status;
status = notify_extensions(tracee, SYSCALL_EXIT_START, 0, 0);
if (status < 0) {
poke_reg(tracee, SYSARG_RESULT, (word_t) status);
goto end;
}
if (status > 0)
return;
/* Set the tracee's errno if an error occured previously during
* the translation. */
if (tracee->status < 0) {
poke_reg(tracee, SYSARG_RESULT, (word_t) tracee->status);
goto end;
}
/* Translate output arguments:
* - break: update the syscall result register with "status"
* - goto end: nothing else to do.
*/
syscall_number = get_sysnum(tracee, ORIGINAL);
syscall_result = peek_reg(tracee, CURRENT, SYSARG_RESULT);
switch (syscall_number) {
case PR_brk:
translate_brk_exit(tracee);
goto end;
case PR_getcwd: {
char path[PATH_MAX];
size_t new_size;
size_t size;
word_t output;
size = (size_t) peek_reg(tracee, ORIGINAL, SYSARG_2);
if (size == 0) {
status = -EINVAL;
break;
}
/* Ensure cwd still exists. */
status = translate_path(tracee, path, AT_FDCWD, ".", false);
if (status < 0)
break;
new_size = strlen(tracee->fs->cwd) + 1;
if (size < new_size) {
status = -ERANGE;
break;
}
/* Overwrite the path. */
output = peek_reg(tracee, ORIGINAL, SYSARG_1);
status = write_data(tracee, output, tracee->fs->cwd, new_size);
if (status < 0)
break;
/* The value of "status" is used to update the returned value
* in translate_syscall_exit(). */
status = new_size;
break;
}
case PR_accept:
case PR_accept4:
/* Nothing special to do if no sockaddr was specified. */
if (peek_reg(tracee, ORIGINAL, SYSARG_2) == 0)
goto end;
/* Fall through. */
case PR_getsockname:
case PR_getpeername: {
word_t sock_addr;
word_t size_addr;
word_t max_size;
/* Error reported by the kernel. */
if ((int) syscall_result < 0)
goto end;
sock_addr = peek_reg(tracee, ORIGINAL, SYSARG_2);
size_addr = peek_reg(tracee, MODIFIED, SYSARG_3);
max_size = peek_reg(tracee, MODIFIED, SYSARG_6);
status = translate_socketcall_exit(tracee, sock_addr, size_addr, max_size);
if (status < 0)
break;
/* Don't overwrite the syscall result. */
goto end;
}
#define SYSARG_ADDR(n) (args_addr + ((n) - 1) * sizeof_word(tracee))
#define POKE_WORD(addr, value) \
poke_word(tracee, addr, value); \
if (errno != 0) { \
status = -errno; \
break; \
}
#define PEEK_WORD(addr) \
peek_word(tracee, addr); \
if (errno != 0) { \
status = -errno; \
break; \
}
case PR_socketcall: {
word_t args_addr;
word_t sock_addr;
word_t size_addr;
word_t max_size;
args_addr = peek_reg(tracee, ORIGINAL, SYSARG_2);
switch (peek_reg(tracee, ORIGINAL, SYSARG_1)) {
case SYS_ACCEPT:
case SYS_ACCEPT4:
/* Nothing special to do if no sockaddr was specified. */
sock_addr = PEEK_WORD(SYSARG_ADDR(2));
if (sock_addr == 0)
goto end;
/* Fall through. */
case SYS_GETSOCKNAME:
case SYS_GETPEERNAME:
/* Handle these cases below. */
status = 1;
break;
case SYS_BIND:
case SYS_CONNECT:
/* Restore the initial parameters: this memory was
* overwritten at the enter stage. Remember: POKE_WORD
* puts -errno in status and breaks if an error
* occured. */
POKE_WORD(SYSARG_ADDR(2), peek_reg(tracee, MODIFIED, SYSARG_5));
POKE_WORD(SYSARG_ADDR(3), peek_reg(tracee, MODIFIED, SYSARG_6));
status = 0;
break;
default:
status = 0;
break;
}
/* Error reported by the kernel or there's nothing else to do. */
if ((int) syscall_result < 0 || status == 0)
goto end;
/* An error occured in SYS_BIND or SYS_CONNECT. */
if (status < 0)
break;
/* Remember: PEEK_WORD puts -errno in status and breaks if an
* error occured. */
sock_addr = PEEK_WORD(SYSARG_ADDR(2));
size_addr = PEEK_WORD(SYSARG_ADDR(3));
max_size = peek_reg(tracee, MODIFIED, SYSARG_6);
status = translate_socketcall_exit(tracee, sock_addr, size_addr, max_size);
if (status < 0)
break;
/* Don't overwrite the syscall result. */
goto end;
}
#undef SYSARG_ADDR
#undef PEEK_WORD
#undef POKE_WORD
case PR_fchdir:
case PR_chdir:
/* These syscalls are fully emulated, see enter.c for details
* (like errors). */
status = 0;
break;
case PR_rename:
case PR_renameat:
case PR_renameat2: {
char old_path[PATH_MAX];
char new_path[PATH_MAX];
ssize_t old_length;
ssize_t new_length;
Comparison comparison;
Reg old_reg;
Reg new_reg;
char *tmp;
/* Error reported by the kernel. */
if ((int) syscall_result < 0)
goto end;
if (syscall_number == PR_rename) {
old_reg = SYSARG_1;
new_reg = SYSARG_2;
}
else {
old_reg = SYSARG_2;
new_reg = SYSARG_4;
}
/* Get the old path, then convert it to the same
* "point-of-view" as tracee->fs->cwd (guest). */
status = read_path(tracee, old_path, peek_reg(tracee, MODIFIED, old_reg));
if (status < 0)
break;
status = detranslate_path(tracee, old_path, NULL);
if (status < 0)
break;
old_length = (status > 0 ? status - 1 : (ssize_t) strlen(old_path));
/* Nothing special to do if the moved path is not the
* current working directory. */
comparison = compare_paths(old_path, tracee->fs->cwd);
if (comparison != PATH1_IS_PREFIX && comparison != PATHS_ARE_EQUAL) {
status = 0;
break;
}
/* Get the new path, then convert it to the same
* "point-of-view" as tracee->fs->cwd (guest). */
status = read_path(tracee, new_path, peek_reg(tracee, MODIFIED, new_reg));
if (status < 0)
break;
status = detranslate_path(tracee, new_path, NULL);
if (status < 0)
break;
new_length = (status > 0 ? status - 1 : (ssize_t) strlen(new_path));
/* Sanity check. */
if (strlen(tracee->fs->cwd) >= PATH_MAX) {
status = 0;
break;
}
strcpy(old_path, tracee->fs->cwd);
/* Update the virtual current working directory. */
substitute_path_prefix(old_path, old_length, new_path, new_length);
tmp = talloc_strdup(tracee->fs, old_path);
if (tmp == NULL) {
status = -ENOMEM;
break;
}
TALLOC_FREE(tracee->fs->cwd);
tracee->fs->cwd = tmp;
status = 0;
break;
}
case PR_readlink:
case PR_readlinkat: {
char referee[PATH_MAX];
char referer[PATH_MAX];
size_t old_size;
size_t new_size;
size_t max_size;
word_t input;
word_t output;
/* Error reported by the kernel. */
if ((int) syscall_result < 0)
goto end;
old_size = syscall_result;
if (syscall_number == PR_readlink) {
output = peek_reg(tracee, ORIGINAL, SYSARG_2);
max_size = peek_reg(tracee, ORIGINAL, SYSARG_3);
input = peek_reg(tracee, MODIFIED, SYSARG_1);
}
else {
output = peek_reg(tracee, ORIGINAL, SYSARG_3);
max_size = peek_reg(tracee, ORIGINAL, SYSARG_4);
input = peek_reg(tracee, MODIFIED, SYSARG_2);
}
if (max_size > PATH_MAX)
max_size = PATH_MAX;
if (max_size == 0) {
status = -EINVAL;
break;
}
/* The kernel does NOT put the NULL terminating byte for
* readlink(2). */
status = read_data(tracee, referee, output, old_size);
if (status < 0)
break;
referee[old_size] = '\0';
/* Not optimal but safe (path is fully translated). */
status = read_path(tracee, referer, input);
if (status < 0)
break;
if (status >= PATH_MAX) {
status = -ENAMETOOLONG;
break;
}
status = detranslate_path(tracee, referee, referer);
if (status < 0)
break;
/* The original path doesn't require any transformation, i.e
* it is a symetric binding. */
if (status == 0)
goto end;
/* Overwrite the path. Note: the output buffer might be
* initialized with zeros but it was updated with the kernel
* result, and then with the detranslated result. This later
* might be shorter than the former, so it's safier to add a
* NULL terminating byte when possible. This problem was
* exposed by IDA Demo 6.3. */
if ((size_t) status < max_size) {
new_size = status - 1;
status = write_data(tracee, output, referee, status);
}
else {
new_size = max_size;
status = write_data(tracee, output, referee, max_size);
}
if (status < 0)
break;
/* The value of "status" is used to update the returned value
* in translate_syscall_exit(). */
status = new_size;
break;
}
#if defined(ARCH_X86_64)
case PR_uname: {
struct utsname utsname;
word_t address;
size_t size;
if (get_abi(tracee) != ABI_2)
goto end;
/* Error reported by the kernel. */
if ((int) syscall_result < 0)
goto end;
address = peek_reg(tracee, ORIGINAL, SYSARG_1);
status = read_data(tracee, &utsname, address, sizeof(utsname));
if (status < 0)
break;
/* Some 32-bit programs like package managers can be
* confused when the kernel reports "x86_64". */
size = sizeof(utsname.machine);
strncpy(utsname.machine, "i686", size);
utsname.machine[size - 1] = '\0';
status = write_data(tracee, address, &utsname, sizeof(utsname));
if (status < 0)
break;
status = 0;
break;
}
#endif
case PR_execve:
translate_execve_exit(tracee);
goto end;
case PR_ptrace:
status = translate_ptrace_exit(tracee);
break;
case PR_wait4:
case PR_waitpid:
if (tracee->as_ptracer.waits_in != WAITS_IN_PROOT)
goto end;
status = translate_wait_exit(tracee);
break;
default:
goto end;
}
poke_reg(tracee, SYSARG_RESULT, (word_t) status);
end:
status = notify_extensions(tracee, SYSCALL_EXIT_END, 0, 0);
if (status < 0)
poke_reg(tracee, SYSARG_RESULT, (word_t) status);
}