10

For some security purpose, I use ptrace to get the syscall number, and if it's a dangerous call (like 10 for unlink), I want to cancel this syscall.

Here's the source code for the test program del.c. Compile with gcc -o del del.c.

#include <stdio.h>
#include <stdlib.h>
int main()
{
    remove("/root/abc.out");
    return 0;
}

Here's the security manager source code test.c. Compile with gcc -o test test.c.

#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>

int main()
{
    int i;
    pid_t child;
    int status;
    long orig_eax;
    child = fork();
    if(child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/root/del", "del",  NULL);
    }
    else {
        i = 0;
        while(1){
            wait(&status);
            if (WIFEXITED(status) || WIFSIGNALED(status) )break;

            orig_eax = ptrace(PTRACE_PEEKUSER,
                          child, 4 * ORIG_EAX,
                          NULL);
            if (orig_eax == 10){
                fprintf(stderr, "Got it\n");
                kill(child, SIGKILL);
            }
            printf("%d time,"
               "system call %ld\n", i++, orig_eax);
            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        }
    }
    return 0;
}

Create the abc.out file, then run the test program:

cd /root
touch abc.out
./test

The file /root/abc.out should still exist.

How do I implement this requirement?

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
laifjei
  • 606
  • 2
  • 7
  • 16
  • 1
    Are you doing anything to force the forked process to wait to see whether or not your security manager is going to kill it? As I understand it, `waitpid()` is going to make the current process wait until the forked process terminates. So of course you will not be able to stop the system-call. By the time you're able to get the system-call code, the forked process has already executed it. – aroth Aug 18 '12 at 01:50
  • @aroth, I sure `waitpid()` works, My problem is how to cancel the sys_call? – laifjei Aug 18 '12 at 02:43

3 Answers3

8

Well it seems that sometimes PTRACE_KILL does not work very well, you can use kill instead:

if (orig_eax == 10)
{
    kill(pid, SIGKILL);
}

EDIT : I test on my machine (Ubuntu kernel 3.4) with this program and all is ok:

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <stdio.h>

int main(int argc, char **argv)
{   
    pid_t child;
    long orig_eax;
    int status;

    child = fork();
    if(child == 0) 
    {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
    }
    else 
    {
        /* Both wait and waitpid works */
        //wait(NULL);
        waitpid(child, &status, 0);
        orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL);
        /* Tracking execve syscall */
        if (orig_eax == 11)
        {
            /* Both PTRACE_KILL and kill() works on my 3.4.4 Kernel */
            fprintf(stdout, "GOT IT\n");
            //ptrace(PTRACE_KILL, child, NULL, NULL);
            kill(child, SIGKILL);
        }
    }

    return 0;
}

UPDATE : The problem is that you are using 10 for tracking system call instead of 11(because you are executing execve command), this code will work with your rm command:

if (orig_eax == 11)
{
    /* Both PTRACE_KILL and kill() works on my 3.4.4 Kernel */
    fprintf(stdout, "INSIDE THE TRAP, FILE WILL NOT BE REMOVED\n");
    ptrace(PTRACE_KILL, child, NULL, NULL);
    //kill(child, SIGKILL);
}

EDIT : I try this code and all wroks fine (the file abc.out still exist after the execution of CALL_REMOVE)

/*
 * REMOVE.c
 * gcc -Wall REMOVE.c -o REMOVE
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
        /* Both calls work */
        //remove("/root/abc.out");
        unlink("/root/abc.out");

        return 0;
}

/*
 * CALL_REMOVE.c
 * gcc -Wall CALL_REMOVE.c -o CALL_REMOVE
 */

#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <sys/user.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
        int i;
        pid_t child;
        int status;
        long orig_eax;
        int kill_ret = 0;

        child = fork();

        if(child == 0)
        {
                ptrace(PTRACE_TRACEME, 0, NULL, NULL);
                execl("/root/REMOVE", "REMOVE",  NULL);
        }
        else
        {
                i = 0;
                while(1)
                {
                        wait(&status);
                        if (WIFEXITED(status) || WIFSIGNALED(status) )
                                break;

                        orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL);
                        if (orig_eax == 10)
                        {
                                fprintf(stderr, "Got it\n");
                                kill_ret = kill(child, SIGKILL);
                                if (kill_ret == -1)
                                {
                                    fprintf(stderr, "Failed to kill ---> %s\n", strerror(errno));
                                }
                        }
                        printf("%d time, system call %ld\n", i++, orig_eax);
                        ptrace(PTRACE_SYSCALL, child, NULL, NULL);
                }
        }

        return 0;
}

We got this output:

root@UnixServer:/root# ll
total 28K
-rw-r--r-- 1 root root    6 2012-08-18 19:37 abc.out
-rw-r--r-- 1 root root 1023 2012-08-18 19:39 CALL_REMOVE.c
-rw-r--r-- 1 root root  213 2012-08-18 19:39 REMOVE.c
-rwxr-xr-x 1 root root 7,3K 2012-08-18 19:39 CALL_REMOVE
-rwxr-xr-x 1 root root 7,0K 2012-08-18 19:39 REMOVE
root@UnixServer:/root# ./CALL_REMOVE 
0 time, system call 11
1 time, system call 45
2 time, system call 45
3 time, system call 33
4 time, system call 33
5 time, system call 192
6 time, system call 192
7 time, system call 33
8 time, system call 33
9 time, system call 5
10 time, system call 5
11 time, system call 197
12 time, system call 197
13 time, system call 192
14 time, system call 192
15 time, system call 6
16 time, system call 6
17 time, system call 33
18 time, system call 33
19 time, system call 5
20 time, system call 5
21 time, system call 3
22 time, system call 3
23 time, system call 197
24 time, system call 197
25 time, system call 192
26 time, system call 192
27 time, system call 192
28 time, system call 192
29 time, system call 192
30 time, system call 192
31 time, system call 6
32 time, system call 6
33 time, system call 192
34 time, system call 192
35 time, system call 243
36 time, system call 243
37 time, system call 125
38 time, system call 125
39 time, system call 125
40 time, system call 125
41 time, system call 125
42 time, system call 125
43 time, system call 91
44 time, system call 91
Got it
45 time, system call 10
root@UnixServer:/root# ll
total 28K
-rw-r--r-- 1 root root    6 2012-08-18 19:37 abc.out
-rw-r--r-- 1 root root 1023 2012-08-18 19:39 CALL_REMOVE.c
-rw-r--r-- 1 root root  213 2012-08-18 19:39 REMOVE.c
-rwxr-xr-x 1 root root 7,3K 2012-08-18 19:39 CALL_REMOVE
-rwxr-xr-x 1 root root 7,0K 2012-08-18 19:39 REMOVE
root@UnixServer:/root# 
TOC
  • 4,326
  • 18
  • 21
  • I try it, but the sys_call success also. – laifjei Aug 18 '12 at 08:43
  • @laifjei : that's strange! and what about if you use wait(NULL) instead of waitpid()? – TOC Aug 18 '12 at 08:48
  • @laifjei : On my Ubuntu (3.4 kernel), both couples (wait, waitpid) and (PTRACE_KILL, kill()) works fine. What system kernel are you using? – TOC Aug 18 '12 at 08:54
  • `Linux ubuntu 2.6.38-8-generic #42-Ubuntu SMP Mon Apr 11 03:31:50 UTC 2011 i686 i686 i386 GNU/Linux` – laifjei Aug 18 '12 at 08:57
  • I have one solution: modify the sys_call's args, make the sys_call failed. It's that work? – laifjei Aug 18 '12 at 08:59
  • Well, i dont think that this issue is interesting in your case, because you're trying to trap a specific sys call. The strange thing is that kill does not work! – TOC Aug 18 '12 at 09:03
  • @laifjei : is your system up to date? – TOC Aug 18 '12 at 09:05
  • I use `remove("/root/abc.out");`, when the test finished, the file `/root/abc.out` gone. – laifjei Aug 18 '12 at 09:15
  • @, I edit my question, you can test on your machine, and see what's happen. – laifjei Aug 18 '12 at 11:15
  • @laifjei : All works fine on my machine, i use it as normal and as root user and both works fine. also unlink call works if i use it instead of remove. So i really don't understand what's going on! – TOC Aug 18 '12 at 17:54
  • I try it on `Linux master 2.6.18-8.el5xen #1 SMP Thu Mar 15 21:02:53 EDT 2007 i686 athlon i386 GNU/Linux` machine, it works. So I think it's a machine depend problem. Thanks. – laifjei Aug 19 '12 at 00:51
1

There are tons of low-level/clever (and error prone) ways to solve this problem, but the modern Linux Kernel (3.x, your distribution might need a backported patch) supports something called seccomp which allows you to restrict the system calls a process can make. Sandboxes use this functionality, including Chromium.

You can see a discussion about this on StackOverflow here or just Google for the documentation and sample implementations. There's quite a bit of information out there.

Community
  • 1
  • 1
adam
  • 384
  • 2
  • 9
0

Paraphrasing https://nullprogram.com/blog/2018/06/23/

There is no way to cancel a system call once it's been initiated. However, you can modify the arguments to the system call, or its return value once it's returned. Hence, you can do something like this:

1) Wait for the traced process to invoke a system call.

2) Replace the system call number with something invalid. To do this, read the traced process's registers, modify them, and write them back to the process.

3) Continue the process; the system call will be executed, and the kernel will return an error since the process invoked an unimplemented system call.

4) When ptrace traces the return from the system call, you can optionally replace the kernel's error code with something else (e.g. replace the kernel's "unimplemented system call" with a "no permission error").

Stephen Warren
  • 240
  • 1
  • 9