/*--------------------------------------------------------------------*/
/*--- User-mode execve(), and other stuff shared between stage1 ---*/
/*--- and stage2. m_ume.c ---*/
/*--------------------------------------------------------------------*/
/*
This file is part of Valgrind, a dynamic binary instrumentation
framework.
Copyright (C) 2000-2013 Julian Seward
jseward@acm.org
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., 59 Temple Place, Suite 330, Boston, MA
02111-1307, USA.
The GNU General Public License is contained in the file COPYING.
*/
#include "pub_core_basics.h"
#include "pub_core_vki.h"
#include "pub_core_libcbase.h"
#include "pub_core_libcassert.h" // VG_(exit), vg_assert
#include "pub_core_libcfile.h" // VG_(close) et al
#include "pub_core_libcprint.h" // VG_(message)
#include "pub_core_mallocfree.h" // VG_(strdup)
#include "pub_core_syscall.h" // VG_(mk_SysRes_Error)
#include "pub_core_options.h" // VG_(clo_xml)
#include "pub_core_ume.h" // self
#include "priv_ume.h"
typedef struct {
Bool (*match_fn)(const void *hdr, SizeT len);
Int (*load_fn)(Int fd, const HChar *name, ExeInfo *info);
} ExeHandler;
static ExeHandler exe_handlers[] = {
# if defined(VGO_linux)
{ VG_(match_ELF), VG_(load_ELF) },
# elif defined(VGO_darwin)
{ VG_(match_macho), VG_(load_macho) },
# else
# error "unknown OS"
# endif
{ VG_(match_script), VG_(load_script) },
};
#define EXE_HANDLER_COUNT (sizeof(exe_handlers)/sizeof(exe_handlers[0]))
// Check the file looks executable.
SysRes
VG_(pre_exec_check)(const HChar* exe_name, Int* out_fd, Bool allow_setuid)
{
Int fd, ret, i;
SysRes res;
Char buf[4096];
SizeT bufsz = sizeof buf, fsz;
Bool is_setuid = False;
// Check it's readable
res = VG_(open)(exe_name, VKI_O_RDONLY, 0);
if (sr_isError(res)) {
return res;
}
fd = sr_Res(res);
// Check we have execute permissions
ret = VG_(check_executable)(&is_setuid, exe_name, allow_setuid);
if (0 != ret) {
VG_(close)(fd);
if (is_setuid && !VG_(clo_xml)) {
VG_(message)(Vg_UserMsg, "\n");
VG_(message)(Vg_UserMsg,
"Warning: Can't execute setuid/setgid/setcap executable: %s\n",
exe_name);
VG_(message)(Vg_UserMsg, "Possible workaround: remove "
"--trace-children=yes, if in effect\n");
VG_(message)(Vg_UserMsg, "\n");
}
return VG_(mk_SysRes_Error)(ret);
}
fsz = (SizeT)VG_(fsize)(fd);
if (fsz < bufsz)
bufsz = fsz;
res = VG_(pread)(fd, buf, bufsz, 0);
if (sr_isError(res) || sr_Res(res) != bufsz) {
VG_(close)(fd);
return VG_(mk_SysRes_Error)(VKI_EACCES);
}
bufsz = sr_Res(res);
// Look for a matching executable format
for (i = 0; i < EXE_HANDLER_COUNT; i++) {
if ((*exe_handlers[i].match_fn)(buf, bufsz)) {
res = VG_(mk_SysRes_Success)(i);
break;
}
}
if (i == EXE_HANDLER_COUNT) {
// Rejected by all executable format handlers.
res = VG_(mk_SysRes_Error)(VKI_ENOEXEC);
}
// Write the 'out_fd' param if necessary, or close the file.
if (!sr_isError(res) && out_fd) {
*out_fd = fd;
} else {
VG_(close)(fd);
}
return res;
}
// returns: 0 = success, non-0 is failure
//
// We can execute only binaries (ELF, etc) or scripts that begin with "#!".
// (Not, for example, scripts that don't begin with "#!"; see
// do_exec_shell_followup for how that's handled.)
Int VG_(do_exec_inner)(const HChar* exe, ExeInfo* info)
{
SysRes res;
Int fd;
Int ret;
res = VG_(pre_exec_check)(exe, &fd, False/*allow_setuid*/);
if (sr_isError(res))
return sr_Err(res);
vg_assert2(sr_Res(res) >= 0 && sr_Res(res) < EXE_HANDLER_COUNT,
"invalid VG_(pre_exec_check) result");
ret = (*exe_handlers[sr_Res(res)].load_fn)(fd, exe, info);
VG_(close)(fd);
return ret;
}
static Bool is_hash_bang_file(const HChar* f)
{
SysRes res = VG_(open)(f, VKI_O_RDONLY, 0);
if (!sr_isError(res)) {
HChar buf[3] = {0,0,0};
Int fd = sr_Res(res);
Int n = VG_(read)(fd, buf, 2);
if (n == 2 && VG_STREQ("#!", buf))
return True;
}
return False;
}
// Look at the first 80 chars, and if any are greater than 127, it's binary.
// This is crude, but should be good enough. Note that it fails on a
// zero-length file, as we want.
static Bool is_binary_file(const HChar* f)
{
SysRes res = VG_(open)(f, VKI_O_RDONLY, 0);
if (!sr_isError(res)) {
UChar buf[80];
Int fd = sr_Res(res);
Int n = VG_(read)(fd, buf, 80);
Int i;
for (i = 0; i < n; i++) {
if (buf[i] > 127)
return True; // binary char found
}
return False;
} else {
// Something went wrong. This will only happen if we earlier
// succeeded in opening the file but fail here (eg. the file was
// deleted between then and now).
VG_(fmsg)("%s: unknown error\n", f);
VG_(exit)(126); // 126 == NOEXEC
}
}
// If the do_exec fails we try to emulate what the shell does (I used
// bash as a guide). It's worth noting that the shell can execute some
// things that VG_(do_exec)() (which subsitutes for the kernel's exec())
// will refuse to (eg. scripts lacking a "#!" prefix).
static Int do_exec_shell_followup(Int ret, const HChar* exe_name, ExeInfo* info)
{
# if defined(VGPV_arm_linux_android) \
|| defined(VGPV_x86_linux_android) \
|| defined(VGPV_mips32_linux_android) \
|| defined(VGPV_arm64_linux_android)
const HChar* default_interp_name = "/system/bin/sh";
# else
const HChar* default_interp_name = "/bin/sh";
# endif
SysRes res;
struct vg_stat st;
if (VKI_ENOEXEC == ret) {
// It was an executable file, but in an unacceptable format. Probably
// is a shell script lacking the "#!" prefix; try to execute it so.
// Is it a binary file?
if (is_binary_file(exe_name)) {
VG_(fmsg)("%s: cannot execute binary file\n", exe_name);
VG_(exit)(126); // 126 == NOEXEC
}
// Looks like a script. Run it with /bin/sh. This includes
// zero-length files.
info->interp_name = VG_(strdup)("ume.desf.1", default_interp_name);
info->interp_args = NULL;
if (info->argv && info->argv[0] != NULL)
info->argv[0] = exe_name;
ret = VG_(do_exec_inner)(info->interp_name, info);
if (0 != ret) {
// Something went wrong with executing the default interpreter
VG_(fmsg)("%s: bad interpreter (%s): %s\n",
exe_name, info->interp_name, VG_(strerror)(ret));
VG_(exit)(126); // 126 == NOEXEC
}
} else if (0 != ret) {
// Something else went wrong. Try to make the error more specific,
// and then print a message and abort.
Int exit_code = 126; // 126 == NOEXEC (bash)
res = VG_(stat)(exe_name, &st);
// Does the file exist ?
if (sr_isError(res) && sr_Err(res) == VKI_ENOENT) {
VG_(fmsg)("%s: %s\n", exe_name, VG_(strerror)(ret));
exit_code = 127; // 127 == NOTFOUND (bash)
// Was it a directory?
} else if (!sr_isError(res) && VKI_S_ISDIR(st.mode)) {
VG_(fmsg)("%s: is a directory\n", exe_name);
// Was it not executable?
} else if (0 != VG_(check_executable)(NULL, exe_name,
False/*allow_setuid*/)) {
VG_(fmsg)("%s: %s\n", exe_name, VG_(strerror)(ret));
// Did it start with "#!"? If so, it must have been a bad interpreter.
} else if (is_hash_bang_file(exe_name)) {
VG_(fmsg)("%s: bad interpreter: %s\n", exe_name, VG_(strerror)(ret));
// Otherwise it was something else.
} else {
VG_(fmsg)("%s: %s\n", exe_name, VG_(strerror)(ret));
}
VG_(exit)(exit_code);
}
return ret;
}
// This emulates the kernel's exec(). If it fails, it then emulates the
// shell's handling of the situation.
// See pub_core_ume.h for an indication of which entries of 'info' are
// inputs, which are outputs, and which are both.
/* returns: 0 = success, non-0 is failure */
Int VG_(do_exec)(const HChar* exe_name, ExeInfo* info)
{
Int ret;
info->interp_name = NULL;
info->interp_args = NULL;
ret = VG_(do_exec_inner)(exe_name, info);
if (0 != ret) {
ret = do_exec_shell_followup(ret, exe_name, info);
}
return ret;
}
/*--------------------------------------------------------------------*/
/*--- end ---*/
/*--------------------------------------------------------------------*/