JVM安全管理器文件权限 - 自定义策略

时间:2017-08-26 16:39:22

标签: java jvm securitymanager java-security-manager

我发现了使用JVM Security Manager自定义策略的某种意外行为。

回复:https://github.com/pedrorijo91/jvm-sec-manager

在分支主文件中,进入../allow/allow.txt文件夹:

  • 自定义策略文件授予文件../deny/deny.txt
  • 的文件读取权限
  • 没有文件run.sh
  • 的权限
  • HelloWorld.java中的代码尝试读取这两个文件
  • 有一个java.security.AccessControlException: access denied ("java.io.FilePermission" "../deny/deny.txt" "read")脚本来运行命令

现在一切都按预期工作:允许的文件读取,但另一个抛出安全异常:../allow/allow.txt

但是如果我将两个文件(../deny/deny.txtcode)移动到{{1}}文件夹(更改自定义策略和java代码以使用这些文件),我也不会例外。 (分支'意外')

当前目录是特殊情况还是其他情况?

1 个答案:

答案 0 :(得分:1)

简要说明

此行为记录在许多地方:

后两者重申了第一个的结束语,其中指出:

代码总是可以从它所在的同一目录(或该目录的子目录)中读取文件;它不需要明确的许可。

换句话说,如果

(HelloWorld.class.getProtectionDomain().getCodeSource().implies(
    new CodeSource(new URL("file:" + codeDir),
    (Certificate[]) null)) == true)

然后默认情况下HelloWorld将被授予对表示的目录及其后代的读访问权。特别是对于code目录本身,这应该有一些直观的意义,否则该类甚至无法访问public - 访问其包中的类。

全文

基本上取决于ClassLoader:如果它statically已将Permission分配给ProtectionDomain,那么mapped班级 - 适用于java.net.URLClassLoadersun.misc.Launcher$AppClassLoader(特定于OpenJDK的默认系统类加载器) - 无论Policy生效,这些权限都将始终与域相关。

变通方法

任何与授权相关的典型“快速”“解决方法”是扩展SecurityManager并覆盖让您感到烦恼的方法;即在这种情况下checkRead组方法。

另一方面,对于不会降低AccessController和朋友灵活性的更全面的解决方案,您必须编写一个至少覆盖URLClassLoader#getPermissions(CodeSource)和/的类加载器或者将加载类的域'CodeSource限制到文件级别(默认情况下由URLClassLoaderAppClassLoader分配的域的代码源暗示(递归地).class文件的类路径条目(JAR)或目录))。为了进一步细化,您的加载器也可以分配您自己的域子类的实例,和/或封装您自己的子类的代码源的域,分别覆盖ProtectionDomain#implies(Permission)和/或CodeSource#implies(CodeSource);例如,可以使前者支持“否定许可”语义,后者可以将代码源暗示基于任意逻辑,可能与物理代码位置分离(例如,认为“信任级别”)。

<小时/>

根据评论澄清

要证明在不同的类加载器下这些权限确实很重要,请考虑以下示例:有两个类,AB; Amain方法,只需在B上调用方法即可。此外,应用程序使用不同的系统类加载器启动,a)基于每个类分配域(而不是基于每个类路径条目,默认情况下),它加载的类,而不是b)分配对这些域的任何权限。

<强>装载机:

package com.example.q45897574;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Pattern;

public class RestrictiveClassLoader extends URLClassLoader {

    private static final Pattern COMMON_SYSTEM_RESOURCE_NAMES = Pattern
            .compile("(((net\\.)?java)|(java(x)?)|(sun|oracle))\\.[a-zA-Z0-9\\.\\-_\\$\\.]+");
    private static final String OWN_CLASS_NAME = RestrictiveClassLoader.class.getName();
    private static final URL[] EMPTY_URL_ARRAY = new URL[0], CLASSPATH_ENTRY_URLS;
    private static final PermissionCollection NO_PERMS = new Permissions();

    static {
        String[] classpathEntries = AccessController.doPrivileged(new PrivilegedAction<String>() {
            @Override
            public String run() {
                return System.getProperty("java.class.path");
            }
        }).split(File.pathSeparator);
        Set<URL> classpathEntryUrls = new LinkedHashSet<>(classpathEntries.length, 1);
        for (String classpathEntry : classpathEntries) {
            try {
                URL classpathEntryUrl;
                if (classpathEntry.endsWith(".jar")) {
                    classpathEntryUrl = new URL("file:jar:".concat(classpathEntry));
                }
                else {
                    if (!classpathEntry.endsWith("/")) {
                        classpathEntry = classpathEntry.concat("/");
                    }
                    classpathEntryUrl = new URL("file:".concat(classpathEntry));
                }
                classpathEntryUrls.add(classpathEntryUrl);
            }
            catch (MalformedURLException mue) {
            }
        }
        CLASSPATH_ENTRY_URLS = classpathEntryUrls.toArray(EMPTY_URL_ARRAY);
    }

    private static byte[] readClassData(URL classResource) throws IOException {
        try (InputStream in = new BufferedInputStream(classResource.openStream());
                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            while (in.available() > 0) {
                out.write(in.read());
            }
            return out.toByteArray();
        }
    }

    public RestrictiveClassLoader(ClassLoader parent) {
        super(EMPTY_URL_ARRAY, parent);
        for (URL classpathEntryUrl : CLASSPATH_ENTRY_URLS) {
            addURL(classpathEntryUrl);
        }
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if (name == null) {
            throw new ClassNotFoundException("< null >", new NullPointerException("name argument must not be null."));
        }
        if (OWN_CLASS_NAME.equals(name)) {
            return RestrictiveClassLoader.class;
        }
        if (COMMON_SYSTEM_RESOURCE_NAMES.matcher(name).matches()) {
            return getParent().loadClass(name);
        }
        Class<?> ret = findLoadedClass(name);
        if (ret != null) {
            return ret;
        }
        return findClass(name);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String modifiedClassName = name.replace(".", "/").concat(".class");
        URL classResource = findResource(modifiedClassName);
        if (classResource == null) {
            throw new ClassNotFoundException(name);
        }
        byte[] classData;
        try {
            classData = readClassData(classResource);
        }
        catch (IOException ioe) {
            throw new ClassNotFoundException(name, ioe);
        }
        return defineClass(name, classData, 0, classData.length, constructClassDomain(classResource));
    }

    @Override
    protected PermissionCollection getPermissions(CodeSource codesource) {
        return NO_PERMS;
    }

    private ProtectionDomain constructClassDomain(URL codeSourceLocation) {
        CodeSource cs = new CodeSource(codeSourceLocation, (Certificate[]) null);
        return new ProtectionDomain(cs, getPermissions(cs), this, null);
    }

}

<强> A

package com.example.q45897574;

public class A {

    public static void main(String... args) {
        /*
         * Note:
         * > Can't we set the security manager via launch argument?
         * No, it has to be set here, or bootstrapping will fail.
         * > Why?
         * Because our class loader's domain is unprivileged.
         * > Can't it be privileged?
         * Yes, but then everything under the same classpath entry becomes
         * privileged too, because our loader's domain's code source--which
         * _its own_ loader creates, thus escaping our control--implies _the
         * entire_ classpath entry. There are various workarounds, which
         * however fall outside of this example's scope.
         */
        System.setSecurityManager(new SecurityManager());
        B.b();
    }

}

<强> B

package com.example.q45897574;

public class B {

    public static void b() {
        System.out.println("success!");
    }
}

非特权测试
确保在策略级别授予 nothing ;然后运行(假设一个基于Linux的操作系统 - 根据需要修改类路径):

java -cp "/home/your_user/classpath/" \
-Djava.system.class.loader=com.example.q45897574.RestrictiveClassLoader \
-Djava.security.debug=access=failure com.example.q45897574.A

您应该获得NoClassDefFoundError以及FilePermission失败的com.example.q45897574.A

特权测试:
现在授予A必要的权限(再次确保纠正codeBase(代码源URL)和权限目标名称):

grant codeBase "file:/home/your_user/classpath/com/example/q45897574/A.class" {
    permission java.io.FilePermission "/home/your_user/classpath/com/example/q45897574/B.class", "read";
};

......然后重新开始。这次执行应该成功完成。