From: Mike Frysinger <vapier@gentoo.org>
To: linux-parisc@vger.kernel.org
Subject: ptrace interface does not permit modification of syscall return
Date: Mon, 21 Dec 2015 12:55:58 -0500 [thread overview]
Message-ID: <20151221175558.GA11489@vapier.lan> (raw)
[-- Attachment #1.1: Type: text/plain, Size: 1529 bytes --]
i have a ptrace program that watches for specific syscalls and when
matched, will:
- on entry change the syscall nr to -1 (so the kernel will skip it)
- on exit change the return to -EPERM so the userspace sees a denial
i have this working on most arches (x86, x86_64, arm, alpha, ia64, etc...).
on parisc, the kernel (using 3.18.7 currently) appears to be wrong. in my
tests, if i don't mess with the syscall nr, i can change the return value
fine (to EPERM or whatever). but the syscall executed which i do not want.
if i change the syscall to -1, then i can't change the return value (so the
child sees ENOSYS), but the kernel still executes the original syscall.
i have a simple test case attached to show the issue. the code does:
- spawn a child with the parent tracing it
- child will do:
- dupe stderr to another fd
- unlink a file named ".test.flag"
- write a message through the new fd
- close a magic # so the parent knows to start denying
- should see EPERM but it sees ENOSYS
- close the new fd
- should see EPERM but it is closed!
- write to the new fd
- should work, but the fd is closed
- call create on ".test.flag"
- should see EPERM, but the file is created!
- parent will do:
- log the syscalls until child runs close(-12345)
- the parent will then try to deny all close/creat calls
- uses PTRACE_POKEUSER w/PT_GR20 to set syscall to -1
- uses PTRACE_POKEUSER w/PT_GR28 to set return to -EPERM
you can run the test case by doing:
$ gcc test.c && ./a.out
-mike
[-- Attachment #1.2: test.c --]
[-- Type: text/x-c, Size: 5650 bytes --]
#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <asm/offset.h>
#include <asm/ptrace.h>
static pid_t trace_pid;
static long _do_ptrace(enum __ptrace_request request, const char *srequest, void *addr, void *data)
{
long ret;
try_again:
errno = 0;
ret = ptrace(request, trace_pid, addr, data);
if (ret == -1) {
/* Child hasn't gotten to the next marker yet ? */
if (errno == ESRCH) {
int status;
if (waitpid(trace_pid, &status, 0) == -1) {
/* nah, it's dead ... should we whine though ? */
_exit(0);
}
sched_yield();
goto try_again;
} else if (errno == EIO || errno == EFAULT) {
/* This comes up when the child itself tries to use a bad pointer.
* That's not something the sandbox should abort on. #560396
*/
return ret;
} else if (!errno)
if (request == PTRACE_PEEKDATA ||
request == PTRACE_PEEKTEXT ||
request == PTRACE_PEEKUSER)
return ret;
err(1, "do_ptrace: ptrace(%s, ..., %p, %p)", srequest, addr, data);
}
return ret;
}
#define do_ptrace(request, addr, data) _do_ptrace(request, #request, addr, data)
static long do_peekuser(long offset)
{
return do_ptrace(PTRACE_PEEKUSER, (void *)offset, NULL);
}
static long do_pokeuser(long offset, long val)
{
return do_ptrace(PTRACE_POKEUSER, (void *)offset, (void *)val);
}
static void trace_child_signal(int signo, siginfo_t *info, void *context)
{
#if 0
warnx("got sig %s(%i): code:%s(%i) status:%s(%i)",
strsignal(signo), signo,
"---", info->si_code,
strsignal(info->si_status), info->si_status);
#endif
switch (info->si_code) {
case CLD_DUMPED:
case CLD_KILLED:
_exit(128 + info->si_status);
case CLD_EXITED:
_exit(info->si_status);
case CLD_TRAPPED:
switch (info->si_status) {
case SIGSTOP:
kill(trace_pid, SIGCONT);
case SIGTRAP:
case SIGCONT:
return;
}
/* For whatever signal the child caught, let's ignore it and
* continue on. If it aborted, segfaulted, whatever, that's
* its problem, not ours, so don't whine about it. We just
* have to be sure to bubble it back up. #265072
*/
do_ptrace(PTRACE_CONT, NULL, (void *)(long)info->si_status);
return;
}
errx(1, "unhandled signal case");
}
static const char *lookup_syscall(long nr)
{
switch (nr) {
#define X(n) case SYS_##n: return #n;
X(access)
X(brk)
X(close)
X(creat)
X(dup)
X(fstat64)
X(mmap)
X(mprotect)
X(munmap)
X(open)
X(read)
X(uname)
X(write)
#undef X
}
return "";
}
void child_main(void)
{
char test_file[] = ".test.flag";
char msg[] = "child: you should see two of these\n";
int fd = dup(2);
unlink(test_file);
write(fd, msg, sizeof(msg));
/* Marker for the parent to watch. */
errno = 0;
close(-12345);
fprintf(stderr, "child: close marker (should be EPERM): %m\n");
errno = 0;
close(fd);
fprintf(stderr, "child: real close (should be EPERM): %m\n");
errno = 0;
write(fd, msg, sizeof(msg));
fprintf(stderr, "child: write (should be success): %m\n");
errno = 0;
creat(test_file, 0660);
fprintf(stderr, "child: creat (should be EPERM): %m\n");
errno = 0;
access(test_file, F_OK);
fprintf(stderr, "child: access (should be ENOENT): %m\n");
unlink(test_file);
exit(0);
}
static void parent_main(void)
{
int status;
long nr, arg1;
/* Wait for the child to exec. */
while (1) {
do_ptrace(PTRACE_SYSCALL, NULL, NULL);
waitpid(trace_pid, &status, 0);
unsigned event = ((unsigned)status >> 16);
if (event == PTRACE_EVENT_EXEC) {
warnx("parent: hit exec!");
break;
} else
warnx("parent: waiting for exec; status: %#x", status);
}
/* Main loop. */
bool saw_close = false;
bool before_syscall = false;
bool fake_syscall_ret = false;
while (1) {
do_ptrace(PTRACE_SYSCALL, NULL, NULL);
waitpid(trace_pid, &status, 0);
nr = do_peekuser(PT_GR20);
arg1 = do_peekuser(PT_GR26);
if (before_syscall) {
warnx("parent: NR:%3li %s", nr, lookup_syscall(nr));
/* Once the child hits the marker, deny all close & creat calls */
if (nr == __NR_close || nr == __NR_creat) {
if (saw_close || arg1 == -12345) {
saw_close = true;
warnx("parent: setting NR to -1");
do_pokeuser(PT_GR20, -1);
fake_syscall_ret = true;
}
}
} else if (fake_syscall_ret) {
warnx("parent: forcing EPERM");
do_pokeuser(PT_GR28, -EPERM);
fake_syscall_ret = false;
}
before_syscall = !before_syscall;
}
}
int main(int argc, char *argv[])
{
struct sigaction sa, old_sa;
/* Child will re-exec us so the ptrace is clean for the parent. */
if (argc > 1)
child_main();
/* Set up signal handler to watch for child events. */
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sa.sa_sigaction = trace_child_signal;
sigaction(SIGCHLD, &sa, &old_sa);
/* Fork a child and have the parent do some early ptrace init. */
trace_pid = fork();
if (trace_pid == -1) {
err(1, "fork() failed");
} else if (trace_pid) {
warn("parent waiting for child (pid=%i) to signal", trace_pid);
waitpid(trace_pid, NULL, 0);
do_ptrace(PTRACE_SETOPTIONS, NULL,
(void *)(PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC));
parent_main();
errx(1, "child should have quit, as should we");
}
/* Have the child set itself up for tracing before execing again. */
warnx("child setting up ...");
sigaction(SIGCHLD, &old_sa, NULL);
do_ptrace(PTRACE_TRACEME, NULL, NULL);
kill(getpid(), SIGSTOP);
execl(argv[0], argv[0], "--child", NULL);
return 0;
}
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
next reply other threads:[~2015-12-21 17:55 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-12-21 17:55 Mike Frysinger [this message]
2015-12-22 21:10 ` ptrace interface does not permit modification of syscall return Helge Deller
2015-12-28 18:03 ` Mike Frysinger
2016-01-19 15:11 ` Helge Deller
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20151221175558.GA11489@vapier.lan \
--to=vapier@gentoo.org \
--cc=linux-parisc@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.