Prevent Hacking with Modules in Java 9

Peter Verhas, EPAM Systems

DevDays | May 24, 2018 14:00 - 14:45, Vilnius

About

Some hacking that were available up to Java 8

JPMS module system that prevents such hacking

Using unsafe and the liaison of Java 9 with unsafe

how will we go on with all these, future

Hacking?

what hacking? ... is it Perl ... or... bash ?

what are we talking about ?

Well, ...

There are some things that are hackable

mainly because of optimizations

Java

early times it was struggling with speed.

... well... sometimes it still does ...

some optimizations were not that optimal, for example

the Integer cache

the Integer cache

can be hacked

why would anyone hack it?

That is not the point. It is a security issue, it is not good.

Not a tragedy though.... but it is not nice.

the Integer cache

What is it?


private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
    static {
        // code that that fills cache[-128...127] = -128 ...127
    }
    private IntegerCache() {}
}
            

it is used inside Integer.java

It is used in valueOf()


public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
            

still in Integer.java

What if ....

I overwrote the cache using reflection?

Would it be weird?


        if( 1 + (Integer)1 == 2 ){
            System.out.println("OK");
        }else{
            System.out.println("WEIRD");
        }
            

Entropy


public static void main(String[] args) throws Exception {
    Class clazz = Class.forName("java.lang.Integer$IntegerCache");
    Field field = clazz.getDeclaredField("cache");
    field.setAccessible(true);
    Integer[] cache = (Integer[]) field.get(clazz);
    for (int i = 0; i < cache.length; i++) {
        cache[i] = new Integer(new Random().nextInt(cache.length));
    }
    for (int i = 0; i < 10; i++) {
        System.out.println((Integer) i);
    }
}
            
from https://blog.jooq.org/2013/10/17/add-some-entropy-to-your-jvm/ (article from Lukas Eder)

What we got was the chaos


        92
        221
        45
        48               for (int i = 0; i < 10; i++) {
        236                  System.out.println((Integer) i);
        183              }
        39
        193
        33
        84
            
from https://www.sitepoint.com/10-things-you-didnt-know-about-java/ (article from Lukas Eder)

That was

Java 8

do we still use Java 8?

Not

any

more

We use Java 9

Do we get

CHAOS?

Not

any

more

What we get now


Exception in thread "main"
  java.lang.reflect.InaccessibleObjectException:
Unable to make field static final java.lang.Integer[]
java.lang.Integer$IntegerCache.cache accessible: module java.base
does not "opens java.lang" to unnamed module @1bc6a36e
            
from my command line on my pc (Java 9.0.1)

a nice fat exception

What is happening?


Exception in thread "main"
  java.lang.reflect.InaccessibleObjectException:
Unable to make field static final java.lang.Integer[]
java.lang.Integer$IntegerCache.cache accessible: module java.base
does not "opens java.lang" to unnamed module @1bc6a36e
            
from my command line on my pc (Java 9.0.1)

What happened is

Java 9 introduced JPMS

(Java Platform Module System)

Public is not so public any more

We have modules

JPMS Crash Course

use down arrow to transition

So what is a module?

A module is a JAR file

that has a module-info.class file

module-info.class

compiled from module-info.java

declares 5 things

what the module exports

what other modules it requires

what services the module provides

what services the module uses

what packages the module opens

Module exports

packages

(to certain other modules)

in which

public members of the public classes

can be used by code in other module

that requires the module

Module exports

essentially the API of the library the module contains


module org.astro {
  exports org.astro.api;
  }
            

from the ORACLE documentation (almost)

http://openjdk.java.net/projects/jigsaw/quick-start

Module requires

specifies the other modules that this module uses


module com.greetings {
  requires org.astro;
}            
from the ORACLE documentation

Module provides

service implementation

service provision is an extension of ServiceLoader (available since 1.2, nobody used really)

does anyone know what ServiceLoader is?

Module provides


module org.fastsocket {
  requires com.socket;
  provides com.socket.spi.NetworkSocketProvider
           with org.fastsocket.FastNetworkSocketProvider;
}
                
from the ORACLE documentation

Module uses


 module com.socket {
        exports com.socket;
        exports com.socket.spi;
        uses com.socket.spi.NetworkSocketProvider;
    }
            
from the ORACLE documentation

Module opens

packages

(to certain other modules)

for reflective access


 module com.socket {
        exports com.socket;
        exports com.socket.spi;
        opens com.socket.spi to network.helper;
        uses com.socket.spi.NetworkSocketProvider;
    }
            
from the ORACLE documentation

Sooo.... back from the crash course


Exception in thread "main"
  java.lang.reflect.InaccessibleObjectException:
Unable to make field static final java.lang.Integer[]
java.lang.Integer$IntegerCache.cache accessible: module java.base
does not "opens java.lang" to unnamed module @1bc6a36e
            

what we see here is...

JDK is modular and does not opens (sic) Integer cache

We cannot

access the internal parts of the JDK any more

do things developers are not supposed to do

for example using unsafe

Do you know what is unsafe?

use down arrow to transition

Unsafe is a class in JDK

that we are not supposed to use.

Because it is unsafe.

It is there for INTERNAL USE!

So we do not touch it!

Have you ever used unsafe?

I mean other than using the JDK that is obviously using its internals.

YOU DID.

if you are a Java programmer and used

Hibernate

Spring

Mockito

Netty, Hazelcast, Cassandra, EasyMock, JMock, PowerMock, Scala Specs, Spock, Robolectric, Grails, Neo4j, Akka, Apache Kafka, Apache Wink, Apache Storm, Apache Hadoop, Apache Continuum, ...

What ???

It was not supposed to be used!!!

and still... what is better?

Note on the door: please do not come in!

... or ...

Lock the door!

Seems we have to

lock the door!

Why is this utmost important?

Java is mature

Java is enterprisey

Java is fast

Java should not tolerate hacking any more

But this is incompatible!

Don't you say!

So what?

There are two roads

Stay compatible, no new features, no change, ... Java is the new COBOL?

Limit backward compatibility

When implementation becomes API backward compatibility hinders the development of the implementation.

true for a library

true for a language

Java is not backward compatible any more

can you list some feature?

Underscore cannot be variable name

APIs get deprecated, like

...applet

...observer

Is this something new?

Were all previous Java releases backward compatible?

1.2 String.hashCode() has changed

1.4 cannot import class from default package

1.5 BigDecimal.toString() was changed

enum was introduced as keyword in Java 1.5

... not to mention concurrency

and some other

But all these were

just small fixes and Java tried to remain backward compatible

from now on.... backward compatibility is limited

We can deprecate

@deprecated( forRemoval=true )

_ is invalid as variable

there was a hint already

unsafe is not available any more

So we cannot...

reflectively access IntegerCache

not even via reflective access provided by unsafe

So we cannot...


public static void main(String[] args) throws Exception {
    Class usf = Class.forName("sun.misc.Unsafe");
    Field unsafeField = usf.getDeclaredField("theUnsafe");
    unsafeField.setAccessible(true);
    sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null);
    Class clazz = Class.forName("java.lang.Integer$IntegerCache");
    Field field = clazz.getDeclaredField("cache");
    Integer[] cache = (Integer[])unsafe.getObject(
                unsafe.staticFieldBase(field),
                unsafe.staticFieldOffset(field));

                // the rest is the same
            
from my command line on my pc (Java 9.0.1)

What we get now


Exception in thread "main" java.lang.IllegalAccessError:
class devdays.lt2018.prevent.hacking.IntegerHack (in module integer.hack)
cannot access class sun.misc.Unsafe (in module jdk.unsupported)
because module integer.hack does not read module jdk.unsupported
at integer.hack/devdays.lt2018.prevent.hacking.IntegerHack.main
            
from my command line on my pc (Java 9.0.1)

a nice fat exception

WAIT!!!


because module integer.hack does not read module jdk.unsupported
at integer.hack/devdays.lt2018.prevent.hacking.IntegerHack.main
            

? ? ? ? ? ?

What if we just


module integer.hack {
    requires jdk.unsupported;
}
            

then we get our nice chaos we are accustomed to


        92
        221
        45
        48               for (int i = 0; i < 10; i++) {
        236                  System.out.println((Integer) i);
        183              }
        39
        193
        33
        84
            
actual output may random and different

Keep Calm

unsafe is still there (by default)

but you have to be really desperate

libraries (Spring, Hibernate etc.) will upgrade

they are desperate

but it will

Disappear

Take-away

what we have learned from all this

Java got matured

it cannot grow hackable and it will not be hackable

but it will grow and stay with us for long long time

it is complex... ever more complex

which is GOOD!

we will have jobs!!!

 

thanks for your patience

Peter Verhas, EPAM Systems