/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.security; import java.util.ArrayList; import java.util.WeakHashMap; import org.apache.harmony.security.fortress.SecurityUtils; /** * {@code AccessController} provides static methods to perform access control * checks and privileged operations. */ public final class AccessController { private AccessController() { throw new Error("statics only."); } /** * A map used to store a mapping between a given Thread and * AccessControllerContext-s used in successive calls of doPrivileged(). A * WeakHashMap is used to allow automagical wiping of the dead threads from * the map. The thread (normally Thread.currentThread()) is used as a key * for the map, and a value is ArrayList where all AccessControlContext-s * are stored. * ((ArrayList)contexts.get(Thread.currentThread())).lastElement() - is * reference to the latest context passed to the doPrivileged() call. */ private static final WeakHashMap<Thread, ArrayList<AccessControlContext>> contexts = new WeakHashMap<Thread, ArrayList<AccessControlContext>>(); /** * Returns the result of executing the specified privileged action. Only the * {@code ProtectionDomain} of the direct caller of this method and the * {@code ProtectionDomain}s of all subsequent classes in the call chain are * checked to be granted the necessary permission if access checks are * performed. * <p> * If an instance of {@code RuntimeException} is thrown during the execution * of the {@code PrivilegedAction#run()} method of the given action, it will * be propagated through this method. * * @param action * the action to be executed with privileges * @return the result of executing the privileged action * @throws NullPointerException * if the specified action is {@code null} * @since Android 1.0 */ public static <T> T doPrivileged(PrivilegedAction<T> action) { if (action == null) { throw new NullPointerException("action can not be null"); } return doPrivilegedImpl(action, null); } /** * Returns the result of executing the specified privileged action. The * {@code ProtectionDomain} of the direct caller of this method, the {@code * ProtectionDomain}s of all subsequent classes in the call chain and all * {@code ProtectionDomain}s of the given context are checked to be granted * the necessary permission if access checks are performed. * <p> * If an instance of {@code RuntimeException} is thrown during the execution * of the {@code PrivilegedAction#run()} method of the given action, it will * be propagated through this method. * * @param action * the action to be executed with privileges * @param context * the {@code AccessControlContext} whose protection domains are * checked additionally * @return the result of executing the privileged action * @throws NullPointerException * if the specified action is {@code null} * @since Android 1.0 */ public static <T> T doPrivileged(PrivilegedAction<T> action, AccessControlContext context) { if (action == null) { throw new NullPointerException("action can not be null"); } return doPrivilegedImpl(action, context); } /** * Returns the result of executing the specified privileged action. Only the * {@code ProtectionDomain} of the direct caller of this method and the * {@code ProtectionDomain}s of all subsequent classes in the call chain are * checked to be granted the necessary permission if access checks are * performed. * <p> * If a checked exception is thrown by the action's run method, it will be * wrapped and propagated through this method. * <p> * If an instance of {@code RuntimeException} is thrown during the execution * of the {@code PrivilegedAction#run()} method of the given action, it will * be propagated through this method. * * @param action * the action to be executed with privileges * @return the result of executing the privileged action * @throws PrivilegedActionException * if the action's run method throws any checked exception * @throws NullPointerException * if the specified action is {@code null} * @since Android 1.0 */ public static <T> T doPrivileged(PrivilegedExceptionAction<T> action) throws PrivilegedActionException { if (action == null) { throw new NullPointerException("action can not be null"); } return doPrivilegedImpl(action, null); } /** * Returns the result of executing the specified privileged action. The * {@code ProtectionDomain} of the direct caller of this method, the {@code * ProtectionDomain}s of all subsequent classes in the call chain and all * {@code ProtectionDomain}s of the given context are checked to be granted * the necessary permission if access checks are performed. * <p> * If a checked exception is thrown by the action's run method, it will be * wrapped and propagated through this method. * <p> * If an instance of {@code RuntimeException} is thrown during the execution * of the {@code PrivilegedAction#run()} method of the given action, it will * be propagated through this method. * * @param action * the action to be executed with privileges * @param context * the {@code AccessControlContext} whose protection domains are * checked additionally * @return the result of executing the privileged action * @throws PrivilegedActionException * if the action's run method throws any checked exception * @throws NullPointerException * if the specified action is {@code null} * @since Android 1.0 */ public static <T> T doPrivileged(PrivilegedExceptionAction<T> action, AccessControlContext context) throws PrivilegedActionException { if (action == null) { throw new NullPointerException("action can not be null"); } return doPrivilegedImpl(action, context); } /** * The real implementation of doPrivileged() method. It pushes the passed * context into this thread's contexts stack, and then invokes * <code>action.run()</code>. The pushed context is then investigated in the * {@link #getContext()} which is called in the {@link #checkPermission}. */ private static <T> T doPrivilegedImpl(PrivilegedExceptionAction<T> action, AccessControlContext context) throws PrivilegedActionException { Thread currThread = Thread.currentThread(); ArrayList<AccessControlContext> a = null; try { // currThread==null means that VM warm up is in progress if (currThread != null && contexts != null) { synchronized (contexts) { a = contexts.get(currThread); if (a == null) { a = new ArrayList<AccessControlContext>(); contexts.put(currThread, a); } } a.add(context); } return action.run(); } catch (Exception ex) { // Errors automagically go through - they are not catched by this // block // Unchecked exceptions must pass through without modification if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } // All other (==checked) exceptions get wrapped throw new PrivilegedActionException(ex); } finally { if (currThread != null) { // No need to sync() here, as each given 'a' will be accessed // only from one Thread. 'contexts' still need sync() however, // as it's accessed from different threads simultaneously if (a != null) { // it seems I will never have here [v.size() == 0] a.remove(a.size() - 1); } } } } /** * The real implementation of appropriate doPrivileged() method.<br> * It pushes the passed context into this thread's stack of contexts and * then invokes <code>action.run()</code>.<br> * The pushed context is then investigated in the {@link #getContext()} * which is called in the {@link #checkPermission}. */ private static <T> T doPrivilegedImpl(PrivilegedAction<T> action, AccessControlContext context) { Thread currThread = Thread.currentThread(); if (currThread == null || contexts == null) { // Big boom time - VM is starting... No need to check permissions: // 1st, I do believe there is no malicious code available here for // this moment // 2d, I cant use currentThread() as a key anyway - when it will // turn into the real Thread, I'll be unable to retrieve the value // stored with 'currThread==null' as a key. return action.run(); } ArrayList<AccessControlContext> a = null; try { synchronized (contexts) { a = contexts.get(currThread); if (a == null) { a = new ArrayList<AccessControlContext>(); contexts.put(currThread, a); } } a.add(context); return action.run(); } finally { // No need to sync() here, as each given 'a' will be accessed // only from one Thread. 'contexts' still need sync() however, // as it's accessed from different threads simultaneously if (a != null) { a.remove(a.size() - 1); } } } /** * Checks the specified permission against the vm's current security policy. * The check is performed in the context of the current thread. This method * returns silently if the permission is granted, otherwise an {@code * AccessControlException} is thrown. * <p> * A permission is considered granted if every {@link ProtectionDomain} in * the current execution context has been granted the specified permission. * If privileged operations are on the execution context, only the {@code * ProtectionDomain}s from the last privileged operation are taken into * account. * <p> * This method delegates the permission check to * {@link AccessControlContext#checkPermission(Permission)} on the current * callers' context obtained by {@link #getContext()}. * * @param perm * the permission to check against the policy * @throws AccessControlException * if the specified permission is not granted * @throws NullPointerException * if the specified permission is {@code null} * @see AccessControlContext#checkPermission(Permission) * * @since Android 1.0 */ public static void checkPermission(Permission perm) throws AccessControlException { if (perm == null) { throw new NullPointerException("permission can not be null"); } getContext().checkPermission(perm); } /** * Returns array of ProtectionDomains from the classes residing on the stack * of the current thread, up to and including the caller of the nearest * privileged frame. Reflection frames are skipped. The returned array is * never null and never contains null elements, meaning that bootstrap * classes are effectively ignored. */ private static native ProtectionDomain[] getStackDomains(); /** * Returns the {@code AccessControlContext} for the current {@code Thread} * including the inherited access control context of the thread that spawned * the current thread (recursively). * <p> * The returned context may be used to perform access checks at a later * point in time, possibly by another thread. * * @return the {@code AccessControlContext} for the current {@code Thread} * @see Thread#currentThread * @since Android 1.0 */ public static AccessControlContext getContext() { // duplicates (if any) will be removed in ACC constructor ProtectionDomain[] stack = getStackDomains(); Thread currThread = Thread.currentThread(); if (currThread == null || contexts == null) { // Big boo time. No need to check anything ? return new AccessControlContext(stack); } ArrayList<AccessControlContext> threadContexts; synchronized (contexts) { threadContexts = contexts.get(currThread); } AccessControlContext that; if ((threadContexts == null) || (threadContexts.size() == 0)) { // We were not in doPrivileged method, so // have inherited context here that = SecurityUtils.getContext(currThread); } else { // We were in doPrivileged method, so // Use context passed to the doPrivileged() that = threadContexts.get(threadContexts.size() - 1); } if (that != null && that.combiner != null) { ProtectionDomain[] assigned = null; if (that.context != null && that.context.length != 0) { assigned = new ProtectionDomain[that.context.length]; System.arraycopy(that.context, 0, assigned, 0, assigned.length); } ProtectionDomain[] allpds = that.combiner.combine(stack, assigned); if (allpds == null) { allpds = new ProtectionDomain[0]; } return new AccessControlContext(allpds, that.combiner); } return new AccessControlContext(stack, that); } }