Date: Thu, 28 Sep 2000 23:33:28 +0100 From: Chris Evans <chris@FERRET.LMH.OX.AC.UK> Subject: Very interesting traceroute flaw To: BUGTRAQ@SECURITYFOCUS.COM Hi, CREDIT ====== I'm starting with a credit section because I did not discover this flaw. The flaw was discovered by Pekka Savola <pekkas@netcore.fi>, who noted that traceroute could be caused to crash, which is pretty suboptimal behaviour for a suid-root program :-) I took this forward and speculate that in fact this very minor code flaw may well be exploitable. VERSIONS AFFECTED ================= (Where LBNL = Lawrence Berkeley National Laboratory) Affected: LBNL 1.4a5 Safe: LBNL 1.4a7 Safe: RedHat7.0 traceroute (1.4a5 + a patch) DISCUSSION OF FLAW ================== First, some background reading, namely Solar Designer's excellent discussion on the generic exploitation of heap overflows; http://www.securityfocus.com/archive/1/71598 The discussion shows nicely how heap mismanagement is fatal. However, overflowing a malloc()'ed buffer is not the only bad thing you can do to the heap. In the case of traceroute, there was a reliable way of making traceroute call free() on a pointer that was not obtained with malloc(). This flaw in traceroute (if your version is vulnerable) is tickled like this: traceroute -g 1 -g 1 (I think it didn't need a hostname) Segmentation fault Looking at the code, there is a file "savestr.c", which contains a function savestr(). This savestr() function is essentially a strdup() function, but with the difference that an attempt is made to cut down on the number of malloc() calls. This is accomplished by malloc()'ing a large block and handing out pointers _inside_ this block as savestr() is repeatedly called. So where does this all go wrong? Unfortunately, the clients of the savestr() method seemed to treat savestr() like it was strdup() - i.e. all pointers returned must be free()'d or you have a leak. This is not the case, so we have the flaw: free() called on a pointer not allocated by malloc(). Let's have a look at some cheesy ASCII diagram showing the block of memory allocated by savestr(): ----------------------------------------------- | | ----------------------------------------------- ^ * ^ | | address free() returned called by malloc() here (2nd and 1st savestr() savestr call call result) The reason this is so serious is that the free() call will attempt to do a bit of free chunk management. This involves memory writes. The memory writes are controlled by a "malloc chunk" descriptor, which resides just before the address returned by malloc(), and passed by free(). Looking at the above diagram, the location of the descriptor used by the faulty free() called is marked by a "*". Bad news - that's in a region of memory controlled by the malicious user. See the above link to Solar's advisory for better discussion on how control of the contents of a malloc chunk descriptor may be used to subvert a program. EXPLOIT ======= No exploit is currently known, although it is believed than an exploit could well be possible. I'm not an exploit dude, otherwise I'd have had a good go! If anyone can produce an exploit, please post it and claim lots of kudos :) A working exploit would be a chilling reminder that the slightest flaw in a security sensitive program is fatal, even if the flaw looks harmless at first glance. SMALL RANT ========== If an exploit _is_ produced, it'll gain root access. Ouch. This really should not be the case. A sensible and fault tolerant traceroute solution should allocate a raw socket and then drop privileges on startup (quite a few do this). This limits the severity of the hole to being able to gain a raw socket. Not good but infinitely better than a root compromise. RedHat-7.0 includes a patch to get a raw socket then drop privs at startup. Hopefully this will be hoovered up into the upstream traceroute source tarball. It does not appear to be in version 1.4a7 FOOD FOR THOUGHT ================ Many classic memory management flaws may be exploitable with a little effort. Of particular note, calling free() twice on the same pointer is not uncommon. This will be an exploitable flaw if, after the first free() call, the memory is re-allocated and filled with user controlled data. Cheers Chris