/** * Update pid & wait status of @ptracer's wait(2) for the given * @ptracee. This function returns -errno if an error occurred, 0 if * the wait syscall will be restarted (ie. the event is discarded), * otherwise @ptracee's pid. */ static int update_wait_status(Tracee *ptracer, Tracee *ptracee) { word_t address; int result; /* Special case: the Linux kernel reports the terminating * event issued by a process to both its parent and its * tracer, except when they are the same. In this case the * Linux kernel reports the terminating event only once to the * tracing parent ... */ if (PTRACEE.ptracer == ptracee->parent && (WIFEXITED(PTRACEE.event4.ptracer.value) || WIFSIGNALED(PTRACEE.event4.ptracer.value))) { /* ... So hide this terminating event (toward its * tracer, ie. PRoot) and make the second one appear * (towards its parent, ie. the ptracer). This will * ensure its exit status is collected from a kernel * point-of-view (ie. it doesn't stay a zombie * forever). */ restart_original_syscall(ptracer); /* Detach this ptracee from its ptracer, PRoot doesn't * have anything else to emulate. */ detach_from_ptracer(ptracee); /* Zombies can rest in peace once the ptracer is * notified. */ if (PTRACEE.is_zombie) TALLOC_FREE(ptracee); return 0; } address = peek_reg(ptracer, ORIGINAL, SYSARG_2); if (address != 0) { poke_int32(ptracer, address, PTRACEE.event4.ptracer.value); if (errno != 0) return -errno; } PTRACEE.event4.ptracer.pending = false; /* Be careful; ptracee might get freed before its pid is * returned. */ result = ptracee->pid; /* Zombies can rest in peace once the ptracer is notified. */ if (PTRACEE.is_zombie) { detach_from_ptracer(ptracee); TALLOC_FREE(ptracee); } return result; }
/** * 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: { 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_3; } /* 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: if ((int) syscall_result >= 0) { /* New processes have no heap. */ bzero(tracee->heap, sizeof(Heap)); case PR_rt_sigreturn: case PR_sigreturn: tracee->restore_original_regs = false; } 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) { pid_t pid; /* See ptrace/wait.c for explanation. */ pid = (pid_t) syscall_result; if (pid > 0 && is_exited_direct_ptracee(tracee, pid)) { remove_exited_direct_ptracee(tracee, pid); restart_original_syscall(tracee); } 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); }