| |
| /*--------------------------------------------------------------------*/ |
| /*--- 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-2015 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) || defined(VGO_solaris) |
| { 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 ---*/ |
| /*--------------------------------------------------------------------*/ |