Unsage类的使用

Java是以安全著称,但在Java中有一个类是一个Bug级别的存在,那就是Unsafe. 前面已经说过Unsafe在java中的使用,此处我们直接说用法:

避免初始化

当你想跳过对象初始化的阶段,或者绕过构造函数的检查,去实例化没有任何公共构造函数的类,可以使用allocateInstance:

1
2
3
4
5
6
7
class A {
private long a; // not initialized value
public A() {
this.a = 1; // initialization
}
public long a() { return this.a; }
}

使用构造函数、反射和unsafe初始化它,将得到不同的结果。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
A o1 = new A(); // constructor
System.out.println(o1.a()); // prints 1

A o2 = A.class.newInstance(); // reflection
System.out.println(o2.a()); // prints 1

A o3 = (A) UnsafeUtils.getUnsafe().allocateInstance(A.class); // unsafe
System.out.println(o3.a()); // prints 0
}

内存崩溃(Memory corruption)

我们可以使用Unsafe去做一些绕过安全的技术:

1
2
3
4
5
6
7
class Guard {
private int ACCESS_ALLOWED = 1;

public boolean giveAccess() {
return 42 == ACCESS_ALLOWED;
}
}

当客户端调用giveAccess代码是,始终返回的都是false.使用Unsafe可以绕过权限:

1
2
3
4
5
Guard guard=new Guard();
System.out.println("修改前:"+guard.giveAccess());
Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption
System.out.println("修改后:"+guard.giveAccess());

计算对象的大小

sizeOf是返回对象的自身内存大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static long sizeOf(Object o) {
Unsafe u = UnsafeUtils.getUnsafe();
HashSet<Field> fields = new HashSet<Field>();
Class c = o.getClass();
while (c != Object.class) {
for (Field f : c.getDeclaredFields()) {
if ((f.getModifiers() & Modifier.STATIC) == 0) {
fields.add(f);
}
}
c = c.getSuperclass();
}
// get offset
long maxSize = 0;
for (Field f : fields) {
long offset = u.objectFieldOffset(f);
if (offset > maxSize) {
maxSize = offset;
}
}

return ((maxSize/8) + 1) * 8; // padding
}

如果只是对象类的结构大小,那么可以更简单的实现:

1
2
3
4
5
6
7
8
9
public static long sizeOf(Object object){
return UnsafeUtils.getUnsafe().getAddress(
normalize(UnsafeUtils.getUnsafe().getInt(object, 4L)) + 12L);
}
private static long normalize(int value) {
if(value >= 0) return value;
return (~0L >>> 32) & value;
}

浅拷贝(Shallow copy)

为了计算自身内存的大小,可以简单的添加拷贝的对象方法,标准的解决方案是使用Cloneable修改代码,或者实现自定义的拷贝方法.

浅拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class ShallowCopy {
static Unsafe unsafe = UnsafeUtils.getUnsafe();
public static Object shallowCopy(Object obj) {
long size = sizeOf(obj);
long start = toAddress(obj);
long address = unsafe.allocateMemory(size);
unsafe.copyMemory(start, address, size);
return fromAddress(address);
}
//toAddress和fromAddress将对象转换为其在内存中的地址,反之亦然。
static long toAddress(Object obj) {
Object[] array = new Object[] {obj};
long baseOffset = unsafe.arrayBaseOffset(Object[].class);
return normalize(unsafe.getInt(array, baseOffset));
}

static Object fromAddress(long address) {
Object[] array = new Object[] {null};
long baseOffset = unsafe.arrayBaseOffset(Object[].class);
unsafe.putLong(array, baseOffset, address);
return array[0];
}
public static long sizeOf(Object o) {

HashSet<Field> fields = new HashSet<Field>();
Class c = o.getClass();
while (c != Object.class) {
for (Field f : c.getDeclaredFields()) {
if ((f.getModifiers() & Modifier.STATIC) == 0) {
fields.add(f);
}
}
c = c.getSuperclass();
}

// get offset
long maxSize = 0;
for (Field f : fields) {
long offset = unsafe.objectFieldOffset(f);
if (offset > maxSize) {
maxSize = offset;
}
}
return ((maxSize/8) + 1) * 8; // padding
}
//normalize是一个为了正确内存地址使用,将有符号的int类型强制转换成无符号的long类型的方法。
private static long normalize(int value) {
if(value >= 0) return value;
return (~0L >>> 32) & value;
}
}

隐藏密码(Hide Password)

Unsafe类中可以删除内存中的对象,比如密码信息。用户的密码大多数都是byte[]或char[]数组,为什么要使用数组呢?

这个是出于安全的考虑,因为我们可以删除不需要的数组元素,如果是一个字符串对象的话,这可以像一个对象在内存中保存,删除该对象只是删除了引用,真正的数据还保存在内存中。

1
2
3
4
5
6
7
8
9
10
11
12

String password = new String("l00k@myHor$e");
String fake = new String(password.replaceAll(".", "?"));
System.out.println(password); // l00k@myHor$e
System.out.println(fake); // ????????????

unsafe.copyMemory(
fake, 0L, null, toAddress(password), sizeOf(password));

System.out.println(password); // ????????????
System.out.println(fake); // ????????????