Skip to content

Commit

Permalink
i#907 early inject symlink: replace accesses to /proc/self/exe
Browse files Browse the repository at this point in the history
For early injection, /proc/self/exe points at libdynamorio.so instead of
the app.  We monitor SYS_readlink and SYS_execve and when we see a
reference to the /proc/self/exe for this process we replace it with the
actual application path.

Adds a new linux.readlink test.

Fixes #907

Review-URL: https://codereview.appspot.com/211650043
  • Loading branch information
derekbruening committed Mar 13, 2015
1 parent 17e2b7b commit ae0f55e
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 4 deletions.
88 changes: 86 additions & 2 deletions core/unix/os.c
Original file line number Diff line number Diff line change
Expand Up @@ -3849,6 +3849,36 @@ os_get_disk_free_space(/*IN*/ file_t file_handle,
return true;
}

#ifdef LINUX
static bool
symlink_is_self_exe(const char *path)
{
/* Look for "/proc/%d/exe" where %d exists in /proc/self/task/%d,
* or "/proc/self/exe". Rule out the exe link for another process
* (though it could also be under DR we have no simple way to obtain
* its actual app path).
*/
# define SELF_LEN_LEADER 6 /* "/proc/" */
# define SELF_LEN_TRAILER 4 /* "/exe" */
# define SELF_LEN_MAX 18
size_t len = strlen(path);
if (strcmp(path, "/proc/self/exe") == 0)
return true;
if (len < SELF_LEN_MAX && /* /proc/nnnnnn/exe */
strncmp(path, "/proc/", SELF_LEN_LEADER) == 0 &&
strncmp(path + len - SELF_LEN_TRAILER, "/exe", SELF_LEN_TRAILER) == 0) {
int pid;
if (sscanf(path + SELF_LEN_LEADER, "%d", &pid) == 1) {
char task[32];
snprintf(task, BUFFER_SIZE_ELEMENTS(task), "/proc/self/task/%d", pid);
NULL_TERMINATE_BUFFER(task);
return os_file_exists(task, true/*dir*/);
}
}
return false;
}
#endif

void
exit_process_syscall(long status)
{
Expand Down Expand Up @@ -4403,6 +4433,10 @@ ignorable_system_call_normalized(int num)
case SYS_set_tls:
#endif
return false;
#ifdef LINUX
case SYS_readlink:
return !DYNAMO_OPTION(early_inject);
#endif
default:
#ifdef VMX86_SERVER
if (is_vmkuw_sysnum(num))
Expand Down Expand Up @@ -5083,11 +5117,22 @@ handle_execve(dcontext_t *dcontext)
/* FIXME i#191: supposed to preserve things like pending signal
* set across execve: going to ignore for now
*/
char *fname = (char *) sys_param(dcontext, 0);
char *fname = (char *) sys_param(dcontext, 0);
bool x64 = IF_X64_ELSE(true, false);
file_t file;
char *inject_library_path;
DEBUG_DECLARE(char **argv = (char **) sys_param(dcontext, 1);)
#ifdef LINUX
if (DYNAMO_OPTION(early_inject) && symlink_is_self_exe(fname)) {
/* i#907: /proc/self/exe points at libdynamorio.so. Make sure we run
* the right thing here.
*/
dcontext->sys_param4 = (reg_t) fname; /* store for restore in post */
fname = get_application_name();
*sys_param_addr(dcontext, 0) = (reg_t) get_application_name();
} else
dcontext->sys_param4 = 0; /* no restore in post */
#endif

LOG(GLOBAL, LOG_ALL, 1, "\n---------------------------------------------------------------------------\n");
LOG(THREAD, LOG_ALL, 1, "\n---------------------------------------------------------------------------\n");
Expand Down Expand Up @@ -5167,6 +5212,12 @@ handle_execve_post(dcontext_t *dcontext)
#ifdef STATIC_LIBRARY
/* nothing to clean up */
return;
#endif
#ifdef LINUX
if (dcontext->sys_param4 != 0) {
/* restore original /proc/.../exe */
*sys_param_addr(dcontext, 0) = dcontext->sys_param4;
}
#endif
if (new_envp != NULL) {
int i;
Expand Down Expand Up @@ -6436,8 +6487,17 @@ pre_system_call(dcontext_t *dcontext)
break;
}

/* i#107 syscalls that might change/query app's segment */
#ifdef LINUX
case SYS_readlink:
if (DYNAMO_OPTION(early_inject)) {
dcontext->sys_param0 = sys_param(dcontext, 0);
dcontext->sys_param1 = sys_param(dcontext, 1);
dcontext->sys_param2 = sys_param(dcontext, 2);
}
break;

/* i#107 syscalls that might change/query app's segment */

# ifdef X64
case SYS_arch_prctl: {
/* we handle arch_prctl in post_syscall */
Expand Down Expand Up @@ -7318,6 +7378,30 @@ post_system_call(dcontext_t *dcontext)
}
#endif

#ifdef LINUX
case SYS_readlink:
if (success && DYNAMO_OPTION(early_inject)) {
/* i#907: /proc/self/exe is a symlink to libdynamorio.so. We need
* to fix it up if the app queries. Any thread id can be passed to
* /proc/%d/exe, so we have to check. We could instead look for
* libdynamorio.so in the result but we've tweaked our injector
* in the past to exec different binaries so this seems more robust.
*/
if (symlink_is_self_exe((const char *)dcontext->sys_param0)) {
char *tgt = (char *) dcontext->sys_param1;
size_t tgt_sz = (size_t) dcontext->sys_param2;
int len = snprintf(tgt, tgt_sz, "%s", get_application_name());
if (len > 0)
set_success_return_val(dcontext, len);
else {
set_failure_return_val(dcontext, EINVAL);
DODEBUG({ dcontext->expect_last_syscall_to_fail = true; });
}
}
}
break;
#endif

#ifdef VMX86_SERVER
default:
if (is_vmkuw_sysnum(sysnum)) {
Expand Down
2 changes: 2 additions & 0 deletions suite/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1940,6 +1940,8 @@ if (UNIX)
# thereby exercising on our ability to copy libc's TLS data.
tobuild_ops(linux.app_tls linux/app_tls.c "-native_exec_list libfoo.so" "")

tobuild_ops(linux.readlink linux/readlink.c "-early_inject" "exec")

# Test fully static executables, which we only support with early injection
# or STATIC_LIBRARY.
set(fib_static_ops "-early_inject")
Expand Down
3 changes: 1 addition & 2 deletions suite/tests/linux/execve.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2014 Google, Inc. All rights reserved.
* Copyright (c) 2014-2015 Google, Inc. All rights reserved.
* Copyright (c) 2003-2008 VMware, Inc. All rights reserved.
* **********************************************************/

Expand Down Expand Up @@ -37,7 +37,6 @@

#include "tools.h"

#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h> /* for wait and mmap */
#include <sys/wait.h> /* for wait */
Expand Down
82 changes: 82 additions & 0 deletions suite/tests/linux/readlink.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* **********************************************************
* Copyright (c) 2015 Google, Inc. All rights reserved.
* **********************************************************/

/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/

#include "tools.h"
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

/* Tests i#907: /proc/self/exe transparency with early injection. */

int
main(int argc, char *argv[])
{
/* Test reading the symlink via readlink */
char proc[64];
char buf[512];
ssize_t res;
snprintf(proc, BUFFER_SIZE_ELEMENTS(proc), "/proc/%d/exe", getpid());
NULL_TERMINATE_BUFFER(proc);
res = readlink(proc, buf, BUFFER_SIZE_ELEMENTS(buf));
NULL_TERMINATE_BUFFER(buf);
if (res > 0 && strrchr(buf, '/') != NULL)
print("/proc/pid/exe points to %s\n", strrchr(buf, '/')+1);
else {
perror("readlink failed");
return 1;
}
/* XXX: another good test would be to make a thread and use /proc/tid/exe */

/* Test executing the symlink via execve.
* We invoked ourselves initially with an arg, to avoid repeated execs.
*/
if (argc > 1) {
pid_t child = fork();
if (child < 0) {
perror("fork failed");
} else if (child > 0) {
pid_t result = waitpid(child, NULL, 0);
assert(result == child);
print("child has exited\n");
} else {
const char *arg[2] = {proc, NULL};
/* Update for child's pid */
snprintf(proc, BUFFER_SIZE_ELEMENTS(proc), "/proc/%d/exe", getpid());
NULL_TERMINATE_BUFFER(proc);
res = execve(proc, (char **)arg, NULL/*env*/);
if (res < 0)
perror("execve failed");
}
}
return 0;
}
3 changes: 3 additions & 0 deletions suite/tests/linux/readlink.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/proc/pid/exe points to linux.readlink
/proc/pid/exe points to linux.readlink
child has exited

0 comments on commit ae0f55e

Please sign in to comment.