Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6916398

RFE: Inadequately granular security manager permissions for JDK logger

XMLWordPrintable

    • Icon: Enhancement Enhancement
    • Resolution: Unresolved
    • Icon: P3 P3
    • None
    • 6u10
    • core-libs

      A DESCRIPTION OF THE REQUEST :
      Applications often use logging for their own purposes. However, there are some libraries out there which, for whatever reason, invoke code to call either LogManager.readConfiguration() or LogManager.reset() - which will of course clobber the application's logging configuration. While this is not good behavior for a library, if you don't control the source code to the library, the only option may be to block such calls via a SecurityManager.

      However, java.util.logging.LogManager uses a single Permission object,
      private Permission ourPermission = new LoggingPermission("control", null);
      for logger access control.

      What this means is that there is no simple way to forbid reconfiguring the VM-wide logging system, without also forbidding use of Logger altogether (Logger.getLogger() -> LogManager.addLogger() -> LogManager.doSetLevel() -> LogManager.checkAccess()).

      SecurityManager.getClassContext() is also inadequate to determine whether the security check came through readConfiguration() or reset() and block it, rather than through innocently creating a new Logger. While it might be possible to predict the list of classes using getClassContext() to get this right, there is no reason to assume that the JDK will keep calling-class order backward compatible forever.

      The only way to do this reliably at the application-level is to construct an Exception, iterate its StackTraceElements and read the method name. While this works reliably, constructing an Exception and unwinding its stack for potentially every logging call in an application that uses logging heavily is unacceptable, performance-wise - the cost of constructing an Exception grows with call-stack-depth.

      JUSTIFICATION :
      For details of the origin of this issue, see:
      https://netbeans.org/bugzilla/show_bug.cgi?id=178013

      In this case, an Ant task called by NetBeans was calling Logger.readConfiguration(), resetting the entire logging subsystem for NetBeans VM to one using the JDK's FileLogger, which naively uses synchronization to serialize log writes (IMO this is a bug all its own - logging should not be a blocking operation except in very rare cases, as it means logging code affects liveness).

      This caused nearly instantaneous deadlocks, since NetBeans uses Logger heavily for numerous purposes, and is heavily multithreaded.

      The evil hack I had to do in NetBeans SecurityManager can be found here:
      http://bugzilla-attachments-178013.netbeans.org/bugzilla/attachment.cgi?id=92192


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      A better solution would be to use a second Permission object - one for logger configuration, and another for logger creation.

      ---------- BEGIN SOURCE ----------
      This is the most concise example I could come up with. Try swapping the lines that define the variable "sm" - this is the workaround we are currently using in NetBeans (only when assertions are enabled, to avoid performance problems in production builds).

      public class Main {
        public static void main(String[] args) {
          AbstractSM sm = new SM();
      // AbstractSM sm = new EvilWorkaround();
          System.setSecurityManager(sm);
          LogManager.getLogManager(); //allow class init
          sm.activated = true;
          try {
            LogManager.getLogManager().reset();
          } catch (SecurityException se) {
            System.err.println("Expected security exception from LogManager.reset() caught");
          }
          try {
            Logger.getLogger("foo").log(Level.SEVERE, "bar");
          } catch (SecurityException e) {
            System.err.println("Logging system is unusable");
            e.printStackTrace();
            System.exit(1);
          }
          sm.activated = false;
          System.err.println("Logging system working correctly - reset() " +
                  "and readConfiguration() blocked, logger creation not");
        }

        private static class AbstractSM extends SecurityManager {
          protected boolean activated;
        }

        private static final class SM extends AbstractSM {
          @Override
          public void checkPermission(Permission perm) {
            if (!activated) {
              return;
            }
            if (perm instanceof java.util.logging.LoggingPermission) {
              LoggingPermission p = (LoggingPermission) perm;
              if ("control".equals(p.getName())) { //the only name used, alas
                throw new SecurityException("Cannot reset logger");
              }
            }
          }
        }

        private static final class EvilWorkaround extends AbstractSM {
          @Override
          public void checkPermission(Permission perm) {
            if (!activated) {
              return;
            }
            if (perm instanceof java.util.logging.LoggingPermission) {
              LoggingPermission p = (LoggingPermission) perm;
              if ("control".equals(p.getName())) { //the only name used, alas
                //In a deep stack, this approach can seriously impact performance
                Throwable t = new Exception().fillInStackTrace();

                for (StackTraceElement e : t.getStackTrace()) {
                  if ("java.util.logging.LogManager".equals(e.getClassName()) && "reset".equals(e.getMethodName())) { //NOI18N
                    SecurityException se = new SecurityException("Illegal attempt to reset system logger"); //NOI18N
                    throw se;
                  }
                  if ("java.util.logging.LogManager".equals(e.getClassName()) && "readConfiguration".equals(e.getMethodName())) { //NOI18N
                    SecurityException se = new SecurityException("Illegal attempt to replace system logger configuration"); //NOI18N
                    throw se;
                  }
                  if ("java.util.logging.Logger".equals(e.getClassName()) && "getLogger".equals(e.getMethodName())) { //NOI18N
                    return;
                  }
                }
              }
            }
          }
        }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      See class EvilWorkaround in the example code.

            Unassigned Unassigned
            ndcosta Nelson Dcosta (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Imported:
              Indexed: