As many of our regular readers will know, the Portcullis Labs team have a good deal of experience with reviewing the security of POSIX alike OS, and as a result, we’ve made some interesting discoveries in terms of how easy it can be to escalate ones privileges. As I discussed some time ago at CRESTCon, one particular avenue of attack that we like is the runtime linker itself. As part of our ongoing research, I’ve recently issued a request for comments for a patch that tackles a number of systemic weaknesses in the Linux (glibc) runtime linker that we often exploit. A few further points on the rationale…
If you take a look over our advisories page, over the last couple of years I’ve spent a good deal of time dealing with vendors who, for one reason or another have shipped binaries where it is possible to inject untrusted code into privileged processes, notably but not exclusively via DT_RPATH.
What’s the fix?
More often than not, the underlying issue is an empty element within the DT_RPATH header or equivalent. Sometimes it’s not, but even in those cases, it is largely that one or more elements isn’t qualified (i.e. it doesn’t start with /). The patch I have circulated fixes this, by causing the runtime linker to ignore any elements of DT_RPATH and LD_LIBRARY_PATH that do not start with a /, and/or junk any use of dlopen() where the filename is likewise unqualified.
Won’t this break stuff?
Maybe (certainly it is means a change to glibc behavior), but more often than not, the fact that a given binary currently works in an unsafe way is a bug – and an exploitable one at that. Moreover, Solaris has had a similar sanity check (in their case only for privileged setUID binaries) for a good number of years without serious incident. I believe we should be fixing software that exhibits the behavior I’ve described, but this patch will (I think) kill the bug class irrespective of that.
I’ve already had some useful feedback about non-privileged use cases that would be affected by the change. It would be dead easy only to use the part of the patch that rejects unsafe linker paths for setUID binaries only. However, as I have said, that offers less protection. I’d like to make the patch consumable and indeed, I’m leaning towards having $RELATIVE (new) and $ORIGIN honoured when the binary isn’t setUID.
The patch I have submitted is the most robust variant I’ve produced in that it kills unqualified linker paths, irrespective of the privileges with which the affected binary is executed. We could kill the checks for non-setUID binaries or we could add some additional errors in such cases. I did experiment with only checking a subset of cases (namely where LD_LIBRARY_PATH itself is set) if the process wasn’t privileged, but in the end, I concluded that the loading of any unqualified linker path could provide an exploit vector (if the non-setUID binary is executed from a privileged process etc) and so erred on the side of caution. A useful variant of my patch for auditors is one that logs dangerous conditions rather than reject them outright, but I’m unconvinced that it is helpful for the masses.
The next step will be (once I have digested the views of the oss-security community) to submit it directly to the glibc maintainers. Sharing it with security folk (who may well wear slightly more positive hats to begin with) seemed like a safe place to start but a fix that no one uses does not offer any real value.