Java反射机制清空字符串导致业务异常分析

编者按:笔者在处理业务线问题时遇到接口返回的内容和实际内容不一致的现象。根因是业务方通过java反射机制将string类型敏感数据引用的value数组元素全部设置为'0',从而实现清空用户敏感数据的功能。这种清空用户敏感数据的方法会将字符串常量池相应地址的内容修改,进而导致所有指向该地址的引用的内容和实际值不一致的现象。
背景知识 jvm为了提高性能和减少内存开销,在实例化字符串常量时进行了优化。jvm在java堆上开辟了一个字符串常量池空间(stringtable),jvm通过ldc指令加载字符串常量时会调用 stringtable::intern 函数将字符串加入到字符串常量池中。
stringtable::intern函数代码oop stringtable::intern(handle string_or_null, jchar* name,                        int len, traps) {  unsigned int hashvalue = hash_string(name, len);  int index = the_table()->hash_to_index(hashvalue);  oop found_string = the_table()->lookup(index, name, len, hashvalue);  // found  if (found_string != null) {    ensure_string_alive(found_string);    return found_string;  }  debug_only(stablememorychecker smc(name, len * sizeof(name[0])));  assert(!universe::heap()->is_in_reserved(name),         proposed name of symbol must be stable);  handle string;  // try to reuse the string if possible  if (!string_or_null.is_null()) {    string = string_or_null;  } else {    string = java_lang_string::create_from_unicode(name, len, check_null);  }#if include_all_gcs  if (g1stringdedup::is_enabled()) {    // deduplicate the string before it is interned. note that we should never    // deduplicate a string after it has been interned. doing so will counteract    // compiler optimizations done on e.g. interned string literals.    g1stringdedup::deduplicate(string());  }#endif  // grab the stringtable_lock before getting the_table() because it could  // change at safepoint.  oop added_or_found;  {    mutexlocker ml(stringtable_lock, thread);    // otherwise, add to symbol to table    added_or_found = the_table()->basic_add(index, string, name, len,                                  hashvalue, check_null);  }  ensure_string_alive(added_or_found);  return added_or_found;} stringtable::intern 函数处理流程
字符串的创建方式
根据stringtable::intern函数处理流程,我们可以简单描绘如下6种常见的字符串的创建方式以及引用关系。
现象 某业务线使用fastjson实现java对象序列化功能,低概率出现接口返回的json数据的某个属性值和实际值不一致的现象。正确的属性值应该为null,实际属性值却为0000。
原因分析 为了排除fastjson自身的嫌疑,我们将其替换jackson后,依然会低概率出现同样的现象。由于两个不同三方件同时存在这个问题的可能性不大,为此我们暂时排除fastjson引入该问题的可能性。为了找到该问题的根因,我们在环境中开启远程调试功能。待问题复现,调试代码时我们发现只要是指向null的引用,显示的内容全部变成0000,由此我们初步怀疑字符串常量池中的null被修改成0000。
一般导致常量池被修改有两种可能性:
第三方动态库引入的bug导致字符串常量池内容被修改; 在业务代码中通过java反射机制主动修改字符串常量池内容; 业务方排查项目中使用到的第三方动态库,未发现可疑的动态库,排除第一种可能性。排查业务代码中使用到java反射的功能,发现清空密码功能会使用到java反射机制,并且将string类型密码的value数组元素全部设置为'0'。
业务出现的现象可以简单通过代码模拟:
在teststring对象类中定义一个nullstr属性,初始值为null; 定义一个带有password属性的user类; 在main方法中创建一个密码为null的user对象,使用java反射机制将密码字符串的所有字符全部修改为'0',分别在密码修改前后打印teststring对象nullstr属性值;   复现代码 import java.lang.reflect.field;import java.util.arrays;public class teststring {    private string nullstr = null;    public string getnullstr() {        return nullstr;    }    static class user {        private final string password;        user(string password) {            this.password = password;        }        public string getpassword() {            return password;        }    }    private static void clearpassword(user user) throws exception {        field field = string.class.getdeclaredfield(value);        field.setaccessible(true);        char[] chars = (char[]) field.get(user.getpassword());        arrays.fill(chars, '0');    }    public static void main(string[] args) throws exception {        user user = new user(null);        teststring teststring = new teststring();        system.out.println(before clear password >>>>);        system.out.println(     user.password: + user.getpassword());        system.out.println(teststring.nullstr: + teststring.getnullstr());        system.out.println(--------------------------------);        clearpassword(user);        system.out.println(after clear password >>>>);        system.out.println(     user.password: + user.getpassword());        system.out.println(teststring.nullstr: + teststring.getnullstr());    }} 复现代码字符串引用关系如下图所示。
user对象的password属性和teststring的nullstr属性引用都同时指向常量池中的null字符串,null字符串的value指向 {'n','u','l','l'} char数组。使用java反射机制将user对象的password属性引用的value数组全部设置为'0',导致teststring的nullstr属性值也变成了 0000。 输出结果如下:
  before clear password >>>>       user.password:null  teststring.nullstr:null  --------------------------------  after clear password >>>>       user.password:0000  teststring.nullstr:0000 通过输出结果我们可以发现在通过java反射机制修改某一个字符串内容后,所有指向原字符串的引用的内容全部变成修改后的内容。
总结 在保存业务敏感数据时避免使用string类型保存,建议使用byte[]或char[]数组保存,然后通过java反射机制清空敏感数据。
后记 如果遇到相关技术问题(包括不限于毕昇 jdk),可以通过 compiler sig 求助。compiler sig 每双周周二举行技术例会,同时有一个技术交流群讨论 gcc、llvm 和 jdk 等相关编译技术,感兴趣的同学可以添加如下微信小助手入群。


透明指纹传感器:用户将手指放在屏幕任何位置都可进行身份识别
什么是HashMap HashMap数据结构分析
三台有启停顺序要求电动机的联锁控制线路分析
物联网的发展前景如何,我们该如何抢占市场先机
智芯传感推出ZXP6系列高精度宽温域差压气体压力传感器
Java反射机制清空字符串导致业务异常分析
是什么支持了中国支付的十几年的繁荣的原动力
干簧技术课堂-第二课
追觅、戴森、添可吸尘器的对比,谁才是最好用的吸尘器
基于DSP的交流电机的变速控制 三相交流电机控制器结构解析
诺基亚8什么时候上市?诺基亚8最新消息汇总:官方自曝诺基亚8,一部让您久等的旗舰!
中控智慧科技考勤机TX628-P简介
灵活的连接和供电是当今数据中心设计的核心
Mini/Micro规模化应用设备材料还要做些什么呢?
伺服电机和三相异步电机的区别
数据库管理系统(DBMS)是什么意思
中国电信正式公布了2019年PON设备集采结果
性价比之王:红米4只售699还送活塞耳机
自制电话分线器
TD-SCDMA初期投资规模有限