How can serialisation/deserialisation break immuta

2020-06-02 02:06发布

I was asked this question in an interview. The interviewer wanted to know how to make an object immutable. and then he asked what if I serialise this object - will it break immutability? If yes, how do I prevent it? Can anyone please help me understand this?

标签: java
7条回答
萌系小妹纸
2楼-- · 2020-06-02 02:35

You should read Effective Java written by Joshua Bloch. There is whole chapter about security issues connected with serialization and advices how to design your class properly.

In few words: you should learn about readObject and readResolve methods.

More detailed answer: Yes serialization can break immutability.

Let's assume you have class Period (it's example from Joshua's book):

private final class Period implements Serializable {
    private final Date start;
    private final Date end;

public Period(Date start, Date end){
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
    if(this.start.compareTo(this.end() > 0)
        throw new IllegalArgumentException("sth");
}
//getters and others methods ommited
}

It looks great. It's immutable (you can't change start and end after initialization), elegant, small, threadsafe etc.

But...

You have to remember that serialization is another way of creating objects (and it is not using constructors). Objects are build from byte stream.

Consider scenario when someone (attacker) change your serialization byte array. If he does such thing he could break your condition about start < end. Moreover there is possibility that attacker will put in stream (passed to deserialization method) reference to his Date object (which is mutable and Period class immutability will be completely destructed).

The best defense is not using serialization if you don't have to. If you have to serialize your class use Serialization Proxy pattern.

Edit (at kurzbot request): If you want to use Serialization Proxy you have to add static inner class inside Period. This class objects will be used for serialization instead of Period class objects.

In Period class write two new methods:

private Object writeReplace(){
    return new SerializationProxy(this);
}

private void readObject(ObjectInputStream stream) throws InvalidObjectException {
    throw new InvalidObjectException("Need proxy");
}

First method replace default serialized Period object with SerializationProxy object. Second guarantee that attacker won't use standard readObject method.

You should write writeObject method for SerializationProxy so you can use:

private Object readResolve() {
    return new Period(start, end);
}

In that case you are using only public API and have certainty that Period class will remain immutably.

查看更多
相关推荐>>
3楼-- · 2020-06-02 02:36

The dirt-simple answer is

class X implements Serializable {
    private final transient String foo = "foo";
}

The field foo will equals "foo" if the object is newly created, but will be null when deserialized (and without resorting to dirty tricks, you won't be able to assign it).

查看更多
虎瘦雄心在
4楼-- · 2020-06-02 02:41

When you serialize an object graph that has multiple references to the same object, the serializer notes this fact, so that the deserialized object graph has the same structure.

For example,

int[] none = new int[0];
int[][] twoArrays = new int[] { none, none };
System.out.print(twoArrays[0] == twoArrays[1]);

will print true, and if you serialized and deserialized twoArrays then you would get the same result instead of each element of the array being a different object as in

int[][] twoDistinctArrays = new int[] { new int[0], new int[0] };

You can exploit this support for reference sharing to craft bytes after a serialized entry to share a reference with a privately help object or array, and then mutate it.

So an unserializable class can maintain invariants -- that a privately held object does not escape -- that a serializable class cannot maintain.

查看更多
beautiful°
5楼-- · 2020-06-02 02:41

Make it immutable by keeping all state information in a form where it can not be changed after the object is created.

Java does not allow perfect immutability in some cases.

Serializable is something that you can do but it isn't perfect because there has to be a way to recreate an exact copy of an object when deserializing and it may not be sufficient to use the same constructors to deserialize and to create the object in the first place. That leaves a hole.

Some things to do:

  • Nothing but private or final properties.
  • Constructor sets any of those properties that are critical to operation.

Some other things to think about:

  • static variables are probably a bad idea though a static final constant isn't a problem. There is no way to set these from the outside when the class is loaded but preclude setting them again later.
  • if one of the properties passed to the constructor is an object, the caller can keep a reference to that object and, if it is not also immutable, change some internal state of that object. That effectively changes the internal state of your object that has stored a copy of that, now modified, object.
  • someone could, theoretically, take the serialized form and alter it (or just build the serialized form from scratch) and then use that to deserialize, thus creating a modified version of the object. (I figure this is probably not worth worrying about in most cases.)
  • you could write custom serialize/deserialize code that signs the serialized form (or encrypts it) such that modifications are detectable. Or you could use some form of transmission of the serialized form that guarantees it is not changed. (This assumes you have some control over the serialized form when not in transit.)
  • There are byte code manipulators that can do anything they want to an object. For example, add a setter method to an otherwise immutable object.

The simple answer is that in most cases, just follow the two rules at the top of this answer and that will be good enough to handle your needs for immutability.

查看更多
▲ chillily
6楼-- · 2020-06-02 02:44

As others have said, one could make the argument that serialization results in a brand new object, which is then immutable, so no, serialization doesn't break it, but I think there's a bigger picture to immutability we have to consider before answering that question.

I think the real answer depends completely on the class being serialized, and the level of immutability required, but since the interviewer failed to give us source code, I will come up with my own. I'd also like to point out that, as soon as people start talking about immutability, they start throwing around the final keyword - yes, that makes a reference immutable, but it's not the only way to achieve immutability. Okay, let's look at some code:

public class MyImmutableClass implements Serializable{
    private double value;

    public MyImmutableClass(double v){
        value = v;
    }

    public double getValue(){ return value; }
}

Is this class mutable because I implemented Serializable? Is it mutable because I didn't use the final keyword? No way - it's immutable in every practical sense of the word, because I'm not going to modify the source code (even if you ask me to nicely), but more importantly, it's immutable because no outside class can change the value of value, short of using Reflection to make it public and then modifying it. By that token, I suppose you could run some intermediary hex editor and manually modify the value in RAM too, but that doesn't make it any more mutable than it was before. Extending classes can't modify it either. Sure, you can extend it and then override getValue() to return something different, but doing so will not have changed the underlying value.

I know this might rub a lot of people the wrong way, but it's my opinion that immutability is often purely semantic - e.g. is it immutable to someone calling your code from an outside class, or is it immutable from someone using BusPirate on your motherboard? There are VERY good reasons to use final to help ensure immutability, but I think it's importance is vastly overstated in more than a few arguments. Just because the JVM is allowed to do some magic under the hood to ensure Serialization works doesn't mean the level of immutability your application requires is somehow broken.

查看更多
\"骚年 ilove
7楼-- · 2020-06-02 02:52

You can prevent from serialization or cloning with the help of SecurityManager in java

public final class ImmutableBean {
private final String name;

public ImmutableBean(String name) {
    this.name = name;
    //this line prevent it form serialization and reflection
    System.setSecurityManager(new SecurityManager());
}

public String getName() {
    return name;
}

}

查看更多
登录 后发表回答