A Journey of an IllegalAccessError issue

Eric Lu
Eric Lu
Jul 27, 2017 · 6 min read

Few weeks ago, I deeply dived into a serious crash issue which was happened only in Android 4.4 (safe in 4.3).

In this post, I will go through all approaches I’ve tried and tools I used to analyze this issue and every steps in detail, and finally, the root cause.

Solving this crash is easy, but what I want to investigate is the reason of why it had different behavior in Android 4.3 and 4.4.

In essence, the root cause was a code change in Android 4.4 dalvik about handling array type of object class. For the readers who want to jump into the root cause part directly, please refer to xxxxx.

The tools or technics I used to investigate on this issue:

  • JADX, a Dex to Java decompiler.
  • Apktool, a tool for reverse engineering Android apk files.
  • API imitation (like using Java reflection to access the private classes)
  • java2smali, a plugin for Android Studio to compile Java files to smali.
  • AndFix, a library that offer hot-fix for Android App.
  • Approaches to invoke functions in libdvm.so

Issue overview

Some glances of this issue:

  • Crash caused by IllegalAccessError
  • Happened only in Android 4.4
  • ProGuard is enabled

This crash issue was happened only in the released version of app with ProGuard enabled, and only in Android 4.4

The original crash log:

java.lang.IllegalAccessError: tried to access class com.eric.manager.MyManager$TaskParams[] from class com.eric.view.MyView
at com.eric.view.MyView.d(MyView.java:8074)

Actually, there are no 8074 lines in MyView.java

The restored crash log (using ProGuard mapping file):

java.lang.IllegalAccessError: tried to access class com.eric.manager.MyManager$TaskParams[] from class com.view.MyView
at com.eric.manager.MyManager.fetch (MyManager.java:74)
at com.eric.view.MyView.fetch (MyView.java:168)

The crash can be presented as:

IllegalAccessError: tried to access TaskParams[] from class MyView

Next, take a look to the source code corresponding to the crash log:

Source code of MyView.java

package com.eric.view.MyView;public class MyView {
public void fetch() {
MyManager.getInstance().fetch(); // line: 168
}
}

Source code of MyManager.java

package com.eric.manager.MyManager;public class MyManager {
// no modifier
static class TaskParam {
}
public void fetch() {
TaskParam param = new TaskParam();
fun(param); // line: 74, crash happened!
}
}

I noticed 2 things:

  1. The log showed that crash was happened when accessing TaskParam[], but those kind of access didn’t exist in whole source code.
  2. The crash is happened in the 8074th line of MyView.java, but there are no much lines in it.

It seems that the code which was executed is different from the app source code.

One thing came in my mind: ProGuard optimization

ProGuard inline optimization

To spy into the APK file, I used JADX tool. JADX is a tool that can convert APK file to Java files (if the APK didn’t have any protection on it). By using JADX, I can see the code which had been really executed.

The code of MyView.java seeing in JADX looks like:

package com.eric.view.MyView;static void d() {
// ... Some codes are ignored.
// It's safe to instantiate a TaskParam object.
TaskParam param = new TaskParam();
// But got crash when instantiating a TaskParam array.
a.b(new TaskParam[]{param}); // crash
}

In the original source code, method MyManager#fetch is called by MyView#fetch. But this calling had been removed and MyManager#fetch had been inlined in MyView#fetch after ProGuard optimization. ProGuard inlined MyManager#fetch method because it was called only once in whole app After ProGuard optimization, ProGuard moved all of the codes in MyManager#fetch method to MyView#fetch method and changed some codes or modifiers of relating classes.

In the APK codes, I noticed that it’s safe to instantiate a TaskParam object, but got crash when instantiating a TaskParam array.

This access only existed in APK codes.

Disable ProGuard inline optimization

One way to solve this crash is to disable ProGuard inline optimization.

https://www.guardsquare.com/en/proguard/manual/optimizations

// Inlines short methods.
method/inlining/short
// Inlines methods that are only called once.
method/inlining/unique

But it still can not explain why there has difference between Android 4.3 and 4.4.

Other approaches I have tried (but in vain)

  • I checked whether it has difference between the ClassLoaders which are used by MyView class and TaskParam array, or the TaskParam class and TaskParam array. But I missed checking the results of getModifiers
  • I checked and changed the location of smali files of MyView class and TaskParam array.
  • Disable the enforcing mode in SELinux (emulator disabled it already).
  • Checked the content in /data/dalvik-cache/

Another clue

Never give up! Keep finding the reason making the difference. I saw a suspicious log when I lunched the app at the first time in Android 4.4, but not shown in Android 4.3.

W/dalvikvm: DexOpt: resolve class illegal access: Lcom/eric/view/MyView; -> [Lcom/eric/manager/MyManager$TaskParam;

After that, I traced the source code of Android dalvik to find where it got logged.

The suspicious log is logged here.

There are lots of function calls inside dvmInSamePackage function, so it’s difficult to identify what function causing the function dvmCheckClassAccess returning false. And there are no difference between the source code of those functions in Android 4.3 and 4.4.

Next, it would be wonderful if I can call those functions in app to check the returning values. But, is it possible? How to do that?

Invoke functions which are in libdvm.so (dalvik vm)

Yes, it’s possible to invoke functions in libdvm.so library without root permission. It requires some steps for preparation. Steps include:

  1. Use dlopen function to open libdvm.so library.
  2. Use nm -g libdvm.so to get the symbol names I’d like to invoke.
  3. Use dlsym function to get the function pointer.
  4. Invoke function.

It’s quite complicated to implement the steps above correctly. So I leveraged a library called AndFix to speedup the implementation. I copied all of the source code of AndFix into the app project.

Experiment results of dalvik vm (Android 4.3 and 4.4)

I checked the returned results of many functions in libdvm.so library and checked some access modifiers of TaskParam array. The table below shows the results:

Experiment results

The most important part in the table is the result of dvmIsPublicClass(TaskParam[]) function. The TaskParam[] in Android 4.3 is public, but it became non-public in Android 4.4. This caused the crash in Android 4.4.

But, why it had this difference in Android 4.4? What makes the difference to the Dalvik VM of Android 4.4?

Found the commit that caused this difference

It seemed that the logic of handling Object array had been changed in Android 4.4 Dalvik VM. Next, I went to compare the Dalvik VM source code in Android 4.3 and 4.4.

I found out the changes in Array.cpp might be the suspect.

Code differences in Array.cpp

Then, I used the technics mentioned before to examine the results of these changes.

Finally, I was sure that the difference made TaskParam[] be non-public in Android 4.4.

Next, I want to found which commit made these changes.

Finally, I found out the commit:

The commit:
https://android-review.googlesource.com/#/c/60751/

https://code.google.com/p/android/issues/detail?id=56267

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade