An analysis of the /e/OS app installer
The /e/ operating system (formerly eelo) is a privacy-focused variant of Android, forked from Lineage OS. It is part of the Murena project and is led by Gaël Duval, of Mandrake/Mandriva fame.
"Apps", the /e/ app installer, downloads applications from CleanAPK.org, an intermediary which provides apps that originate from F-Droid and elsewhere.
Since apps are not downloaded directly from F-Droid or Google Play, the installer takes certain measures to protect against tampering. Unfortunately, these measures can be bypassed in the majority of cases. This means that CleanAPK.org (or whoever compromises it) can get maliciously modified apps installed on /e/ users' devices, either when the user is installing a new app or during the update process.
Such an attack can be targeted at specific users, based on device information which the installer reveals to the CleanAPK server every time it checks for updates: the list of installed apps, device model, build ID, Android version and installed languages. If the installer is configured to install updates automatically (as is the default), CleanAPK can push apps to users' devices in the background. It can install new apps, but can not replace installed apps with different ones.
These conclusions are based on:
- intercepting connections made by a build of "Apps" 1.1.6 from April 2021;
- testing the attacks on the same build;
- an evaluation of code that has been written since to address parts of this problem.
According to the FAQ about its built-in app installer,
The /e/ app installer relies on a third-party application repository called cleanapk.org.
Apps served by cleanapk.org can be searched here.
The FAQ also states:
Apps are checked either using a PGP signature check, or a checksum.
In case you suspect an app to be tampered, we would request you to please report the same here email@example.com and we will take appropriate action keeping you informed.
I read the code that checked for tampering and found that it could be easily bypassed. On the 21st of May 2021, I reported this to /e/ with a 90 day publication deadline, which I later extended by another 60 days after finding that the initial fix did not adequately address the problem. The team tackled many of the issues I reported (/e/OS v0.19 contains the fixes), but as of today (Oct 29 2021), the fundamental problem remains unsolved and the attacks described above are still possible.
CleanAPK.org has a REST API which responds to search queries and returns metadata about available apps. For an example of how it works, let's say you search for NewPipe, tap the first result, then press "install"; the app installer would make the following API calls (I have removed unessential parameters):
The last call is made redundantly, as the same information is also included in the second call.
On the client side, the installer tries to determine the original source of the app that the user is about to install: /e/ (system app), F-Droid or Google Play. Based on this, it applies one of 3 checks:
- system apps have their signatures checked using the system's signing key;
- F-Droid apps have their signatures checked using F-Droid's PGP key;
- Google Play apps are checked by comparing their SHA1 hashes with those included in the API response; since CleanAPK provides both the files and their hashes, it can tamper with them at will.
In order to provide tamper protection for Google Play apps, the installer would need to fetch signing keys from the source, which is difficult, because Google Play does not publish them like F-Droid does. You generally can't check that the app you got from a third party is the same as on Google Play unless you also download it from Google Play.
Looking at the installer's code, we find that signature verification for F-Droid apps can also be bypassed. It used to be the case - and still is, for un-updated systems - that if the API response contained
"package_name":"com.google.android.gms", the installer assumed that the APK file was a system app (MicroG) with the same package name and did no further verification. And if the response contained a SHA1 hash of the APK file, the installer assumed that it was a Google Play app and did no signature verification. Both of these assumptions could be leveraged to install malicious apps.
Now the app installer extracts the package name from the APK file, then makes a GET request to
to determine if it is an F-Droid app or not. This means that the f-droid.org server gets to learn about every app that /e/ users are trying to install. This method also does not protect against instances where the attacker modifies the package name within the APK file. By modifying the package name, CleanAPK can induce a fallback to the
checkGoogleApp() function, which provides no tamper protection.
So, in the end, F-Droid apps from CleanAPK are not protected against tampering either. CleanAPK can make it appear as though you are installing a libre app like NewPipe and instead you would be installing a malicious version of it (or a different app entirely) with a different internal package name.
System apps are the exception to the rule: they are not downloaded from CleanAPK, but from https://gitlab.e.foundation/. The only apps that currently fall under this category are MicroG and Mail.
In Android, once an app is installed, its updates must be signed with the same key. This means that CleanAPK won't be able to get a malicious APK installed if its package name collides with that of an already installed app. However, avoiding collisions is easy, especially since CleanAPK receives a list of every user's apps.
During update checks (which occur daily, by default), the app installer sends a request to api.cleanapk.org for each installed app, with the exception of pre-installed system apps (like the installer itself). Each request looks something like this:
User-Agent: Dalvik/2.1.0 (Linux; U; Android 8.0.0; SM-J337U Build/R16NW)
The User-Agent header contains the device model, build ID and Android version. Installed languages are also revealed to CleanAPK via the Accept-Language header. The information from these headers, along with the IP address and the list of installed apps, can be used to uniquely identify most /e/OS devices. Aside from privacy, this also affects security, since device fingerprinting can enable targeted attacks. A compromised CleanAPK.org can quite easily target a specific device to install malware on.
My suggestions to /e/OS developers regarding the app installer:
- Download the entire package index in advance, like F-Droid does;
- barring 1, the k-anonymity method used in https://haveibeenpwned.com/Passwords might also work for privacy-friendly updates;
- download apps directly from the source;
- barring 3, download vetted signing certificate fingerprints from a server other than CleanAPK's; an initial list of fingerprints can be bundled with the installer, like the F-Droid PGP key currently is;
- check that app metadata from the API matches the actual app;
- don't send the User-Agent header;
- don't send the Accept-Language header; it is currently used to get application metadata in the right language, but the CleanAPK API could be changed to return metadata in all available languages at once. If compression is used, the size of such responses should be manageable.
Also, use a better hash function than SHA1. SHA256, for instance.
As mentioned, if the installer is configured to install updates automatically (which it is, by default), CleanAPK can push malicious apps to users' devices in the background. Also, the installer sends the list of installed apps and other device data to CleanAPK every time it checks for updates. The "Apps" app cannot be uninstalled or disabled from the Android UI and there is no obvious way to disable update checks. Still, there are ways for users to limit the reach of the /e/ app installer:
- Update check interval: Monthly
- Automatically install updates: uncheck
Android Settings > Apps > Apps:
> FORCE STOP
- Storage: uncheck
> Data usage:
- Disable all cellular data access: check
- Disable all Wi-Fi data access: check
- Disable all VPN data access: check
Alternatively, users can uninstall or disable "Apps" (and other system apps) from the command line:
adb shell pm uninstall --user 0 foundation.e.apps
adb shell cmd package install-existing foundation.e.apps
# If the above does not work, try:
adb shell pm install -r --user 0 /system/app/Apps/Apps.apk
adb shell pm disable-user --user 0 foundation.e.apps
adb shell pm enable foundation.e.apps
You can also block internet connections for /e/ "Apps" using a firewall such as NetGuard. Or you can block connections to CleanAPK by enabling adb root in the developer options and adding the following to
F-Droid and Aurora Store can be used instead to give you access to pretty much the same broad range of apps, but directly from the source. You can install F-Droid from the official website and Aurora from its official website or from F-Droid.
/e/ "Apps" provides an embedded privacy score (calculated by CleanAPK) and list of trackers (detected by Exodus Privacy). However, the way in which CleanAPK calculates the privacy score is not clearly documented and I found it to be misleading at times (for instance, Tor Browser has a lower privacy score than Facebook Lite). Aurora Store also provides a list of trackers detected by Exodus Privacy. As for F-Droid, it lets you know when an app tracks and reports your activity, which is determined using a computer-assisted human review in which an Exodus Privacy report is taken into account. F-Droid checks for other anti-features as well.
Other than F-Droid, it is not clear what other source(s) CleanAPK uses. But there is at least one sign that it uses APKPure.com:
This API call returns metadata about the Uber app. The response contains:
"apk_App uploaded by:":"Mòbile Soe". The APKPure page for Uber also contains
"App uploaded by: Mòbile Soe". The same is also true for other proprietary apps served by CleanAPK, but not all.
If APKPure is used as a source, then we're relying on two intermediaries, not just CleanAPK. So it's even more important for the app installer to have proper tamper protections in place.
Maybe CleanAPK also gets apps from Google Play directly, or maybe not. It would be good to have transparency on this.
I can't shake the sense that the /e/ team is stretched too thin. The scope of the /e/ project is huge: maintaining online services, maintaining their own apps and forks, supporting around 200 devices, etc. They have an enormous amount of work on their hands. So, even though it is important, I can't say I'm surprised by the lack of care given to app verification, nor by other signs of rushed work.
But since /e/ is focused on privacy, I am somewhat surprised by the data it leaks. You would think that, before claiming privacy, such a project would analyze and disclose its own network connections. But this is not the first time /e/ has been shown to fail in this regard. Users deserve to know exactly what data is sent where and who can access it.
That being said, let's not lose sight of the fact that /e/ is significantly more privacy-respecting than the average Android system. Also, the problems I have described can be solved and based on my communications with the team, it appears they will be.