Are Xiaomi browsers spyware? Yes, they are…

In case you missed it, there was a Forbes article on Mi Browser Pro and Mint Browser which are preinstalled on Xiaomi phones. The article accuses Xiaomi of exfiltrating a history of all visited websites. Xiaomi on the other hand accuses Forbes of misrepresenting the facts. They claim that the data collection is following best practices, the data itself being aggregated and anonymized, without any connection to user’s identity.

TL;DR: It is really that bad, and even worse actually.

Update (2021-03-06): It has been close to a year since I wrote this article. In this time Xiaomi did little to address this issue. On the other hand, a similar issue has been discovered in Xiaomi’s payment app, which would even transmit account data to the “analytics” servers. So it can be assumed that other Xiaomi apps are similarly compromised.

If you’ve been following my blog for a while, you might find this argumentation familiar. It’s almost identical to Avast’s communication after they were found spying on the users and browser vendors pulled their extensions from add-on stores. In the end I was given proof that their data anonymization attempts were only moderately successful if you allow me this understatement.

Given that neither the Forbes article nor the security researchers involved seem to provide any technical details, I wanted to take a look for myself. I decompiled Mint Browser 3.4.0 and looked for clues. This isn’t the latest version, just in case Xiaomi already modified to code in reaction to the Forbes article. Update (2020-05-08): If you don’t need the technical explanation, the newer article gives an overview of the issue.

Disclaimer: I think that this is the first time I analyzed a larger Android application, so please be patient with me. I might have misinterpreted one thing or another, even though the big picture seems to be clear. Also, my conclusions are based exclusively on code analysis, I’ve never seen this browser in action.

The general analytics setup

The Forbes article explains that the data is being transmitted to a Sensors Analytics backend. The Xiaomi article then provides the important clue: sa.api.intl.miui.com is the host name of this backend. They then go on explaining how it’s a server that Xiaomi owns rather than a third party. But they are merely trying to distract us: if sensitive data from my browser is being sent to this server, why would I care who owns it?

We find this server name mentioned in the class miui.globalbrowser.common_business.g.i (yes, some package and class names are mangled). It’s used in some initialization code:

final StringBuilder sb = new StringBuilder();
sb.append("https://sa.api.intl.miui.com/sa?project=global_browser_mini&r=");
sb.append(A.e);
a = sb.toString();

Looking up A.e, it turns out to be a country code. So the i.a static member here ends up holding the endpoint URL with the user’s country code filled in. And it is being used in the class’ initialization function:

public void a(final Context c) {
    SensorsDataAPI.sharedInstance(this.c = c, i.a, this.d);
    SensorsDataAPI.sharedInstance().identify(com.xiaomi.mistatistic.sdk.e.a(this.c));
    this.c();
    this.d();
    this.e();
    this.b();
}

The Sensors Analytics API is public, so we can look up the SensorsDataAPI class and learn that the first sharedInstance() call creates an instance and sets its server URL. The next line calls identify() setting an “anonymous ID” for this instance which will be sent along with every data point, more on that later.

The call to this.c() is also worth noting as this will set a bunch of additional properties to be sent with each request:

public void c() {
    final JSONObject jsonObject = new JSONObject();
    jsonObject.put("uuid", (Object)com.xiaomi.mistatistic.sdk.e.a(this.c));
    int n;
    if (H.f(miui.globalbrowser.common.a.a())) {
        n = 1;
    }
    else {
        n = 0;
    }
    jsonObject.put("internet_status", n);
    jsonObject.put("platform", (Object)"AndroidApp");
    jsonObject.put("miui_version", (Object)Build$VERSION.INCREMENTAL);
    final String e = A.e;
    a(e);
    jsonObject.put("miui_region", (Object)e);
    jsonObject.put("system_language", (Object)A.b);
    SensorsDataAPI.sharedInstance(this.c).registerSuperProperties(jsonObject);
}

There we have the same “anonymous ID” sent as uuid parameter, just in case. In addition, the usual version, region, language data is being sent.

For me, it wasn’t entirely trivial to figure out where this class is being initialized from. Turns out, from class miui.globalbrowser.common_business.g.b:

public static void a(final String s, final Map<String, String> map) {
    a(s, map, true);
}

public static void a(final String s, final Map<String, String> map, final boolean b) {
    if (b) {
        i.a().a(s, map);
    }
    miui.globalbrowser.common_business.g.d.a().a(s, map);
}

So the miui.globalbrowser.common_business.g.b.a() call will set the third parameter to true by default. This call accesses a singleton miui.globalbrowser.common_business.g.i instance (will be created if it doesn’t exist) and makes it actually track an event (s is the event name here and map are the parameters being sent in addition to the default ones). The additional miui.globalbrowser.common_business.g.d.a() call triggers their MiStatistics analytics framework which I didn’t investigate.

And that’s it. We now have to find where in the code miui.globalbrowser.common_business.g.b class is used and what data it receives. All that data will be sent to Sensors Analytics backend regularly.

How anonymous is that ID?

Looking up com.xiaomi.mistatistic.sdk.e.a() eventually turns up ID generation code very close to the one cited in the Xiaomi blog post:

public static String d(final Context context) {
    if (!TextUtils.isEmpty((CharSequence)y.g)) {
        return y.g;
    }
    final long currentTimeMillis = System.currentTimeMillis();
    final String a = L.a(context, "anonymous_id", "");
    final long a2 = L.a(context, "aigt", 0L);
    final long a3 = L.a(context, "anonymous_ei", 7776000000L);
    if (!TextUtils.isEmpty((CharSequence)a) && currentTimeMillis - a2 < a3) {
        y.g = a;
    }
    else {
        L.b(context, "anonymous_id", y.g = UUID.randomUUID().toString());
    }
    L.c(context, "aigt", currentTimeMillis);
    return y.g;
}

The L.a() call is retrieving a value from context.getSharedPreferences() with fallback. L.b() and L.c() calls will store a value there. So Xiaomi is trying to tell us: “Look, the ID is randomly generated, without any relation to the user. And it is renewed every 90 days!”

Now 90 days are a rather long time interval even for a randomly generated ID. With enough data points it should be easy to deduce the user’s identity from it. But there is another catch. See that aigt preference? What is its value?

The intention here seems to be that aigt is the timestamp when the ID was generated. So if that timestamp deviates from current time by more than 7776000000 milliseconds (90 days) a new ID is going to be generated. However, this implementation is buggy, it will update aigt on every call rather than only when a new ID is generated. So the only scenario where a new ID will be generated is: this method wasn’t called for 90 days, meaning that the browser wasn’t started for 90 days. And that’s rather unlikely, so one has to consider this ID permanent.

And if this weren’t enough, there is another catch. If you look at the SensorsDataAPI class again, you will see that the “anonymous ID” is merely a fallback when a login ID isn’t available. And what is the login ID here? We’ll find it being set in the miui.globalbrowser.common_business.g.i class:

public void b() {
    final Account a = miui.globalbrowser.common.c.b.a(this.c);
    if (a != null && !TextUtils.isEmpty((CharSequence)a.name)) {
        SensorsDataAPI.sharedInstance().login(a.name);
    }
}

That’s exactly what it looks like: a Xiaomi account ID. So if the user is logged into the browser, the tracking data will be connected to their Xiaomi account. And that one is linked to the user’s email address at the very least, probably to other identifying parameters as well.

What is being collected?

As mentioned above, we need to look at the places where miui.globalbrowser.common_business.g.b class methods are called. And very often these are quite typical for product analytics, for example:

final HashMap<String, String> hashMap = new HashMap<String, String>();
if (ex.getCause() != null) {
    hashMap.put("cause", ex.getCause().toString());
}
miui.globalbrowser.common_business.g.b.a("rv_crashed", hashMap);

So there was a crash and the vendor is notified about the issue. Elsewhere the data indicates that a particular element of the user interface was opened, also very useful information to improve the product. And then there is this in class com.miui.org.chromium.chrome.browser.webview.k:

public void onPageFinished(final WebView webView, final String d) {
    ...
    if (!this.c && !TextUtils.isEmpty((CharSequence)d)) {
        miui.globalbrowser.common_business.g.b.a("page_load_event_finish", "url", this.a(d));
    }
    ...
}

public void onPageStarted(final WebView webView, final String e, final Bitmap bitmap) {
    ...
    if (!this.b && !TextUtils.isEmpty((CharSequence)e)) {
        miui.globalbrowser.common_business.g.b.a("page_load_event_start", "url", this.a(e));
    }
    ...
}

That’s the code sending all visited websites to an analytics server. Once when the page starts loading, and another time when it finishes. And the Xiaomi blog post explains why this code exists: “The URL is collected to identify web pages which load slowly; this gives us insight into how to best improve overall browsing performance.”

Are you convinced by this explanation? Because I’m not. If this is all about slow websites, why not calculate the page load times locally and transmit only the slow ones? This still wouldn’t be great for privacy but an order of magnitude better than what Xiaomi actually implemented. Xiaomi really needs to try harder if we are to assume incompetence rather than malice here. How was it decided that sending all visited addresses is a good compromise? Was privacy even considered in that decision? Would they still make the same decision today? And if not, how did they adapt their processes to reflect this?

But there are far more cases where their analytics code collects too much data. In class com.miui.org.chromium.chrome.browser.omnibox.NavigationBar we’ll see:

final HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("used_searchengine", com.miui.org.chromium.chrome.browser.search.b.a(this.L).f());
hashMap.put("search_position", miui.globalbrowser.common_business.g.e.c());
hashMap.put("search_method", miui.globalbrowser.common_business.g.e.b());
hashMap.put("search_word", s);
miui.globalbrowser.common_business.g.b.a("search", hashMap);

So searching from the navigation bar won’t merely track the search engine used but also what you searched for. In the class miui.globalbrowser.download.J we see for example:

final HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("op", s);
hashMap.put("suffix", s2);
hashMap.put("url", s3);
if (d.c(s4)) {
    s = "privacy";
}
else {
    s = "general";
}
hashMap.put("type", s);
b.a("download_files", hashMap);

This isn’t merely tracking the fact that files were downloaded but also the URLs downloaded. What kind of legitimate interest could Xiaomi have here?

And then this browser appears to provide some custom user interface for YouTube videos. Almost everything is being tracked there, for example in class miui.globalbrowser.news.YMTSearchActivity:

final HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("op", "search");
hashMap.put("search_word", text);
hashMap.put("search_type", s);
hashMap.put("page", this.w);
miui.globalbrowser.common_business.g.b.a("youtube_search_op", hashMap);

Why does Xiaomi need to know what people search on YouTube? And not just that, elsewhere they seem to collect data on what videos people watch and how much time they spend doing that. Xiaomi also seems to know what websites people have configured in their speed dial and when they click those. This doesn’t leave a good impression, could it be surveillance functionality after all?

Conclusions

If you use Mint Browser (and presumably Mi Browser Pro similarly), Xiaomi doesn’t merely know which websites you visit but also what you search for, which videos you watch, what you download and what sites you added to the Quick Dial page. Heck, they even track which porn site triggered the reminder to switch to incognito mode! Yes, if Xiaomi wants anybody to believe that this wasn’t malicious they have a lot more explaining to do.

The claim that this data is anonymized cannot be maintained either. Even given the random user ID (which appears to be permanent by mistake) deducing user’s identity should be easy, we’ve seen it before. But they also transmit user’s Xiaomi account ID if they know it, which is directly linked to the user’s identity.

Xiaomi now announced that they will turn off collection of visited websites in incognito mode. That’s a step in the right direction, albeit a tiny one. Will they still collecting all the other data in incognito mode? And even if not, why collect so much data during regular browsing? What reason is there that justifies all these privacy violations?

Update (2020-05-07): I looked into the privacy-related changes implemented in Mint Browser 3.4.3. It’s was a bigger improvement than what it sounded like, the “statistics” collection functionality can be disabled entirely. However, you have to make sure that you have “Incognito Mode” turned on and “Enhanced Incognito Mode” turned off – that’s the only configuration where you can have your privacy.

Comments

  • Anonymous

    This is actually quite disturbing to know. These Chinese firms are getting deeper into tech space. Hope countries would join hands and penalize these Chinese firms, which might be working on behest of China's government.

    Wladimir Palant

    I’m afraid that this isn’t limited to Chinese companies. See Avast for example who were happily pulling the same trick until the criticism got too loud to ignore.

  • Anony

    Is this surprising? Of course no. Are only Chinese firms so greedy about collecting personal data? Of course no. We are not criticizing US companies because "we are all living in America" and that data it is required for international peace! Oh world police, please take my data and continue defending me against those terrorists spawning in middle east and asian deserts.

    Wladimir Palant

    I’m tired of whataboutism and I’d be grateful if you could stay away from my blog. I don’t live in the US, and I’m not criticizing Xiaomi because they are a Chinese company – I’m criticizing them because they are doing bad things. Do you have proof of a US company doing something similar? I’ll be happy to verify your results and help you spread the word. But if you don’t have anything of value to contribute, please just stay quiet.

  • snowden

    Is it not enough to you as a proof of a US company doing something similar? What about this one?

    Wladimir Palant

    These stories have been shared wide enough already and don’t need my help. Is your idea that I shouldn’t write anything about issues in apps by non-US companies as long as there are still US companies doing shady things? Or are you merely implying that I never criticize US companies? If it’s the latter, I invite you to take a better look at my blog – I do, plenty.

  • Anonymous2020

    Can this be some kind of cloud based services they run for things you haven't been considered? Eg. Lots other browsers send the URL and the device type to their server to check potential cyber threats.

    Have you approached XiaoMi for their responses?

    If you do a packet by packet analysis on lots other browsers (Chrome, Safari etc..). They all somehow use cloud based service to do url suggestion / correction etc.. which they require the current URL to be sent to those services.

    My main point is, that you jumped into your conclusions too quickly.

    Wladimir Palant

    I did not have to approach Xiaomi, Forbes did that already. And Xiaomi published their response. Which explains: no, there is no reason for this functionality beyond “insight” and “product improvement”. And besides, it’s legal.

    They also implemented a “fix” for the issue. If you switch “Incognito mode” on and “Enhanced incognito mode” off then no data will be collected. 🤡

    No, I did not jump to conclusions too quickly. I know very well what kind of data other browsers send out. This is nothing like that.

  • carpe diem

    Are Xiaomi browsers spyware? They are, same as: Google Chrome, Mozilla, Facebook, Facebook Messenger, instagram, Apple Store, Amazon, MacOS, Windows etc (in one word everyone). Basically any app that has access to any of your information, collects your information. All the companies collecting the data swear that they will not misuse this data (and that is pretty much all we can hope for). Not sure why you are pinpointing Xiaomi's not very popular browser, you are making it sound like it is doing something unheard of. People don't really care (at least large majority of people).

    Wladimir Palant

    As I said above, I’m tired of whataboutism. Please read the article (better this one, without technical details) and think again: what other browser invades your privacy on the same level? And since you don’t seem to know: no, Google Chrome certainly has its privacy issues but it is nowhere near this level of creepy. Mozilla Firefox has its faults as well by they are still the good guys trying to do the right thing. I’ve been dealing with browsers a lot, and so far Avast Secure Browser has been the only one doing something similar.

    The other products you mention operate on a totally different level and are not comparable.

  • jUnit

    I'm not sure how Java operates around variable reassignment, but I noticed that this.c = c is the first argument to the original call to SensorsDataAPI.sharedInstance, which (in the languages I tend to use) would make this.c retain the value of whatever was passed to this class's initializer as final Context c.

    Those calls to this.c could be calls to something else entirely, is what I'm getting at, basically

    Wladimir Palant

    No, in Java a field and a method can have the same name which makes it unlike JavaScript or Python. So the field this.c assigned here has no relation to the method this.c() being called.

  • Crash Nilson

    Thank you for this piece of work. Helps both consumers and firms manage risk.

  • Cheng Yu

    I feel so sorry that some of the repliers argues you for critizing a 'Chinese' company. I think your view is quite objective and you provide enough proofs. Thank you for your great works.

    Ps. I am a CCP member and my parents was both working for State Owned Companies in China mainland.

  • anon

    would it be useful to spam xiaomi with useless and made up data >:)

    Wladimir Palant

    No. Artificially generated data is usually easy to filter out, and companies doing big data typically have people who do just that as their job (data collected from test installs and similar provides little business value).

  • paillet

    Have you send your analysis to Xiaomi ? If so what is the response.

    Wladimir Palant

    I’m merely confirming someone else’s analysis here. The linked Forbes article has a response from Xiaomi, also links to their blog post. And that response essentially says: no problem whatsoever, we’ve been completely misunderstood, we merely collect telemetry data to improve our product. But since you are all so upset, we’ll introduce an obscure settings combination that will disable it. Good luck finding out what the right combination is, we’ll make it extra confusing.

  • DurzoBlint

    I thank you very much for this in-depth analysis. I've been debating about moving away from Samsung and going to Xiaomi for my next phone just based on price. I mean almost half price for practically the same specs? But I'd be worried about baked in spyware. My question is - Does all of this spyware issue go away if you flash a new rom to the phone? Like flashing vanilla Android or maybe LineageOS?

    Wladimir Palant

    Yes, reflashing should get rid of any issues. As long as you trust the ROM source that is.

  • Jason

    So the spyware is software only, not hardware/firmware? I'm about to start a CS degree soon, so hopefully one day I can check under the hood myself! If flashing a custom ROM like LineageOS gets rid of the problem, then buying these phones for their (presumably clean) hardware is a win for value.

    Wladimir Palant

    I have no idea about hardware or firmware. Their software is the only thing I could look at, and I only looked at one tiny corner.