State of the Onion @ iOS
In this post, I describe my work for Onion Browser for iOS and my research regarding the iOS Network Extension API and if it can be made usable for Tor and Onion Browser.
TL/DR: Jump straight to the Conclusion at the end of this post!
Last year I was honored to get hired by the The Guardian Project to support Mike Tigas with version 2 of his Onion Browser project.
Onion Browser is a Tor-enabled browser for iOS. Download it on the App Store.
It supports different bridging techniques and has a bunch of additional security features.
I’m very proud of being a part of this team and helping along with this project, as it is aimed esp. towards helping people in countries where freedom of expression is limited. The goal is to actually make a dent in improving people’s security online, and I believe that we definitely achieved this goal.
While working on it, I got the task of looking into the state of iOS‘ relatively new (and heavily underdocumented) feature called „Network Extension“ to find out, if it already is in a state where we can use it for Onion Browser.
The basic idea of Network Extension is to have a unified API, where apps can provide a system-wide tunnel for all network traffic. This is especially aimed at VPNs but can actually be used for any kind of network tunnel like SOCKS proxies and systems based on similar technologies.
For example, there already are working implementations of ShadowSocks clients and a Psiphon client.
ShadowSocks is a secure SOCKS5 proxy system trying to circumvent censorship, mainly developed out of China. Unfortunately, the Chinese government currently seems to clamp down on these especially, and Apple willingly helps them in order to not get kicked out of the Chinese market completely. So the apps on the App Store are constantly changing.
Psiphon also is a proxy system combining different techniques to obfuscate and tunnel traffic out of censored areas.
Unfortunately, no one has managed to create a fully functioning Tor Network Extension app (aka. „VPN“ as it is displayed in the UI), which would have quite a few more benefits than the above mentioned apps, besides obfuscating and tunneling traffic.
Why that is so, I will show in the following sections. (Yes, there is one app, which seems to be one, but I’ll explain later, why that is not really the case.)
Tor, WHY U NO NE?
Thankfully, I didn’t have to start from scratch: A more capable guy than me already put some work into this: Conrad Kramer started a project called iCepa, which tries to achieve what we need: A network extension which provides a system-wide Tor tunnel.
The name is a play on Tor, since „cepa“ actually means „onion“ in latin.
Unfortunately, the project wasn’t quite ready, yet, and since Conrad now works for Apple, it seems he doesn’t have time to further that project anymore. That’s where I came into play.
I fixed some minor issues and made it run just to find out quite fast, that there’s a very hard limit to Network Extensions of 15 MByte RAM usage.
Let’s jump into details: All so called „Extensions“ to apps are actually sort of apps-in-an-app. There are a multitude of possible extensions, the most basic one being Share Extensions. An extension is actually its own app bundle, which is bundled with the main app. It runs in its own process, completely disconnected from the app and only has some very limited ways to interact with the app. There is a very limited message passing feature and the possibility to use some shared file storage. (Which actually wasn’t invented for extensions, but can also be used with multiple apps from the same vendor to share common data, e.g. user credentials.)
iOS, Apps and Processes
For clarity: Processes are a scarce resource on iOS and can’t be managed by apps themselves: The OS gives exactly one process to an app, there’s no IPC allowed between processes except the high-level mechanisms given to the app via the provided APIs and apps cannot start their own processes. This is a limitation, which hits Tor quite hard on iOS, not only in Network Extensions but in every Tor app, since it heavily relies on having its own process, which we simply cannot do in this environment.
Fortunately, Conrad Kramer again did work to alleviate some of that pain by creating Tor.framework, which actually starts Tor inside a Thread. There’s other libraries which do that, but, Tor.framework is, to my knowledge, the one which is currently under most active development – by Mike Tigas and me. What Tor.framework however, cannot help with, is the fact, that Tor actually expects to run in its own process and does not have any cleanup or restart facilities, which could be used and triggered from outside. Currently, you are expected to just shut down the Tor process and restart it, which, on iOS, only the user can do, by pushing the app card out of the app overview (seen when double-clicking the home button) and just start the app again. This is a huge drawback in terms of usability, and something, which Onion Browser suffers from, too. We’re working closely with the Tor people to change this and I hope it will be available in Tor 0.3.4.
Memory Limit
But, back to Network Extensions: These were introduced in iOS 8. Since they are allowed to continuously run in the background, which is quite the opposite for normal apps, they come with a limit on how much memory they can use. At first, this was 5 MByte, which, for a lot of applications, just was unusably little. With iOS 10, they raised this limit to 15 MByte, which now looks actually usable.
Hearsay from people inside Apple tells us, that this limit is actually hardcoded quite deeply, so there’s not much point in begging Apple to make an exception and raise this limit for your special app.
It’s also interesting, how this limit is actually calculated: it seems, the algorithm to do this, is actually quite advanced: It does not just dumbly count all RAM allocated, but only memory, which is not backed by file storage and which is not clearly transient, counts towards this limit.
The component handling this is called Jetsam.
This means: If you load data from a file (which you could have just fetched from a network service) in the right way, that data is not counted towards your limit. You should make sure, however, that you can give up that memory intermittently in high pressure situations and reload later, when you need it again.
Unfortunately, this is not, how Tor currently works. It turns out, after some debugging sessions, that iCepa just can’t run stable, because Tor quite quickly exceeds the memory limit: The maximum I could do was to browse 3 web pages in a row. After that, iOS killed the Network Extension due to memory overuse. Sometimes, I didn’t even reach the first page – Tor already got too big during startup.
Fail Open
The worst thing about this is, that network extensions fail open: Unfinished requests are repeated immediately after extension shutdown over the normal connection. This is horrible from a security perspective: Imagine, you live in Iran and surf a gay community site, maybe even just accidentally. Suddenly, the Network Extension drops and your request for a nude image is repeated over the normal internet, where you hit the morality police’s transparent proxy servers. Your chances of getting arrested and tortured suddenly become quite high. I don’t know what Apple actually led to a design decision like this, but I would argue, they should have thought about this for a little while longer.
There is no configuration option, be it from inside the extension nor some UI elements in the Settings app, to define that a network extension has to fail closed. In other words, that the normal network is off limits for a device. The only possible way to achieve this, if you get your device under device management, which basically applies for organisations only, and even then, this only applies to built-in standard VPNs, not for Network Extensions of other apps. You could, theoretically, do this as a private person, but the hoops to jump through, are way too many for a normal user, and, even then, they still just could use standard VPN servers, not Tor. Heck, even I don’t use stuff like that, because it’s just plainly annoying.
So now, the question was, how much memory does Tor possibly use, why and how to improve that:
I found out that, when you use Xcode’s Instruments profiler, the memory limit does not apply. So I could see, what Tor’s upper limit of memory usage would be. After some trials, it turned out that Tor (resp. the iCepa Network Extension running it) normally consumes around 25 MByte, but sometimes reaches up to 60 MByte. So, there seems quite some work to do.
After some discussions with Tor developers, we found out the main reason, why Tor uses so much memory: The microdescriptors, which are an already reduced version of a data structure describing a Tor node and its capabilities, are the main memory hog. When Tor starts up, it tries to learn about the Tor network to create cascades of nodes which it can use as routes. For every node server, a Tor instance learns about, a microdescriptor is stored in RAM. Unfortunately, this is in no way backed by file storage, nor is it just some transient cache. Quite the opposite: It’s essential information Tor needs to run to build routes. Depending on the current state of the Tor network, this leads to quite some memory consumption and will lead to even more, when the network becomes bigger.
We’re in discussion with the Tor people – in fact, while writing this, I’m on my way to a Tor meeting – to change this, but this will take some more moons until we will have a solution, which, obviously, will be, that this data is going to be stored on the file system. Interestingly, Tor does that already, but the data structure stored on disk and the structure held in RAM are quite different and are currently not used in a way, that Jetsam would recognize as disk backed.
iOS Processes Can Communicate via the Network Stack
After this findings, n8fr8 had the idea, to keep Tor inside the app, and not have it in the Network Extension. That was quite interesting, since, as it turns out, iOS actually doesn’t prevent processes talking to each other via the network stack!
You can, in fact, have a Network extension running tun2tor only, and have the app run Tor and these two components actually can still talk to each other.
Before you get crazy ideas, though, keep in mind, that apps normally aren’t able to run side-by-side on iOS: An app gets suspended quite fast, when the user puts it in the background. This also is the biggest drawback of that solution: You actually can’t have a system-wide Tor tunnel, since, when you click the main app away, Tor’s thread gets suspended and its sockets are being taken away, so the Network Extension part suddenly talks into empty space. The upside is, that we now fail closed, since the Network Extension still runs and effectively becomes a sinkhole for all traffic on the device.
What is tun2tor?
If only… unfortunately, that’s not the end of the story. There’s one component to all of this, which I only just mentioned: tun2tor. In order to get the device traffic, which iOS provides to a Network Extension on a tun device into Tor, which uses a SOCKS5 interface, we need an additional component: tun2tor, which was also developed by Conrad Kramer. It is written in Rust and is very alpha. That means, that it also crashes sometimes, or should I rather say quite often, which makes our Network Extension exit, which makes the network fail open, again. D’oh.
Unfortunately, I don’t have real knowledge of low-level networking stuff, nor of Rust, so I’m not sure, if I should dive into this. If you know your way around this – please contact me or The Guardian Project!
BTW: There’s already an app called Red Onion, which actually managed to mangle all of this together using a Network Extension. And it very much looks like it has Tor running inside the app, instead of the Network Extension. Unfortunately, it’s closed source and the developer seems not interested in talking to us.
Other concerns
Pluggable Transports/Proxies
Pluggable Transports or older means of proxying and obfuscation are tools to circumvent measures, which try to censor the Tor network.
The future on supporting these inside Network Extensions is looking rather bleak, though. Even when we manage to have Tor running stable, it doesn’t look like, that we will have enough RAM left for adding yet another component. Remember, that we definitely have to keep some breathing space towards the 15 MByte limit, to not accidentally expose the user.
Until that isn’t increased, I currently don’t see room for these.
WKWebView vs. UIWebView
When building browser apps, there’s the issue with the different UI elements, which you can currently use for browsing: The older UIWebView and the newer WKWebView.
In Onion Browser, we currently use UIWebView, since WKWebView actually doesn’t allow you to hook into the traffic anymore and redirect it to our Tor thread.
In WKWebView this can only be achieved using a Network Extension.
So, as long as we can’t use that, we are stuck with UIWebView and can’t have all the new features of WKWebView.
You could argue, that you could just use any browser, when we finally manage to have Tor run inside a Network Extension in a stable fashion. However, there’s still going to be a need for dedicated Tor browsers, similarly as on desktop OSes, as other browsers often miss advanced features for scrubbing traffic which otherwise could reveal your identity.
Conclusion
To conclude this post, here are the takeaways:
- Tor currently consumes too much memory to run inside a Network Extension, main reason are the microsdescriptors. The remedy for this is to disk back that data in a way, so that Jetsam recognizes it as such and doesn’t count that memory against the hard 15 MByte limit. Code for this is on its way, but will probably take until the end of the year to get released. At least.
- tun2tor needs more work so it becomes stable, or it has to be replaced by something else. This other thing shouldn’t consume much memory itself, to not cut into the budget for Tor too much. iOS native code would be a plus.
- There’s absolutely no wiggle room for broken code or memory leaks, since Network Extensions fail open and therefore would potentially expose users to life threats. Since error-free code is basically unachievable, that’s quite some burden.
- Tor needs more work in regard to running as a thread inside a host process. This is underway and will be released in the near future.
- No Network Extension, no WKWebView support.