Link

Access control

Table of contents

  1. Access control
  2. Classes access modifiers table
  3. Class members access modifiers table
  4. Which access modifier should I pick?

Access control

We know that the Random class produces a pseudo random sequence. This means that a skilled attacker can predict the next number to be drawn after making several observations.

Consider the following example.

package demo;

import java.util.Random;

public class Dice {

  public static int roll() {
   return RANDOM_GENERATOR.nextInt( 6 ) + 1;
  }

  public static final Random RANDOM_GENERATOR = new Random(1);
}

The above example makes use of static fields to highlight other problems that may be created when having fields marked as static.

The Random object is initialised with a seed to simplify the example. Both the roll() method and the random static field can be accessed from anywhere. An attacker can take advantage of that and force the next dice roll to be a 6.

Consider the following example.

package demo;

public class App {

  public static void main( final String[] args ) {
   /* This method is hacked by an attacker */
   initGame();

   /* This will always roll a 6 */
   playGame();
  }

  public static void playGame() {
   final int a = Dice.roll();
   System.out.printf( "I rolled a %s%n", a );
  }

  public static void initGame() {
   /* Skip some numbers so that the next roll, rolls a 6 */
   for ( int i = 0; i < 19; i++ ) {
     Dice.RANDOM_GENERATOR.nextInt();
   }
  }
}

Observations

  1. The attacker first called the nextInt() method 19 times.

        for ( int i = 0; i < 19; i++ ) {
          Dice.random.nextInt();
        }
    

    The attacker knows that the 20th roll will yield a 6.

  2. The attacker then rolled the dice normally

        final int a = Dice.roll();
        System.out.printf( "I rolled a %s%n", a );
    

    and obtained the expected the attacker wanted.

    I rolled a 6
    

    The attacker can also skip ahead some numbers to make the opponent lose, by rolling a smaller number.

  3. The Dice class uses static fields on purpose. Only one instance of the static field RANDOM_GENERATOR exists and anyone can access it from anywhere in the code.

Using access modifies, access to classes and their members (properties, static fields and methods) can be restricted. Making the static field random, private, will not allow an attacker to access the static field directly.

package demo;

import java.util.Random;

public class Dice {

  public static int roll() {
   return RANDOM_GENERATOR.nextInt( 6 ) + 1;
  }

  private static final Random RANDOM_GENERATOR = new Random( 1 );
}

The attacker cannot now access the random field.

⚠ The following example does not compile!!
package demo;

public class App {

  public static void main( final String[] args ) {
   /* This method is hacked by an attacker */
   initGame();

   /* This will always roll a 6 */
   playGame();
  }

  public static void playGame() {
   final int a = Dice.roll();
   System.out.printf( "I rolled a %s%n", a );
  }

  public static void initGame() {
   /* Skip some numbers */
   for ( int i = 0; i < 19; i++ ) {
     /* ⚠️ Cannot access the private field!! */
     Dice.RANDOM_GENERATOR.nextInt();
   }
  }
}

The following error will be produced

src/main/java/demo/App.java:21: error: RANDOM_GENERATOR has private access in Dice
     Dice.RANDOM_GENERATOR.nextInt();
         ^

Classes access modifiers table

Access ModifierAccessible From
publicAnywhere
(no modifier)same package

Note that inner classes are class members and thus do not make use of the above table. Inner classes use the table shown in the following section.

Class members access modifiers table

Access ModifierFrom Same ClassFrom Same PackageFrom SubclassFrom Anywhere
publicYesYesYesYes
protectedYesYesYesNo
(no modifier)YesYesNoNo
privateYesNoNoNo

Note that there can be more than one class within the same file. Two or more classes in the same file are considered as classes in the same package.

⚠ The following example does not compile!!
package demo;

public class A {
  public static void printIt() {
   System.out.printf( "The value of c is %d%n", B.c );
  }
}

class B {
  private static final int c = 7;
}

Both classes are defined in the same source file, A.java, yet these are two different classes within the same package.

$ tree build/classes/java
build/classes/java
└── main
    └── demo
        ├── A.class
        └── B.class

There is one exception to this rule. Consider the following example.

package demo;

public class App {

  private static final int c = 7;

  public static void main( final String[] args ) {
   final Runnable r = new Runnable() {
     @Override
     public void run() {
       System.out.printf( "The value of c is %d%n", c );
     }
   };
   r.run();
  }
}

An inner anonymous class is created within the App class.

final Runnable r = new Runnable() {
  @Override
  public void run() {
   System.out.printf( "The value of c is %d%n", c );
  }
};

This is compiled as a separate class file, App$1.class.

$ tree build/classes/java
build/classes/java
└── main
    └── demo
        ├── App$1.class
        └── App.class

Despite being a different class within the same package, the inner anonymous class is still allowed to access private members within the parent class.

More information about access control can be found in this tutorial.

Which access modifier should I pick?

Always start with the least visible access modifier, private, and then increase the visibility only if required. Remember that once you make something public it may be impossible to reduce its visibility.