| /* |
| * Copyright (c) 1991, 1992 Paul Kranenburg <pk@cs.few.eur.nl> |
| * Copyright (c) 1993 Branko Lankester <branko@hacktic.nl> |
| * Copyright (c) 1993, 1994, 1995, 1996 Rick Sladkey <jrs@world.std.com> |
| * Copyright (c) 1996-1999 Wichert Akkerman <wichert@cistron.nl> |
| * Copyright (c) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation |
| * Linux for s390 port by D.J. Barrow |
| * <barrow_dj@mail.yahoo.com,djbarrow@de.ibm.com> |
| * Copyright (c) 1999-2018 The strace developers. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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 "defs.h" |
| #include <sys/uio.h> |
| #include <asm/unistd.h> |
| |
| #include "scno.h" |
| #include "ptrace.h" |
| |
| static bool process_vm_readv_not_supported; |
| |
| #ifndef HAVE_PROCESS_VM_READV |
| /* |
| * Need to do this since process_vm_readv() is not yet available in libc. |
| * When libc is updated, only "static bool process_vm_readv_not_supported" |
| * line remains. |
| * The name is different to avoid potential collision with OS headers. |
| */ |
| static ssize_t strace_process_vm_readv(pid_t pid, |
| const struct iovec *lvec, |
| unsigned long liovcnt, |
| const struct iovec *rvec, |
| unsigned long riovcnt, |
| unsigned long flags) |
| { |
| return syscall(__NR_process_vm_readv, |
| (long) pid, lvec, liovcnt, rvec, riovcnt, flags); |
| } |
| # define process_vm_readv strace_process_vm_readv |
| #endif /* !HAVE_PROCESS_VM_READV */ |
| |
| static ssize_t |
| vm_read_mem(const pid_t pid, void *const laddr, |
| const kernel_ulong_t raddr, const size_t len) |
| { |
| const unsigned long truncated_raddr = raddr; |
| |
| #if SIZEOF_LONG < SIZEOF_KERNEL_LONG_T |
| if (raddr != (kernel_ulong_t) truncated_raddr) { |
| errno = EIO; |
| return -1; |
| } |
| #endif |
| |
| const struct iovec local = { |
| .iov_base = laddr, |
| .iov_len = len |
| }; |
| const struct iovec remote = { |
| .iov_base = (void *) truncated_raddr, |
| .iov_len = len |
| }; |
| |
| const ssize_t rc = process_vm_readv(pid, &local, 1, &remote, 1, 0); |
| if (rc < 0 && errno == ENOSYS) |
| process_vm_readv_not_supported = true; |
| |
| return rc; |
| } |
| |
| static bool |
| tracee_addr_is_invalid(kernel_ulong_t addr) |
| { |
| return |
| #if ANY_WORDSIZE_LESS_THAN_KERNEL_LONG |
| current_wordsize < sizeof(addr) && addr & ~(kernel_ulong_t) -1U; |
| #else |
| false; |
| #endif |
| } |
| |
| /* legacy method of copying from tracee */ |
| static int |
| umoven_peekdata(const int pid, kernel_ulong_t addr, unsigned int len, |
| void *laddr) |
| { |
| unsigned int nread = 0; |
| unsigned int residue = addr & (sizeof(long) - 1); |
| |
| while (len) { |
| addr &= -sizeof(long); /* aligned address */ |
| |
| errno = 0; |
| union { |
| long val; |
| char x[sizeof(long)]; |
| } u = { .val = ptrace(PTRACE_PEEKDATA, pid, addr, 0) }; |
| |
| switch (errno) { |
| case 0: |
| break; |
| case ESRCH: case EINVAL: |
| /* these could be seen if the process is gone */ |
| return -1; |
| case EFAULT: case EIO: case EPERM: |
| /* address space is inaccessible */ |
| if (nread) { |
| perror_msg("umoven: short read (%u < %u) @0x%" PRI_klx, |
| nread, nread + len, addr - nread); |
| } |
| return -1; |
| default: |
| /* all the rest is strange and should be reported */ |
| perror_msg("umoven: PTRACE_PEEKDATA pid:%d @0x%" PRI_klx, |
| pid, addr); |
| return -1; |
| } |
| |
| unsigned int m = MIN(sizeof(long) - residue, len); |
| memcpy(laddr, &u.x[residue], m); |
| residue = 0; |
| addr += sizeof(long); |
| laddr += m; |
| nread += m; |
| len -= m; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Copy `len' bytes of data from process `pid' |
| * at address `addr' to our space at `our_addr'. |
| */ |
| int |
| umoven(struct tcb *const tcp, kernel_ulong_t addr, unsigned int len, |
| void *const our_addr) |
| { |
| if (tracee_addr_is_invalid(addr)) |
| return -1; |
| |
| const int pid = tcp->pid; |
| |
| if (process_vm_readv_not_supported) |
| return umoven_peekdata(pid, addr, len, our_addr); |
| |
| int r = vm_read_mem(pid, our_addr, addr, len); |
| if ((unsigned int) r == len) |
| return 0; |
| if (r >= 0) { |
| error_msg("umoven: short read (%u < %u) @0x%" PRI_klx, |
| (unsigned int) r, len, addr); |
| return -1; |
| } |
| switch (errno) { |
| case ENOSYS: |
| case EPERM: |
| /* try PTRACE_PEEKDATA */ |
| return umoven_peekdata(pid, addr, len, our_addr); |
| case ESRCH: |
| /* the process is gone */ |
| return -1; |
| case EFAULT: case EIO: |
| /* address space is inaccessible */ |
| return -1; |
| default: |
| /* all the rest is strange and should be reported */ |
| perror_msg("process_vm_readv: pid:%d @0x%" PRI_klx, |
| pid, addr); |
| return -1; |
| } |
| } |
| |
| /* |
| * Like umoven_peekdata but make the additional effort of looking |
| * for a terminating zero byte. |
| */ |
| static int |
| umovestr_peekdata(const int pid, kernel_ulong_t addr, unsigned int len, |
| void *laddr) |
| { |
| unsigned int nread = 0; |
| unsigned int residue = addr & (sizeof(long) - 1); |
| void *const orig_addr = laddr; |
| |
| while (len) { |
| addr &= -sizeof(long); /* aligned address */ |
| |
| errno = 0; |
| union { |
| unsigned long val; |
| char x[sizeof(long)]; |
| } u = { .val = ptrace(PTRACE_PEEKDATA, pid, addr, 0) }; |
| |
| switch (errno) { |
| case 0: |
| break; |
| case ESRCH: case EINVAL: |
| /* these could be seen if the process is gone */ |
| return -1; |
| case EFAULT: case EIO: case EPERM: |
| /* address space is inaccessible */ |
| if (nread) { |
| perror_msg("umovestr: short read (%d < %d) @0x%" PRI_klx, |
| nread, nread + len, addr - nread); |
| } |
| return -1; |
| default: |
| /* all the rest is strange and should be reported */ |
| perror_msg("umovestr: PTRACE_PEEKDATA pid:%d @0x%" PRI_klx, |
| pid, addr); |
| return -1; |
| } |
| |
| unsigned int m = MIN(sizeof(long) - residue, len); |
| memcpy(laddr, &u.x[residue], m); |
| while (residue < sizeof(long)) |
| if (u.x[residue++] == '\0') |
| return (laddr - orig_addr) + residue; |
| residue = 0; |
| addr += sizeof(long); |
| laddr += m; |
| nread += m; |
| len -= m; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Like `umove' but make the additional effort of looking |
| * for a terminating zero byte. |
| * |
| * Returns < 0 on error, strlen + 1 if NUL was seen, |
| * else 0 if len bytes were read but no NUL byte seen. |
| * |
| * Note: there is no guarantee we won't overwrite some bytes |
| * in laddr[] _after_ terminating NUL (but, of course, |
| * we never write past laddr[len-1]). |
| */ |
| int |
| umovestr(struct tcb *const tcp, kernel_ulong_t addr, unsigned int len, |
| char *laddr) |
| { |
| if (tracee_addr_is_invalid(addr)) |
| return -1; |
| |
| const int pid = tcp->pid; |
| |
| if (process_vm_readv_not_supported) |
| return umovestr_peekdata(pid, addr, len, laddr); |
| |
| const size_t page_size = get_pagesize(); |
| const size_t page_mask = page_size - 1; |
| unsigned int nread = 0; |
| |
| while (len) { |
| /* |
| * Don't cross pages, otherwise we can get EFAULT |
| * and fail to notice that terminating NUL lies |
| * in the existing (first) page. |
| */ |
| unsigned int chunk_len = len > page_size ? page_size : len; |
| unsigned int end_in_page = (addr + chunk_len) & page_mask; |
| if (chunk_len > end_in_page) /* crosses to the next page */ |
| chunk_len -= end_in_page; |
| |
| int r = vm_read_mem(pid, laddr, addr, chunk_len); |
| if (r > 0) { |
| char *nul_addr = memchr(laddr, '\0', r); |
| |
| if (nul_addr) |
| return (nul_addr - laddr) + 1; |
| addr += r; |
| laddr += r; |
| nread += r; |
| len -= r; |
| continue; |
| } |
| switch (errno) { |
| case ENOSYS: |
| case EPERM: |
| /* try PTRACE_PEEKDATA */ |
| if (!nread) |
| return umovestr_peekdata(pid, addr, |
| len, laddr); |
| ATTRIBUTE_FALLTHROUGH; |
| case EFAULT: case EIO: |
| /* address space is inaccessible */ |
| if (nread) |
| perror_msg("umovestr: short read (%d < %d) @0x%" PRI_klx, |
| nread, nread + len, addr - nread); |
| return -1; |
| case ESRCH: |
| /* the process is gone */ |
| return -1; |
| default: |
| /* all the rest is strange and should be reported */ |
| perror_msg("process_vm_readv: pid:%d @0x%" PRI_klx, |
| pid, addr); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |