A Journey of an IllegalAccessError issue

Eric Lu
6 min readJul 27, 2017

--

Few weeks ago, I dived into a serious crash issue happened only on Android 4.4 (works good on 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 on 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 on Android 4.4
  • ProGuard is enabled

This crash issue happened only in the released version of app with ProGuard enabled, and only on 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 less than 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 this kind of access didn’t exist in whole codebase.
  2. The crash is happened in the 8074th line of MyView.java, but there are fewer lines in it.

It seems that the code which was executing is different than 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
}

Method MyManager#fetch had been inlined by ProGuard!

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 the whole app. After ProGuard optimization, ProGuard moved all of the code in MyManager#fetch method to MyView#fetch method and changed some code or modifiers of relating classes.

In the APK side, 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’t explain why it behaved differently 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 looking for the root cause. I saw a suspicious log when I lunched the app at the first time on 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 letting the function dvmCheckClassAccess return false. And there are no code changes in those functions between Android 4.3 and 4.4.

Next, it would be nice if I can invoke 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 on Android 4.4? What makes the difference to the Dalvik VM of Android 4.4?

Found the commit that caused this difference

It seems that the logic of handling Object array had been changed on Android 4.4 Dalvik VM. Next, I compared the Dalvik VM source code on Android 4.3 and 4.4.

I found out this code change 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 see 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

--

--