纯JAVA实现Online Judge--3.SecurityManager安全管理器

来源:互联网 发布:如何更改淘宝会员名 编辑:程序博客网 时间:2024/05/18 17:02

前言

    上一篇的博文中,我们通过自己编译用户提交的代码,再通过自定制的类加载器将编译出来后的class信息加载进JVM中,最后再通过反射调用用户代码的main,实现了运行用户代码的目的。具体如何运行用户的代码的部分,将会在后面的博客(多线程跑题)中展开述说,里面将介绍我如何利用多线程的方式,同时对同一份用户代码跑多份测试用例,提升效率的同时,如何解决多线程中遇到的冲突问题。

    现在首先要解决的是安全问题,既然我们运行用户提交的代码了,这就存在一个风险,如果用户想恶意攻击我们的服务器怎么办?比如用户在提交的代码中,打开远程连接连上远程的服务器,打开网络连接不断下载东西,再或者不断新建文件并写入大量数据,更有甚者直接调用System.exit(status);退出JAVA虚拟机等等。

    因为我们直接运行了未知的代码,我们就必须保证我们的服务器安全,不然我们的系统都还没判题几个就被别人搞垮了。我们利用JAVA实现这个安全管理还是挺容易的,因为JAVA提供了SecurityManager安全管理器。每一个JAVA程序在启动的时候,其实内部就设置了一个默认SecurityManager,我们这里只需要继承它,并复写相应的方法即可。


SecurityManager安全管理器

    首先,我们先贴上我们安全管理器的代码,然后再稍微分析一下:

package cn.superman.sandbox.core.securityManager;import java.io.FilePermission;import java.lang.reflect.ReflectPermission;import java.security.Permission;import java.security.SecurityPermission;import java.util.PropertyPermission;import java.util.logging.LoggingPermission;import cn.superman.sandbox.constant.ConstantParameter;public class SandboxSecurityManager extends SecurityManager {/** * 防止有人非法退出虚拟机 */@Overridepublic void checkExit(int status) {if (status != ConstantParameter.EXIT_VALUE) {throw new RuntimeException("非法退出,不允许退出虚拟机");}super.checkExit(status);}@Overridepublic void checkPermission(Permission perm) {conformPermissionToSandbox(perm);}@Overridepublic void checkPermission(Permission perm, Object context) {conformPermissionToSandbox(perm);}/** * 只给与必要的权限(比如读取,获取某些信息等),避免提交者进行非法操作。 *  * @param perm */private void conformPermissionToSandbox(Permission perm) {if (perm instanceof SecurityPermission) {if (perm.getName().startsWith("getProperty")) {return;}} else if (perm instanceof PropertyPermission) {if (perm.getActions().equals("read")) {return;}} else if (perm instanceof FilePermission) {if (perm.getActions().equals("read")) {return;}} else if (perm instanceof RuntimePermission|| perm instanceof ReflectPermission|| perm instanceof LoggingPermission) {return;}throw new SecurityException(perm.toString() + "无法使用该权限");}}

设置时机

    我们在我们的沙箱初始化完成后,并在接收外界数据之前,对其进行设置。至于为什么是这个时机呢?因为我们的沙箱在初始化时就需要用到一定我们限制的权限,比如打开socket等,因此我们要在初始化完成后,因为我们要开始对外服务了(可能需要运行用户的代码了),这个时候就要保证我们沙箱的安全了,所以我们要在开始接收外界数据之前,设置这个安全管理器。

设置的代码很简单:

System.setSecurityManager(new SandboxSecurityManager());

重点内容

    其实从代码中可以看出,我们重点做的就是复写checkPermission和checkExit。

    复写checkExit是为了防止用户恶意调用System.exit(status);代码退出我们的虚拟机,当我们发现用户恶意调用时,但是他们传过来的数值不对时,我们就直接拒绝他这个请求,并且抛出异常通知上层认定这份代码是非法的。至于正确的退出值是多少,我们可以在程序初始化时,通过随机函数自动生成一个等方式来确立。。

    复写checkPermission函数主要是为了,动态捕获用户代码在运行时请求了哪些权限,对于合法符合业务逻辑的权限,我们直接返回即可,给予放行(如反射,读取文件等)。对于非法的权限,我们通过抛出异常的方式,通知上层该用户代码有违法操作,直接判定该份代码为非法。

    权限有很多种,大家可以通过查看JDK文档或者其他博文的方式深入理解,下面给出一个权限控制的大概简图:


预告

    本篇博文介绍的内容,还不能完全的防止用户提交的恶意代码,因为用户还有可能提交一些死循环的代码,当我们沙箱运行这样的代码时,就会有一个线程陷入死循环,导致服务器的CPU资源被占据不放。当然,排除死循环方式,用户运行的代码也有可能会超时,这个时候我们除了判定用户的代码运行超时之外,我们还要终止运行该线程,尽快释放资源(因为已经得出结果了嘛)。
   因此,在下篇博文中,我将会介绍我如何结合OJ的业务逻辑,强行终止(杀死)正在运行的线程。做到限时运行。


原创粉丝点击