Wednesday, 28 July 2010

Grsecurity, Firefox, MPROTECT and You!

Firefox is definitely one of this pieces of software where you want to have all of the available security options enabled :) And one of those features that you might REALLY want is MPROTECT (brought to you by the grsecurity ;)). Making long story short, it works by restricting the mprotect() system call which makes life of an attacker much more difficult because they cannot simply change protection of a specific memory region (mark it as executable if it wasn't originally executable) or create a new writeable&executable memory mapping using the mmap() call. Without this feature, all the 'non-executable memory regions' hype in your system is more or less useless, as the permission could be simply changed by the attacker. So far so good :)

However, you can notice that Gentoo by default disables this protection (without really telling user why!) as it usually wreaks havoc with flash and java plugins and sometimes the browser itself...but what if you don't really care about these and you do want to have this on? Well, it gets more interesting...historically, Firefox had issues with MROTECT every now and then, so how does it look like with the latest 3.6.8 release?

During the emerge process you can see this:

* Legacy EI PaX marking -m
* /var/tmp/portage/www-client/firefox-3.6.8/image///usr/lib64/mozilla-firefox/firefox
* PT PaX marking -m
* /var/tmp/portage/www-client/firefox-3.6.8/image///usr/lib64/mozilla-firefox/firefox

Which can be also confirmed by using the paxctl tool:

# paxctl -v /usr/lib64/mozilla-firefox/firefox
PaX control v0.5
Copyright 2004,2005,2006,2007 PaX Team

- PaX flags: -----m-x-e-- [/usr/lib64/mozilla-firefox/firefox]
MPROTECT is disabled
RANDEXEC is disabled
EMUTRAMP is disabled

Not really what we wanted...let's reset it! (you need to run this command as root)

# paxctl -z /usr/lib64/mozilla-firefox/firefox
# paxctl -v /usr/lib64/mozilla-firefox/firefox
PaX control v0.5
Copyright 2004,2005,2006,2007 PaX Team

- PaX flags: ------------ [/usr/lib64/mozilla-firefox/firefox]

That's better! Ok, but does it actually work?

$ firefox
Segmentation fault

Ooops...not good! So what's the problem? Last few lines of strace output show the answer:

mmap(NULL, 65536, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 EPERM (Operation not permitted)
mprotect(0xfffffffffffff000, 69632, PROT_READ|PROT_WRITE|PROT_EXEC) = -1 ENOMEM (Cannot allocate memory)
--- SIGSEGV (Segmentation fault) @ 0 (0) ---
unlink("/home/radegand/.mozilla/firefox/ekyjebvs.default/lock") = 0
rt_sigaction(SIGSEGV, {SIG_DFL, [], SA_RESTORER, 0x3434af56120}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [SEGV], NULL, 8) = 0
tgkill(26608, 26608, SIGSEGV) = 0
--- SIGSEGV (Segmentation fault) @ 0 (0) ---
+++ killed by SIGSEGV +++
Segmentation fault

The mmap() call above tries to create a writeable and executable memory mapping (which is not permitted by Grsecurity) which is followed by a call to mprotect() to set the same permission ('rwx'), which is also not permitted...so then it segfaults...:)

Luckily, there's a workaround! From information gathered on IRC, the following two options would allow Firefox to run with the MPROTECT feature on:

user_pref("javascript.options.jit.chrome", false);
user_pref("javascript.options.jit.content", false);

They should be added in the prefs.js file located in the Firefox profile folder:

/home/[your_user]/.mozilla/firefox/[random_string].default/

...and then Firefox will run happily ever after...or so one would hope... ;)

Of course you could disable MPROTECT, start Firefox, go to about:config and add the options above. But do you really want to run your browser without this protection, even just for few seconds? ;) Well, if JIT really needs 'rwx' pages, given for instance this issue, I'm not sure if I would... ;P

Ok, but what is the real cause of the problem? Let's see...unpack the source code and have a poke around - Gentoo ebuild system comes handy here:

# ebuild /usr/portage/www-client/firefox/firefox-3.6.8.ebuild unpack
# cd /var/tmp/portage/www-client/firefox-3.6.8/work/
work # grep -R -i PROT_EXEC *
mozilla-1.9.2/js/ctypes/libffi/src/closures.c: don't attempt PROT_EXEC|PROT_WRITE mapping at all, as that
mozilla-1.9.2/js/ctypes/libffi/src/closures.c: ptr = mmap (NULL, length, (prot & ~PROT_WRITE) | PROT_EXEC,
mozilla-1.9.2/js/ctypes/libffi/src/closures.c: ptr = mmap (start, length, prot | PROT_EXEC, flags, fd, offset);
mozilla-1.9.2/js/ctypes/libffi/src/closures.c: with ((prot & ~PROT_WRITE) | PROT_EXEC) and mremap with
mozilla-1.9.2/js/ctypes/libffi/testsuite/libffi.call/ffitest.h: page = mmap (NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
mozilla-1.9.2/js/ctypes/libffi/testsuite/libffi.call/ffitest.h: page = mmap (NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
mozilla-1.9.2/js/ctypes/libffi/testsuite/libffi.special/ffitestcxx.h: page = mmap (NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
mozilla-1.9.2/js/ctypes/libffi/testsuite/libffi.special/ffitestcxx.h: page = mmap (NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
mozilla-1.9.2/js/src/nanojit/avmplus.cpp: flags |= PROT_EXEC;
mozilla-1.9.2/js/src/nanojit/avmplus.cpp: PROT_READ | PROT_WRITE | PROT_EXEC,
mozilla-1.9.2/layout/base/tests/TestPoisonArea.cpp: return mprotect((caddr_t)page, PAGESIZE, PROT_READ|PROT_WRITE|PROT_EXEC);
mozilla-1.9.2/layout/base/tests/TestPoisonArea.cpp: // (mmap(PROT_EXEC) may fail when applied to anonymous memory.)
mozilla-1.9.2/nsprpub/lib/msgc/src/unixgc.c: addr = mmap(lastaddr, size, PROT_READ|PROT_WRITE|PROT_EXEC,
mozilla-1.9.2/nsprpub/lib/msgc/src/unixgc.c: addr = mmap(base + oldSize, allocSize, PROT_READ|PROT_WRITE|PROT_EXEC,
mozilla-1.9.2/nsprpub/pr/src/md/unix/nextstep.c: case PROT_EXEC: mach_prot = VM_PROT_EXECUTE; break;
mozilla-1.9.2/nsprpub/pr/src/md/unix/unix.c: prot |= PROT_EXEC;
mozilla-1.9.2/toolkit/crashreporter/google-breakpad/src/google_breakpad/common/minidump_exception_mac.h: /* EXC_I386_EXTOVRFLT = 9: mapped to EXC_BAD_ACCESS/(PROT_READ|PROT_EXEC) */

So here are your potential offenders...The next step would be to investigate the code further and try to remove the PROT_EXEC flag where it does not seem necessary, compile and test...believe it or not, it's sometimes much easier than it sounds!

...but more about it next time...