Executing Privileged Code

There are cases when it is not known which specific permissions must be verified—for example, when the program logic is supplied before the actual permission configuration has been created, or when such a configuration is expected to change in the future. In these cases, the program can use the Access-Controller.doPrivileged() method to execute the computation with privileges enabled. This method accepts an object that implements the PrivilegedAction or the PrivilegedExceptionAction interface, depending on whether this computation can potentially throw a checked exception.

Click here to view code image

String content = AccessController.doPrivileged(
    new PrivilegedAction<String>() {
      @Override
    public String run() {                                      // (1)
      String result = null;
      try {
        result = Files.readString(Path.of(“/FileSystemPath”)); // (2)
      } catch (IOException ex) {
        // …                                                    (3)
      }
      return result;
    }
  });

The numbered comments below correspond to the numbered lines in the code:

  1. The implementation of the PrivilegedAction interface must override the run() method.
  2. The implementation of the run() method contains logic that has access to restricted resources.
  3. Any checked exceptions thrown must be handled within the run() method. If checked exceptions are to propagate outside of the run() method, then the PrivilegedExceptionAction interface should be implemented instead.

The method doPrivileged() is overloaded and can accept additional parameters, including specific permissions that must be satisfied for the privileged action to succeed.

26.4 Additional Security Guidelines

Attention is drawn to some selected security guidelines in this section.

Accessing the File System

When accessing the file system or performing I/O operations, consider the following recommendations:

  • Remove redundant elements from the path and convert it to a canonical form using the methods normalize() and toRealPath() to guard against directory traversal attacks, such as attempts to guess the directory structure by using relative paths (§21.3, p. 1297).
  • Verify the file sizes and lengths of I/O streams before attempting to process information.
  • Monitor I/O operations to detect excessive use of resources and terminate operations that process excessive amounts of data.
  • Release resources as soon as possible, and terminate and time out long operations.

Deserialization

Be mindful of deserialization (§20.5, p. 1261), as it can create objects that are beyond the control of the application and that can bypass normal constructor invocations. Essentially, the deserialization process circumvents normal secure object creation, and thus must be considered a potential risk factor, especially if a serialized object arrived from an untrusted source. Thus it is a good idea to validate such objects as soon as they are created.

Class Design

Another important set of guidelines is related to class design.

  • Always strive to enforce tight encapsulation using the most restrictive access permissions possible, also known as principle of least privilege (PoLP). Consider utilizing the Java Platform Module System for further reinforcement of encapsulation (§19.3, p. 1173), including restrictions imposed on the use of reflection (§19.8, p. 1191).
  • Make objects immutable (§6.7, p. 356). Don’t forget that even though a variable referencing an object can be marked as final, it does not mean that the object itself is immutable. It may be a good idea to create cloned object replicas of otherwise mutable objects to avoid unsafe memory mutable operations.
  • When extending classes and overriding methods, carefully consider the semantics of the superclass methods to override. Also, remember that any modification made to a superclass, such as changing method implementation or introducing additional methods, may have a cascading effect on all its subclasses in the inheritance hierarchy (§5.1, p. 191).
  • Always mark non-private classes and methods as final if they are not intended to be extended or overridden (§5.5, p. 225).
  • Use factory methods to validate values before invoking a constructor to actually create an object.
  • Avoid invoking overridden methods from constructors (§10.9, p. 555).
  • Declare constructors as private if the class should not be instantiated.

Finally, consider never disabling bytecode verification with command-line options such as -Xverify:none or –noverify that protect bytecode against tampering and dangerous behavior.

Leave a Reply

Your email address will not be published. Required fields are marked *