在仔细锁定但不受信任的代码上使用Thread.stop() [英] Using Thread.stop() on carefully locked down, but untrusted, code

查看:89
本文介绍了在仔细锁定但不受信任的代码上使用Thread.stop()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道不推荐使用Thread.stop(),这是有充分理由的:通常,它不安全.但是,这并不意味着它永远都不安全……据我所知,在我要使用它的上下文中它是安全的.据我所知,我别无选择.

上下文是用于两人策略游戏的第三方插件:国际象棋将作为工作示例.需要为第三方代码提供当前的电路板状态,并(例如)有10秒的时间来决定其移动方向.它可以返回其移动并在允许的时间内终止,或者可以在需要时发出其当前首选移动的信号.如果时限到期,则应在其音轨中将其停止,并播放其最近的首选动作.

编写插件以根据请求正常停止是不能选择:我需要能够使用任意不受信任的第三方插件.因此,我必须采取某种方式强制终止它.

这就是我要锁定它的方法:

  1. 该插件的类放入其自己的线程中,并放入其自己的线程组中.
  2. 它们装有一个类加载器,该类加载器具有严格的SecurityManager限制:所有类可以做的就是数字运算.
  3. 这些类未获得对任何其他线程的引用,因此它们无法在未创建的任何内容上使用Thread.join().
  4. 该插件仅从主机系统获得对对象的两个引用.一种是棋盘的状态;另一种是棋盘的状态.这是一个深层副本,之后会被丢弃,因此它进入不一致状态并不重要.另一个是一个简单的对象,它允许插件设置其当前的首选移动方式.
  5. 我正在检查CPU时间是否超过了限制,并且我正在定期检查该插件的至少一个线程是否正在执行某项操作(以使其无法无限期进入睡眠状态,并避免CPU时间达到限制).
  6. 首选的举动没有足够的状态来保持一致,但是无论如何,在返回后都会仔细谨慎地克隆它,而返回的则被丢弃.至此,该插件在其引用的主机系统中没有一无所有:没有线程,没有对象实例.

结果,似乎插件无法将任何东西保持在不一致的状态(除了可能创建的任何对象,然后将其丢弃);并且不会影响任何其他线程(除了它产生的任何线程之外,这些线程将位于同一ThreadGroup中,因此也将被杀死).

在我看来,不推荐使用Thread.stop()的原因在这里不适用(根据设计).我错过了什么吗?这里还有危险吗?还是我已经足够仔细地隔离事物以至于不会出现问题?

还有更好的方法吗?我认为,唯一的选择是启动一个全新的JVM来运行不受信任的代码,并在不再需要它时强制终止该进程,但这会带来另外一千个问题(昂贵,脆弱,依赖于操作系统). /p>

请注意:我对哦,它由于某种原因而被弃用,您想观看,老兄"这样的答案不感兴趣.我知道已被弃用是有原因的,我完全理解为什么通常将其从笼子里放出来是不安全的.我要问的是是否有特定原因认为在这种情况下是不安全的.

对于它的价值,这是代码的(删节)相关部分:

public void playMoveInternal(GameState game) throws IllegalMoveException,
        InstantiationException, IllegalAccessException,
        IllegalMoveSpecificationException {
    ThreadGroup group = new ThreadGroup("playthread group");
    Thread playthread = null;
    group.setMaxPriority(Thread.MIN_PRIORITY);
    GameMetaData meta = null;
    StrategyGamePlayer player = null;
    try {
        GameState newgame = (GameState) game.clone();
        SandboxedURLClassLoader loader = new SandboxedURLClassLoader(
          // recreating this each time means static fields don't persist
                urls[newgame.getCurPlayer() - 1], playerInterface);
        Class<?> playerClass = loader.findPlayerClass();
        GameTimer timer = new GameTimer(
                newgame.getCurPlayer() == 1 ? timelimit : timelimit2);
          // time starts ticking here!
        meta = new GameMetaData((GameTimer) timer.clone());
        try {
            player = (StrategyGamePlayer) playerClass.newInstance();
        } catch (Exception e) {
            System.err.println("Couldn't create player module instance!");
            e.printStackTrace();
            game.resign(GameMoveType.MOVE_ILLEGAL);
            return;
        }
        boolean checkSleepy = true;
        playthread = new Thread(group, new MoveMakerThread(player, meta,
                newgame), "MoveMaker thread");
        int badCount = 0;
        playthread.start();
        try {
            while ((timer.getTimeRemaining() > 0) && (playthread.isAlive())
                    && (!stopping) && (!forceMove)) {
                playthread.join(50);
                if (checkSleepy) {
                    Thread.State thdState = playthread.getState();
                    if ((thdState == Thread.State.TIMED_WAITING)
                            || (thdState == Thread.State.WAITING)) {
                        // normally, main thread will be busy
                        Thread[] allThreads = new Thread[group
                                .activeCount() * 2];
                        int numThreads = group.enumerate(allThreads);
                        boolean bad = true;
                        for (int i = 0; i < numThreads; i++) {
                            // check some player thread somewhere is doing something
                            thdState = allThreads[i].getState();
                            if ((thdState != Thread.State.TIMED_WAITING)
                                    && (thdState != Thread.State.WAITING)) {
                                bad = false;
                                break; // found a good thread, so carry on
                            }
                        }
                        if ((bad) && (badCount++ > 100))
                            // means player has been sleeping for an expected 5
                            // sec, which is naughty
                            break;
                    }
                }
            }
        } catch (InterruptedException e) {
            System.err.println("Interrupted: " + e);
        }
    } catch (Exception e) {
        System.err.println("Couldn't play the game: " + e);
        e.printStackTrace();
    }
    playthread.destroy();
    try {
        Thread.sleep(1000);
    } catch (Exception e) {
    }
    group.stop();
    forceMove = false;
    try {
        if (!stopping)
            try {
                if (!game.isLegalMove(meta.getBestMove())) {
                    game.resign(GameMoveType.MOVE_ILLEGAL);
                }
                else
                    game.makeMove((GameMove) (meta.getBestMove().clone()));
                // We rely here on the isLegalMove call to make sure that
                // the return type is the right (final) class so that the clone()
                // call can't execute dodgy code
            } catch (IllegalMoveException e) {
                game.resign(GameMoveType.MOVE_ILLEGAL);
            } catch (NullPointerException e) {
                // didn't ever choose a move to make
                game.resign(GameMoveType.MOVE_OUT_OF_TIME);
            }
    } catch (Exception e) {
        e.printStackTrace();
        game.resign(GameMoveType.MOVE_OUT_OF_TIME);
    }
}

解决方案

虽然我可以想象这样的场景:您仔细查看过的代码可以用Thread.stop()安全地停止,但是您正在谈论相反的代码,因此您不信任它.您试图通过SecurityManager限制其操作的很多事情.

那怎么可能出问题呢?首先,停止并没有比中断更可靠.尽管更容易忽略中断,但简单的catch(Throwable t){}足以使Thread.stop()不再起作用,即使没有意图阻止Thread.stop()起作用的意图,这样的catch可能也会出现在代码中,尽管它很糟糕.编码风格.但是,程序员想要忽略Thread.stop()甚至可能会故意添加catch(Throwable t){}和/或catch(ThreadDeath t){} .

谈到不良的编码风格,应始终使用try … finally …实施锁定,以确保即使在特殊情况下也可以确保释放锁定.如果停止的代码无法正确执行此操作,则线程停止时,锁可能保持锁定状态.

最重要的是,在同一JVM 中,您无法保护自己免受不受信任的代码的侵害. JVM 沙箱,用于保护外部.仅举一个简单的例子:恶意代码可能会通过执行大量小的内存分配来开始占用内存.您无法在JVM中执行任何操作来保护在同一JVM中运行的某些代码免受它的攻击.您只能对整个JVM实施限制.

因此,唯一的解决方案是在不同的JVM中运行插件.

I'm aware that Thread.stop() is deprecated, and for good reason: it is not, in general, safe. But that doesn't mean that it is never safe... as far as I can see, it is safe in the context in which I want to use it; and, as far as I can see, I have no other option.

The context is a third-party plug-in for a two-player strategy game: chess will do as the working example. The third-party code needs to be given a current board state, and (say) 10 seconds to decide on its move. It can return its move and terminate within the allowable time, or it can, whenever it wants to, signal its current preferred move; if the time limit expires, it should be stopped in its tracks, and its most recent preferred move should be played.

Writing the plug-in to stop gracefully on request is not an option: I need to be able to take arbitrary untrusted third-party plug-ins. So I have to have some way of forcibly terminating it.

Here's what I'm doing to lock it down:

  1. The classes for the plug-in get put into their own thread, into their own thread group.
  2. They are loaded with a class loader that has a highly restrictive SecurityManager in place: all the classes can do is number crunching.
  3. The classes don't get a reference to any other threads, so they can't use Thread.join() on anything they haven't created.
  4. The plug-in gets only two references to objects from the host system. One is the state of the chess board; this is a deep copy, and is thrown away afterwards, so it doesn't matter if it gets into an inconsistent state. The other is a simple object that allows the plug-in to set its current preferred move.
  5. I'm checking when CPU time has exceeded the limit, and I'm periodically checking that at least one thread of the plug-in is doing something (so that it can't sleep indefinitely and avoid CPU time ever hitting the limit).
  6. The preferred move doesn't have enough state to be inconsistent, but in any case it is carefully and defensively cloned after it is returned, and the one that was returned is discarded. By this point, there is nothing left in the host system to which the plug-in had a reference: no threads, no object instances.

In consequence, it seems, the plug-in can't leave anything in an inconsistent state (except for any objects it might create, which will then be discarded); and it can't affect any other threads (except for any threads it spawns, which will be in the same ThreadGroup, and so will also be killed off).

It looks to me as though the reasons that Thread.stop() is deprecated don't apply here (by design). Have I missed anything? Is there still danger here? Or have I isolated things carefully enough that there can't be a problem?

And is there a better way? The only alternative, I think, is to start up a whole new JVM to run the untrusted code, and forcibly kill the process when it's no longer wanted, but that has a thousand other problems (expensive, fragile, OS-dependent).

Please note: I'm not interested in answers along the lines of "Ooh, it's deprecated for a reason, you want to watch it, mate." I know it's deprecated for a reason, and I completely understand why it is not safe to be let out of the cage in general. What I am asking is whether there is a specific reason for thinking that it is unsafe in this context.

For what it's worth, here is the (abridged) relevant bit of the code:

public void playMoveInternal(GameState game) throws IllegalMoveException,
        InstantiationException, IllegalAccessException,
        IllegalMoveSpecificationException {
    ThreadGroup group = new ThreadGroup("playthread group");
    Thread playthread = null;
    group.setMaxPriority(Thread.MIN_PRIORITY);
    GameMetaData meta = null;
    StrategyGamePlayer player = null;
    try {
        GameState newgame = (GameState) game.clone();
        SandboxedURLClassLoader loader = new SandboxedURLClassLoader(
          // recreating this each time means static fields don't persist
                urls[newgame.getCurPlayer() - 1], playerInterface);
        Class<?> playerClass = loader.findPlayerClass();
        GameTimer timer = new GameTimer(
                newgame.getCurPlayer() == 1 ? timelimit : timelimit2);
          // time starts ticking here!
        meta = new GameMetaData((GameTimer) timer.clone());
        try {
            player = (StrategyGamePlayer) playerClass.newInstance();
        } catch (Exception e) {
            System.err.println("Couldn't create player module instance!");
            e.printStackTrace();
            game.resign(GameMoveType.MOVE_ILLEGAL);
            return;
        }
        boolean checkSleepy = true;
        playthread = new Thread(group, new MoveMakerThread(player, meta,
                newgame), "MoveMaker thread");
        int badCount = 0;
        playthread.start();
        try {
            while ((timer.getTimeRemaining() > 0) && (playthread.isAlive())
                    && (!stopping) && (!forceMove)) {
                playthread.join(50);
                if (checkSleepy) {
                    Thread.State thdState = playthread.getState();
                    if ((thdState == Thread.State.TIMED_WAITING)
                            || (thdState == Thread.State.WAITING)) {
                        // normally, main thread will be busy
                        Thread[] allThreads = new Thread[group
                                .activeCount() * 2];
                        int numThreads = group.enumerate(allThreads);
                        boolean bad = true;
                        for (int i = 0; i < numThreads; i++) {
                            // check some player thread somewhere is doing something
                            thdState = allThreads[i].getState();
                            if ((thdState != Thread.State.TIMED_WAITING)
                                    && (thdState != Thread.State.WAITING)) {
                                bad = false;
                                break; // found a good thread, so carry on
                            }
                        }
                        if ((bad) && (badCount++ > 100))
                            // means player has been sleeping for an expected 5
                            // sec, which is naughty
                            break;
                    }
                }
            }
        } catch (InterruptedException e) {
            System.err.println("Interrupted: " + e);
        }
    } catch (Exception e) {
        System.err.println("Couldn't play the game: " + e);
        e.printStackTrace();
    }
    playthread.destroy();
    try {
        Thread.sleep(1000);
    } catch (Exception e) {
    }
    group.stop();
    forceMove = false;
    try {
        if (!stopping)
            try {
                if (!game.isLegalMove(meta.getBestMove())) {
                    game.resign(GameMoveType.MOVE_ILLEGAL);
                }
                else
                    game.makeMove((GameMove) (meta.getBestMove().clone()));
                // We rely here on the isLegalMove call to make sure that
                // the return type is the right (final) class so that the clone()
                // call can't execute dodgy code
            } catch (IllegalMoveException e) {
                game.resign(GameMoveType.MOVE_ILLEGAL);
            } catch (NullPointerException e) {
                // didn't ever choose a move to make
                game.resign(GameMoveType.MOVE_OUT_OF_TIME);
            }
    } catch (Exception e) {
        e.printStackTrace();
        game.resign(GameMoveType.MOVE_OUT_OF_TIME);
    }
}

解决方案

While I can imagine scenarios where code that you have carefully reviewed may be safely stopped with Thread.stop(), you are talking about the opposite, code that you distrust so much that you are attempting to restrict its actions by a SecurityManager.

So what can go wrong? First of all, stopping does not work more reliable than interruption. While it is easier to ignore an interruption, a simple catch(Throwable t){} is enough to make Thread.stop() not work anymore and such a catch may appear in code even without the intention of preventing Thread.stop() from working, though it is bad coding style. But coders wanting to ignore Thread.stop() might even add a catch(Throwable t){} and/or catch(ThreadDeath t){} intentionally.

Speaking of bad coding style, locking should always be implemented using try … finally … to ensure that the lock is guaranteed to be freed even in the exceptional case. If the stopped code failed to do this right, the lock may remain locked when the thread will be stopped.

The bottom line is that within the same JVM you can’t protect yourself against untrusted code. The JVM is the sandbox which protects the outside. Just one simple example: a malicious code could start eating up memory by performing lots of small memory allocations. There is nothing you can do within the JVM to protect some code running in the same JVM against it. You can only enforce limits for the entire JVM.

So the only solution is to run the plugins in a different JVM.

这篇关于在仔细锁定但不受信任的代码上使用Thread.stop()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆