为什么我无法在 SecurityManager 下关闭自己的 ExecutorService?

发布于 2024-09-16 14:50:16 字数 6235 浏览 6 评论 0原文

在默认安全管理器下,如果我创建 ExecutorService (ThreadPoolExecutor 在这种情况下),我无法关闭它, shutdown() 只是调用 checkPermission("modifyThread") ,因此立即死亡:

import java.util.concurrent.*;

class A {
    public static void main( String[] args) {
        Thread ct = Thread.currentThread();
        System.out.println("current thread: " + ct);
        ct.checkAccess(); // we have access to our own thread...
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(
            1, // one core thread
            1, // doesn't matter because queue is unbounded
            0, TimeUnit.SECONDS, // doesn't matter in this case
            new LinkedBlockingQueue<Runnable>(), /* unbound queue for
                                                  * our single thread */
            new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    // obviously never gets called as we don't add any work
                    System.out.println("making thread");
                    return new Thread(r);
                }
            }
        );
        tpe.shutdown(); // raises security exception
    }
}

Sun JDK:

$ java -Djava.security.manager A 当前线程: 线程[main,5,main] 线程“main”中的异常 java.security.AccessControlException:访问被拒绝(java.lang.RuntimePermissionmodifyThread) 在 java.security.AccessControlContext.checkPermission(AccessControlContext.java:323) 在 java.security.AccessController.checkPermission(AccessController.java:546) 在 java.lang.SecurityManager.checkPermission(SecurityManager.java:532) 在 java.util.concurrent.ThreadPoolExecutor.shutdown(ThreadPoolExecutor.java:1094) 在A.main(A.java:22)

OpenJDK 处:

$ java -Djava.security.manager A 当前线程: 线程[main,5,main] 线程“main”中的异常 java.security.AccessControlException:访问被拒绝(java.lang.RuntimePermissionmodifyThread) 在 java.security.AccessControlContext.checkPermission(AccessControlContext.java:342) 在 java.security.AccessController.checkPermission(AccessController.java:553) 在 java.lang.SecurityManager.checkPermission(SecurityManager.java:549) 在 java.util.concurrent.ThreadPoolExecutor.checkShutdownAccess(ThreadPoolExecutor.java:711) 在java.util.concurrent.ThreadPoolExecutor.shutdown(ThreadPoolExecutor.java:1351) 在A.main(A.java:22)

为什么??????创建只有您控制的线程池并关闭它有什么安全隐患?这是实现中的错误,还是我遗漏了什么?

让我们看看 ExecutorService.shutdown 说...

启动有序关闭,其中执行先前提交的任务,但不会接受新任务。如果已经关闭,调用不会产生额外的效果。

抛出: SecurityException - 如果安全管理器存在并且关闭此 ExecutorService 可能会操作不允许调用者修改的线程,因为它不持有 RuntimePermission("modifyThread"),或者安全管理器的 checkAccess 方法拒绝访问。

这……实在是太模糊了。该规范没有提及关于 ExecutorService 生命周期期间创建的任何“系统线程”,此外,它允许您提供自己的线程,这证明了应该当您这样做时,涉及“系统线程”。 (正如上面我的示例源代码中所做的那样)

感觉 Java SE 实现者看到 shutdown 可能会引发 SecurityException,所以他们就像,“哦,好吧,我“只需在此处添加随机安全检查以确保合规性”...

问题是,阅读 OpenJDK 源代码 (openjdk-6-src-b20-21_jun_2010),事实证明唯一方式 <任何线程的创建都是通过调用您提供的ThreadFactory (在我的测试用例中从未调用过,因为我没有创建任何工作,并且我不调用 prestartCoreThreadpreStartAllCoreThreads)。 ,在 OpenJDK 的 ThreadPoolExecutor 中没有明显原因就完成了安全检查(就像在 sun-jdk-1.6 中所做的那样,但我没有源代码):

/**
 * Initiates an orderly shutdown in which previously submitted
 * tasks are executed, but no new tasks will be accepted.
 * Invocation has no additional effect if already shut down.
 *
 * @throws SecurityException {@inheritDoc}
 */
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

在执行任何操作之前调用 checkShutdownAccess...

/**
 * If there is a security manager, makes sure caller has
 * permission to shut down threads in general (see shutdownPerm).
 * If this passes, additionally makes sure the caller is allowed
 * to interrupt each worker thread. This might not be true even if
 * first check passed, if the SecurityManager treats some threads
 * specially.
 */
private void checkShutdownAccess() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkPermission(shutdownPerm);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                security.checkAccess(w.thread);
        } finally {
            mainLock.unlock();
        }
    }
}

因此 您可以看到,它无条件调用安全管理器上的checkPermission(shutdownPerm)...shutdownPerm 定义为... 私有静态最终 RuntimePermission shutdownPerm = 新的 RuntimePermission("修改线程");

...据我所知,这绝对没有意义,因为 modifyThread 意味着访问系统线程,并且没有系统线程在这里玩,事实上,根本没有线程,因为我没有提交任何工作或预启动,即使有线程,它们也是我的线程,因为我传入了一个线程工厂。该规范没有提及任何关于神奇死亡的内容,除了如果涉及系统线程(它们不涉及),可能会出现 SecurityException

基本上,为什么我不能删除检查系统线程访问权限的行?我认为没有任何安全隐患需要这样做。怎么没有其他人遇到这个问题???我在问题跟踪器上看到过一篇文章,他们通过将对 shutdownNow 的调用更改为 shutdown 来“解决”了这个问题,显然,这并没有为他们解决问题。

Under the default security manager, if I create an ExecutorService (ThreadPoolExecutor in this case), I cannot shut it down, shutdown() just calls checkPermission("modifyThread") and thus immediately dies:

import java.util.concurrent.*;

class A {
    public static void main( String[] args) {
        Thread ct = Thread.currentThread();
        System.out.println("current thread: " + ct);
        ct.checkAccess(); // we have access to our own thread...
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(
            1, // one core thread
            1, // doesn't matter because queue is unbounded
            0, TimeUnit.SECONDS, // doesn't matter in this case
            new LinkedBlockingQueue<Runnable>(), /* unbound queue for
                                                  * our single thread */
            new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    // obviously never gets called as we don't add any work
                    System.out.println("making thread");
                    return new Thread(r);
                }
            }
        );
        tpe.shutdown(); // raises security exception
    }
}

Sun JDK:

$ java -Djava.security.manager A
current thread: Thread[main,5,main]
Exception in thread "main" java.security.AccessControlException: access denied (java.lang.RuntimePermission modifyThread)
at
java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
at
java.security.AccessController.checkPermission(AccessController.java:546)
at
java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at
java.util.concurrent.ThreadPoolExecutor.shutdown(ThreadPoolExecutor.java:1094)
at A.main(A.java:22)

OpenJDK:

$ java -Djava.security.manager A
current thread: Thread[main,5,main]
Exception in thread "main" java.security.AccessControlException: access denied (java.lang.RuntimePermission modifyThread)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:342)
at java.security.AccessController.checkPermission(AccessController.java:553)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.util.concurrent.ThreadPoolExecutor.checkShutdownAccess(ThreadPoolExecutor.java:711)
at java.util.concurrent.ThreadPoolExecutor.shutdown(ThreadPoolExecutor.java:1351)
at A.main(A.java:22)

Why??????? What are the security implications of creating a thread pool that only you control, and shutting it down? Is this a bug in the implementations, or am I missing something?

Let's see what the spec for ExecutorService.shutdown says...

Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation has no additional effect if already shut down.

Throws:
SecurityException - if a security manager exists and shutting down this ExecutorService may manipulate threads that the caller is not permitted to modify because it does not hold RuntimePermission("modifyThread"), or the security manager's checkAccess method denies access.

This... is about as vague as it gets. The spec says nothing about any "system threads" being made during the life-cycle of an ExecutorService and furthermore, it lets you supply your own threads which is proof that there should be no "system threads" involved when you do that. (As done above in my sample source)

It feels like the Java SE implementors saw that it's possible for shutdown to raise SecurityException, so they were just like, "oh okay I'll just add a random security check here for compliance"...

The thing is, reading over OpenJDK source (openjdk-6-src-b20-21_jun_2010), it turns out that the only way any thread is ever created, is by calling your supplied ThreadFactory (which is never called in my testcase since I don't create any work, and I don't call prestartCoreThread or preStartAllCoreThreads). The security check is thus done for no apparent reason in OpenJDK's ThreadPoolExecutor (as is done in sun-jdk-1.6 but I don't have the source):

/**
 * Initiates an orderly shutdown in which previously submitted
 * tasks are executed, but no new tasks will be accepted.
 * Invocation has no additional effect if already shut down.
 *
 * @throws SecurityException {@inheritDoc}
 */
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

checkShutdownAccess is called before doing anything...

/**
 * If there is a security manager, makes sure caller has
 * permission to shut down threads in general (see shutdownPerm).
 * If this passes, additionally makes sure the caller is allowed
 * to interrupt each worker thread. This might not be true even if
 * first check passed, if the SecurityManager treats some threads
 * specially.
 */
private void checkShutdownAccess() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkPermission(shutdownPerm);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                security.checkAccess(w.thread);
        } finally {
            mainLock.unlock();
        }
    }
}

As you can see, it unconditionally invokes checkPermission(shutdownPerm) on the security manager.... shutdownPerm is defined as...
private static final RuntimePermission shutdownPerm =
new RuntimePermission("modifyThread");

...which makes absolutely no sense as far as I can tell because modifyThread implies access to system threads, and there are no system threads in play here, in fact, there are no threads at all because I didn't submit any work or prestart, and even if there were threads, they'd be my threads because I passed in a ThreadFactory. The spec doesn't say anything about magically dying, other than that if system threads are involved (they aren't), there could be a SecurityException.

Basically, why can't I just remove the line that checks access to system threads? I see no security implication calling for it. And how has nobody else come across this issue??? I've seen a post on an issue tracker where they "resolved" this issue by changing a call to shutdownNow to shutdown, obviously, that didn't fix it for them.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

爱,才寂寞 2024-09-23 14:50:16

这很简单:你不能在主线程组中执行此操作。它部分是为小程序设计的。
复制关闭方法的想法为什么?
如果出现问题,您可以自由地使用 PrivilegedAction 来调用关闭。请记住,Thread.interrupt() 看似无辜,但它可能也会抛出 SecurityException

回答这个问题:只要确保您授予自己的代码权限,您就很高兴。另外,“modifyThread”也可以自由授予,它主要由小程序使用。

至于不受信任的代码:嗯,不受信任的代码甚至不应该处理其 ThreadGroup 之外的线程,因此提供 API 来创建 ThreadPool,并允许关闭调用者创建的线程。您可以根据调用者授予权限。

希望这能有所帮助(不过,问号的数量清楚地表明了绝望和最大的烦恼)

    /*
     * Conceptually, shutdown is just a matter of changing the
     * runState to SHUTDOWN, and then interrupting any worker
     * threads that might be blocked in getTask() to wake them up
     * so they can exit. Then, if there happen not to be any
     * threads or tasks, we can directly terminate pool via
     * tryTerminate.  Else, the last worker to leave the building
     * turns off the lights (in workerDone).
     *
     * But this is made more delicate because we must cooperate
     * with the security manager (if present), which may implement
     * policies that make more sense for operations on Threads
     * than they do for ThreadPools. This requires 3 steps:
     *
     * 1. Making sure caller has permission to shut down threads
     * in general (see shutdownPerm).
     *
     * 2. If (1) passes, making sure the caller is allowed to
     * modify each of our threads. This might not be true even if
     * first check passed, if the SecurityManager treats some
     * threads specially. If this check passes, then we can try
     * to set runState.
     *
     * 3. If both (1) and (2) pass, dealing with inconsistent
     * security managers that allow checkAccess but then throw a
     * SecurityException when interrupt() is invoked.  In this
     * third case, because we have already set runState, we can
     * only try to back out from the shutdown as cleanly as
     * possible. Some workers may have been killed but we remain
     * in non-shutdown state (which may entail tryTerminate from
     * workerDone starting a new worker to maintain liveness.)
     */

It's quite simple: you can't do it in the main thread group. It's partly designed for applets.
Copy from shutdown method idea why?
You can freely use PrivilegedAction to call shutdown if that's an issue. Keep in mind that Thread.interrupt() as innocent it might look also throws SecurityException.

To answer the question: just make sure you grant your own code the permissions and you're happy. Alternatively "modifyThread" can be granted freely as well, it's used mostly by applets.

As for untrusted code: well, the untrusted code is not even supposed to deal w/ threads outside its ThreadGroup, so provide the API to create the ThreadPool, and allow shutdown for such created by the caller. You can GRANT the permission based on the caller.

Hope this helped a bit (the amount of question marks clearly shows desperation and utmost annoyance, though)

    /*
     * Conceptually, shutdown is just a matter of changing the
     * runState to SHUTDOWN, and then interrupting any worker
     * threads that might be blocked in getTask() to wake them up
     * so they can exit. Then, if there happen not to be any
     * threads or tasks, we can directly terminate pool via
     * tryTerminate.  Else, the last worker to leave the building
     * turns off the lights (in workerDone).
     *
     * But this is made more delicate because we must cooperate
     * with the security manager (if present), which may implement
     * policies that make more sense for operations on Threads
     * than they do for ThreadPools. This requires 3 steps:
     *
     * 1. Making sure caller has permission to shut down threads
     * in general (see shutdownPerm).
     *
     * 2. If (1) passes, making sure the caller is allowed to
     * modify each of our threads. This might not be true even if
     * first check passed, if the SecurityManager treats some
     * threads specially. If this check passes, then we can try
     * to set runState.
     *
     * 3. If both (1) and (2) pass, dealing with inconsistent
     * security managers that allow checkAccess but then throw a
     * SecurityException when interrupt() is invoked.  In this
     * third case, because we have already set runState, we can
     * only try to back out from the shutdown as cleanly as
     * possible. Some workers may have been killed but we remain
     * in non-shutdown state (which may entail tryTerminate from
     * workerDone starting a new worker to maintain liveness.)
     */
木槿暧夏七纪年 2024-09-23 14:50:16

听起来像是一个懒惰和/或安全的实现。它不检查是否涉及其他线程,而是假设其中一些线程涉及。最好抛出安全异常,而不是留下潜在的安全漏洞。

Sounds like a lazy and/or safe implementation. Instead of checking if other threads are involved, it just assumes some are. Better to throw a security exception rather than leave a potential security hole.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文