You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 7 Next »

Interfaces are used to group together all the methods that a class promises to publicly expose. The implementing classes are obliged to provide concrete implementations for all these methods. Interfaces are a necessary ingredient of the public API and once released, it can be very hard to remediate any flaws without breaking any code that implements the older version. By far, the security specific repercussions include:

  • The interface changes resulting from security fixes can severely impair the contracts of the implementing classes. It is even possible that a security fix introduced in a later version is accompanied by modification to an unrelated interface. This can prevent the client from implementing the security fix since the new interface would impose additional implementation burden on it.
  • If an insider crafts changes to an interface or someone accidentally makes modifications, most of the client code that implements the interface will break (Denial of Service). This is particularly pernicious in distributed Java based applications.
  • Implementers can provide skeletal implementations that the clients can directly extend, however, such code can adversely affect the behavior of the subclasses. When these are not provided, the subclasses are forced to provide dummy implementations. This fosters an environment where comments such as 'ignore this code, does nothing', occur incessantly. Such code may never even get tested.
  • If there is a security flaw in a public API (consider ThreadGroups, CON01-J. Avoid using ThreadGroup APIs) it will persist throughout the lifetime of the application.

Noncompliant Code Example

In this example, an interface User was frozen with two methods authenticate() and subscribe(). Sometime later, the providers released a free service that did not rely on authentication. The addition of the freeService() method, unfortunately, broke all the client code that implemented the interface. Moreover, the implementers who wished to use only freeService had to face the onus of also providing the other two methods which polluted the API, for reasons discussed earlier.

public interface User {
  boolean authenticate(String username, char[] password);
  void subscribe(int noOfDays);
  void freeService(); // introduced after the class is publicly released
}

Noncompliant Code Example

An alternative idea is to prefer abstract classes for dealing with constant evolution, but this comes at the cost of the loss in flexibility that interfaces offer. One notable pattern is for the provider to distribute an abstract skeletal class that implements the evolving interface. The skeletal class can selectively implement a few methods and force the extending classes to provide concrete implementations of the others. If a new method is added to the interface, the skeletal class can provide a non-abstract default implementation that the extending class can optionally override. This pattern is dangerous since a provider is unaware of the extending class's code and may choose an implementation that will introduce security weaknesses in the client API.

public interface User {
  boolean authenticate(String username, char[] password);
  void subscribe(int noOfDays);
  void freeService(); // introduced after the class is publicly released
}

abstract class SkeletalUser implements User {
  public abstract boolean authenticate(String username, char[] password);
  public abstract void subscribe(int noOfDays);
  public void freeService() { /* added later, provide implementation and re-release class*/ }
}

class Client extends SkeletalUser {
  // implements authenticate() and subscribe(), not freeService()
}

Compliant Solution (1)

A better design strategy would have been to anticipate the future evolution of the service. The core functionality should have been implemented in the User interface and in this case, only the premium service may be required to extend from it. To avail of the new free service, an existing class could then choose to simply implement the new interface FreeUser or just completely ignore it.

public interface User {
  boolean authenticate(String username, char[] password);
}

public interface PremiumUser extends User {
  void subscribe(int noOfDays);
}

public interface FreeUser {
  void freeService();
}

Compliant Solution (2)

Another solution is to throw an exception from within the new method defined in the implementing subclass.

class Client implements User {
  public void freeService() {
    throw new AbstractMethodError();
  } 
}

Compliant Solution (3)

Although allowable, a less flexible method is to pass on the implementation of the method to subclasses of the client's core interface implementing class.

abstract class Client implements User  {
  public abstract void freeService(); // delegate implementation of new method to subclasses
  // other concrete implementations
}

A variant of compliant solution (2) can also be applied here by throwing an exception from freeService after making the class (and method) non-abstract.

Risk Assessment

Failing to publish stable, flaw-free interfaces can break the contracts of the implementing classes, pollute the client API and possibly introduce security weaknesses in the implementing classes.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

MSC09-J

low

probable

high

P2

L3

References

[[Bloch 08]] Item 18: "Prefer interfaces to abstract classes"


MSC38-J. Make sensitive classes noncloneable      49. Miscellaneous (MSC)      MSC30-J. Generate truly random numbers

  • No labels