Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Noncompliant Code Example (hashCode)

This noncompliant code example shows a LicenseManager class that maintains a licenseMap. The map stores a LicenseType and license value pair.

...

This setup is vulnerable to an attacker  who who extends the LicenseType class and overrides the equals() and hashCode() methods:

Code Block
public class CraftedLicenseType extends LicenseType {
    private static int guessedHashCode = 0;
    @Override
    public int hashCode() {
        // Returns a new hashCode to test every time get() is called
        guessedHashCode++;
        return guessedHashCode;
    }
    @Override
    public boolean equals(Object arg) {
        // Always returns true
        return true;
    }
}

The Following is the malicious client program is shown below.:

Code Block
public class DemoClient {
    public static void main(String[] args) {
        LicenseManager licenseManager = new LicenseManager();
        for (int i = 0; i <= Integer.MAX_VALUE; i++) {
            Object guessed = licenseManager
                    .getLicenseKey(new CraftedLicenseType());
            if (guessed != null) {
                System.out.println(guessed); // prints ABC-DEF-PQR-XYZ
            }
        }
    }
}

...

This compliant solution uses an IdentityHashMap rather than a HashMap to store the license information.:

Code Block
bgColor#ccccff
public class LicenseManager {
    Map<LicenseType, String> licenseMap = new IdentityHashMap<LicenseType, String>();

  // ...
}

According to the Java API class IdentityHashMap documentation [API 2006],

This class implements the Map interface with a hash table, using reference-equality in place of object-equality when comparing keys (and values). In other words, in an IdentityHashMap, two keys k1 and k2 are considered equal if and only if (k1==k2). (In normal Map implementations (like HashMap) two keys k1 and k2 are considered equal if and only if (k1==null ? k2==null : k1.equals(k2)).)

Consequently, the overridden methods cannot expose internal class details. The client program can continue to add license keys and even retrieve the added key-value pairs, as demonstrated by the following client code.:

Code Block
public class DemoClient {
    public static void main(String[] args) {
        LicenseManager licenseManager = new LicenseManager();
        LicenseType type = new LicenseType();
        type.setType("custom-license-key");
        licenseManager.setLicenseKey(type, "CUS-TOM-LIC-KEY");
        Object licenseKeyValue = licenseManager.getLicenseKey(type);
        System.out.println(licenseKeyValue); // printsPrints CUS-TOM-LIC-KEY
    }
}

 

Compliant Solution (final class)

This compliant solution declares the LicenseType class final so that its methods cannot be overridden.:

Code Block
bgColor#ccccff
final class LicenseType {
  // ...
} 

...

This noncompliant code example consists of a Widget class and a LayoutManager class containing a set of widgets.;

Code Block
bgColor#ffcccc
public class Widget {
    private int noOfComponents;
    public Widget(int noOfComponents) {
        this.noOfComponents = noOfComponents;
    }
    public int getNoOfComponents() {
        return noOfComponents;
    }
    public final void setNoOfComponents(int noOfComponents) {
        this.noOfComponents = noOfComponents;
    }
    public boolean equals(Object o) {
        if (o == null || !(o instanceof Widget)) {
            return false;
        }
        Widget widget = (Widget) o;
        return this.noOfComponents == widget.getNoOfComponents();
    }
    @Override
    public int hashCode() {
        int res = 31;
        res = res * 17 + noOfComponents;
        return res;
    }
}
public class LayoutManager {
    private Set<Widget> layouts = new HashSet<Widget>();
    public void addWidget(Widget widget) {
        if (!layouts.contains(widget)) {
            layouts.add(widget);
        }
    }
    public int getLayoutSize() {
        return layouts.size();
    }
}

An attacker can extend the Widget class as a Navigator widget and override the hashCode() method.:

Code Block
public class Navigator extends Widget {
    public Navigator(int noOfComponents) {
        super(noOfComponents);
    }
    @Override
    public int hashCode() {
        int res = 31;
        res = res * 17;
        return res;
    }
}

Client code follows.:

Code Block
        Widget nav = new Navigator(1);
        Widget widget = new Widget(1);
        LayoutManager manager = new LayoutManager();
        manager.addWidget(nav);
        manager.addWidget(widget);
        System.out.println(manager.getLayoutSize()); // printsPrints 2

The set layouts is expected to contain just one item because the number of components for both the navigator and widget being added is 1. However, the getLayoutSize() method returns 2.

The reason for this discrepancy is that the hashCode() method of Widget is used only once when the widget is added to the set. When the navigator is added, the hashCode() method provided by the Navigator class is used. 

Compliant Solution (final class)

This compliant solution declares the Widget class final so that its methods cannot be overridden.:

Code Block
bgColor#ccccff
public final class Widget {
    // ...
}

Noncompliant Code Example (run())

In this noncompliant code example, class Worker and its subclass SubWorker each contain a startThread()method intended to start a thread.

...

the client may expect Parent and Child to be printed. However, however, Child is printed twice . This is because the overridden method run() is invoked both times when that a new thread is started.

Compliant Solution

...

The client code is also modified to start the parent and child threads separately. This program produces the expected output.:

Code Block
Worker w1 = new Worker();
w1.startThread("parent-thread");
Worker w2 = new SubWorker();
w2.startThread("child-thread");

...