当前位置: 首页 > 科技观察

说说Unsafe在Java中的作用

时间:2023-03-21 19:53:32 科技观察

前言最近在Kotlin项目中发现定义的数据类(成员变量都声明为non-nullable)可以被Gson解析,成员变量为空的对象可以获取而不是如果解析失败,很容易导致后续代码的意外执行,因为成员变量都被处理为不可为空,最终抛出NullPointerException。分析原因在Gson的代码中找到实例化对象的地方。多次构造方法失败后,会使用Unsafe来构造实例。/***返回一个可以构造请求类型实例的函数。*/publicfinalclassConstructorConstructor{...publicObjectConstructorget(TypeTokentypeToken){finalTypetype=typeToken.获取类型();最后一堂课rawType=typeToken.getRawType();...ObjectConstructordefaultConstructor=newDefaultConstructor(rawType);if(defaultConstructor!=null){返回defaultConstructor;}...//最后尝试不安全returnnewUnsafeAllocator(type,rawType);}...}Unsafe是位于sun.misc包下的一个类,主要提供了一些执行低级的、不安全的操作的方法,比如直接访问系统内存资源、内存资源的自我管理等。这些方法对提高Java运行效率,增强对Java语言底层资源的操作能力起到了很大的作用。Unsafe使Java语言具有类似于C语言指针的操作内存空间的能力,Unsafe的使用必须谨慎。Gson利用其操作对象的能力,使用allocateInstance方法绕过构造函数创建对象。尝试{ClassunsafeClass=Class.forName("sun.misc.Unsafe");Fieldf=unsafeClass.getDeclaredField("theUnsafe");f.setAccessible(true);最终对象不安全=f.get(null);最终方法allocateInstance=unsafeClass.getMethod("allocateInstance",Class.class);返回新的UnsafeAllocator(){@Override@SuppressWarnings("unchecked")publicTnewInstance(Classc)throwsException{assertInstantiable(c);返回(T)allocateInstance.invoke(不安全,c);}};}catch(Exceptionignored){}结论通过Unsafe#allocateInstance实例化的对象绕过构造函数,这在Koltin中是额外注意的,因为Kotlin对非空变量的赋值会被Intrinsics.checkParameterIsNotNull处理,并且此时绕过了构造函数的一系列判断,导致上下文不一致。《为什么要用反射来获取Unsafe?》Unsafe是作为单例实现的,getUnsafe()静态方法只有在被调用的类被引导类加载器BootstrapClassLoader加载时才合法。直接反射获取Unsafe实例!publicfinalclassUnsafe{privatestaticfinalUnsafetheUnsafe;...privateUnsafe(){}@CallerSensitivepublicstaticUnsafegetUnsafe(){Classvar0=Reflection.getCallerClass();if(!VM.isSystemDomainLoader(var0.getClassLoader())){thrownewSecurityException("Unsafe");}else{返回不安全;}}...}其他应用UnsafeRestrictAndroidP版本后的隐藏API调用,作为一个“合格”的开发者应该尊重官方的规则,这也有利于项目的长期维护。但是偶尔尝试打破规则!限制对隐藏API的调用https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces?hl=zh-cn。”“先说说系统是怎么实现这个限制的?””通常调用隐藏API都是通过反射,但是反射调用也会被拦截。源码分析可以发现java.lang.Class#getDeclaredMethod()最终会调用native方法getDeclaredMethodInternal。staticjobjectClass_getDeclaredMethodInternal(JNIEnv*env,jobjectjavaThis,jstringname,jobjectArrayargs){ScopedFastNativeObjectAccesssoa(env);StackHandleScope<1>hs(soa.Self());DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(),kRuntimePointerSize);DCHECK(!Runtime::Current()->IsActiveTransaction());Handle结果=hs.NewHandle(mirror::Class::GetDeclaredMethodInternal(soa.Self(),DecodeClass(soa,javaThis),soa.Decode(name),soa.Decode>(args)));if(result==nullptr||ShouldBlockAccessToMember(result->GetArtMethod(),soa.Self())){returnnullptr;}returnsoa.AddLocalReference(result.Get());}当「「ShouldBlockAccessToMember」」返回true时,那么直接返回nullptr,上层就会抛NoSuchMethodXXX异常,触发系统限制templateinlineActionGetMemberAction(T*member,Thread*self,std::functionfn_caller_is_trusted,AccessMethodaccess_method)REQUIRES_SHARED(Locks::mutator_lock_){DCHECK(member!=nullptr);}//解码隐藏的API访问标志。//NB多个线程可能会尝试同时访问(并覆盖)这些,//导致竞争。我们仅在访问未被拒绝时才这样做,因此竞争//无法更改Java语义。然而,我们应该解码访问标志//一次并在整个函数中使用它,否则我们可能会得到不一致的//结果,例如打印白名单警告(b/78327881)。HiddenApiAccessFlags::ApiListapi_list=member->GetHiddenApiAccessFlags();动作action=GetActionFromAccessFlags(member->GetHiddenApiAccessFlags());if(action==kAllow){//什么都不做。返回动作;}//成员被隐藏。调用`fn_caller_in_platform`并找到访问的来源。//这可能*非常*昂贵。最后保存。if(fn_caller_is_trusted(self)){//调用者是可信的。出口。返回kAllow;}//成员被隐藏,调用者不在平台中。returndetail::GetMemberActionImpl(member,api_list,action,access_method);}如果主要判断逻辑中的三个条件之一通过,则不会触发系统限制。fn_caller_is_trusted是判断调用者的Class是否通过BootClassLoader加载,所以系统可以直接调用隐藏的API,系统Class由BootClassLoader加载,其ClassLoader为null。如果设置为null,Class可以通过反射调用隐藏的API。反射直接修改Class.classLoader是不可行的,因为这个字段在深灰名单中,会抛出NoSuchFiledException。""Unsafetime""通过Unsafe获取Classloader在Class中的偏移量,并将偏移量设置为null。Class在内存中的结构如下。前两个变量继承自Object,都是4字节,所以classloader的偏移量是8。structClass{Classshadow$_klass_;int影子$_monitor_;ClassLoaderclassLoader;}果然偏移量为8,输出的是classloader信息,设置为null,getClassLoader又变成了BootClassLoader。classMainActivity:AbsActivity(){overridefunonContentLayoutId():Int=R.layout.activity_mainoverridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)valunsafe=UnsafeAndroid()Timber.d(unsafe.getObject(Reflect::class.java,8).toString())unsafe.getAndSetObject(Reflect::class.java,8,null)Timber.d("${unsafe.getObject(Reflect::class.java,8)}")Timber.d(Reflect::class.java.classLoader.toString())}}D/

(MainActivity.kt:16):dalvik.system.PathClassLoader[DexPathList[[dex文件"/data/data/com.x.example/code_cache/.overlay/base.apk/classes2.dex”,压缩文件“/data/app/~~okoSHt8RD79B35SscL93sA==/com.x.example-HuYEILM1Ybt2xxGkn7eViw==/base.apk"],nativeLibraryDirectories=[/data/app/~~okoSHt8RD79B35SscL93sA==/com.x.example-HuYEILM1Ybt2xxGkn7eViw==/lib/arm64,/data/app/~~okoSHt8RD79B35SscL93sA==/com.x.example-HuYEILM1Ybt2xwViwGkn7==/基地.apk!/lib/arm64-v8a,/system/lib64,/system_ext/lib64]]]D/
(MainActivity.kt:18):nullD/
(MainActivity.kt:19):java.lang.BootClassLoader@42a2eb

最新推荐
猜你喜欢