variable tutorial parameter method lambdas java8 expressions example java serialization lambda java-8

java - tutorial - Posibilidad de eliminar explícitamente el soporte de serialización para una lambda



lambda stream java (1)

Como ya se sabe , es fácil agregar soporte de Serialización a una expresión lambda cuando la interfaz de destino aún no hereda Serializable , al igual que (TargetInterface&Serializable)()->{/*code*/} .

Lo que pido es una forma de hacer lo contrario, eliminar explícitamente el soporte de Serialización cuando la interfaz de destino hereda Serializable .

Como no puede eliminar una interfaz de un tipo, una solución basada en el idioma posiblemente se parecería a (@NotSerializable TargetInterface)()->{/* code */} . Pero hasta donde sé, no hay tal solución. (Corrígeme si me equivoco, esa sería una respuesta perfecta)

Negar la serialización incluso cuando la clase implemente Serializable era un comportamiento legítimo en el pasado y con clases bajo el control de los programadores, el patrón se vería así:

public class NotSupportingSerialization extends SerializableBaseClass { private void writeObject(java.io.ObjectOutputStream out) throws IOException { throw new NotSerializableException(); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { throw new NotSerializableException(); } private void readObjectNoData() throws ObjectStreamException { throw new NotSerializableException(); } }

Pero para la expresión lambda, el programador no tiene ese control sobre la clase lambda.

¿Por qué alguien se molestaría en quitar el soporte? Bueno, además del código más grande generado para incluir el soporte de Serialization , crea un riesgo de seguridad. Considera el siguiente código:

public class CreationSite { public static void main(String... arg) { TargetInterface f=CreationSite::privateMethod; } private static void privateMethod() { System.out.println("should be private"); } }

Aquí, el acceso al método privado no se expone incluso si TargetInterface es public (los métodos de interfaz siempre son public ) siempre que el programador se ocupe de no pasar la instancia f al código que no es de confianza.

Sin embargo, las cosas cambian si TargetInterface hereda Serializable . Entonces, incluso si CreationSite nunca entrega una instancia, un atacante podría crear una instancia equivalente deserializando una secuencia construida manualmente. Si la interfaz para el ejemplo anterior se ve como

public interface TargetInterface extends Runnable, Serializable {}

es tan fácil como:

SerializedLambda l=new SerializedLambda(CreationSite.class, TargetInterface.class.getName().replace(''.'', ''/''), "run", "()V", MethodHandleInfo.REF_invokeStatic, CreationSite.class.getName().replace(''.'', ''/''), "privateMethod", "()V", "()V", new Object[0]); ByteArrayOutputStream os=new ByteArrayOutputStream(); try(ObjectOutputStream oos=new ObjectOutputStream(os)) { oos.writeObject(l);} TargetInterface f; try(ByteArrayInputStream is=new ByteArrayInputStream(os.toByteArray()); ObjectInputStream ois=new ObjectInputStream(is)) { f=(TargetInterface) ois.readObject(); } f.run();// invokes privateMethod

Tenga en cuenta que el código de ataque no contiene ninguna acción que un SecurityManager revoque.

La decisión de admitir la serialización se realiza en tiempo de compilación. Requiere un método sintético de fábrica agregado a CreationSite y un flag pasado al método metafactory . Sin la bandera, la lambda generada no admitirá la serialización, incluso si la interfaz hereda Serializable . La clase lambda incluso tendrá un método writeObject como en el ejemplo anterior de NotSupportingSerialization . Y sin el método de fábrica sintético, la des-serialización es imposible.

Esto me lleva a la única solución, encontré. Puede crear una copia de la interfaz y modificarla para que no herede Serializable , luego compilar contra esa versión modificada. Entonces, cuando la versión real en tiempo de ejecución pasa a heredar Serializable , la serialización aún será revocada.

Bueno, otra solución es nunca utilizar expresiones / referencias de métodos lambda en el código de seguridad relevante, al menos si la interfaz de destino hereda Serializable que siempre debe volver a verificarse, al compilar contra una versión más nueva de la interfaz.

Pero creo que debe haber mejores soluciones, preferiblemente en el idioma.


Cómo manejar la serializabilidad fue uno de los mayores desafíos para el EG; basta decir que no hubo grandes soluciones, solo intercambios entre varias desventajas. Algunas partes insistieron en que todas las lambdas se serializaran automáticamente (!); otros insistieron en que las lambdas nunca pueden ser serializables (lo que a veces parecía una idea atractiva, pero lamentablemente violaría las expectativas del usuario).

Usted nota:

Bueno, otra solución es nunca usar expresiones lambda / referencias de método en el código relevante de seguridad,

De hecho, la especificación de serialización ahora dice exactamente eso.

Pero, hay un truco bastante fácil de hacer lo que quieras aquí. Supongamos que tiene alguna biblioteca que quiere instancias serializables:

public interface SomeLibType extends Runnable, Serializable { }

con métodos que esperan este tipo:

public void gimmeLambda(SomeLibType r)

y desea pasar lambdas en él, pero no hacer que sean serializables (y tomar las consecuencias de eso.) Entonces, escriba este método de ayuda:

public static SomeLibType launder(Runnable r) { return new SomeLibType() { public void run() { r.run(); } } }

Ahora puede llamar al método de la biblioteca:

gimmeLambda(launder(() -> myPrivateMethod()));

El compilador convertirá su lambda en Runnable no serializable, y la envoltura de lavado lo envolverá con una instancia que satisfaga el sistema de tipos. Cuando intenta serializarlo, eso fallará ya que r no es serializable. Más importante aún, no puede forzar el acceso al método privado, porque el soporte de $ deserializeLambda $ que se necesita en la clase de captura ni siquiera estará allí.