【问题标题】:JVM Security Manager File permissions - custom policyJVM 安全管理器文件权限 - 自定义策略
【发布时间】:2017-08-26 16:39:21
【问题描述】:

我在使用 JVM 安全管理器自定义策略时发现了某种意外行为。

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

在分支master中,进入/code文件夹:

  • 自定义策略文件授予文件../allow/allow.txt 的文件读取权限
  • 没有../deny/deny.txt文件的权限
  • HelloWorld.java 中的代码尝试读取这两个文件
  • 有一个run.sh 脚本可以运行命令

现在一切都按预期工作:允许的文件读取,但另一个引发安全异常:java.security.AccessControlException: access denied ("java.io.FilePermission" "../deny/deny.txt" "read")

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

当前目录是特殊情况还是发生了其他事情?

【问题讨论】:

    标签: java jvm securitymanager java-security-manager


    【解决方案1】:

    简要说明

    这种行为记录在许多地方:

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

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

    换句话说,如果

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

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

    全文

    基本上取决于ClassLoader:如果它statically 将任何Permissions 分配给ProtectionDomain,它mapped 类-这适用于java.net.URLClassLoadersun.misc.Launcher$AppClassLoader (特定于 OpenJDK 的默认系统类加载器)——这些权限将始终授予域,无论 Policy 是否有效。

    解决方法

    任何与授权相关的典型“quick-n'-dirty”解决方法是扩展SecurityManager 并覆盖惹恼您的方法;即在这种情况下,checkRead 方法组。

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


    根据 cmets 的说明

    为了证明在不同的类加载器下这些权限实际上很重要,请考虑以下示例:有两个类,ABAmain 方法,它只是调用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";
    };
    

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

    【讨论】:

    • 你说代码需要访问目录和子目录,否则它甚至无法访问公共类。但是安全管理器管理应用程序运行时访问,并且该类由编译器(有权访问目录)编译,并且 .class 文件可以放入 jvm 始终可以访问的任何自定义文件夹中。所以我认为这不是一个正当的理由,或者我的想法可能有一些缺陷?但是当然代码文件夹包含在权限中有点直观,但是 javadoc 没有提到类似的东西
    • 类加载器不会盲目地从事特权操作,例如从文件系统中读取 .class 文件,除非调用者,包括引用要加载的类的类,实际上具有对该文件的读取权限。使用-Djava.security.debug=access=failure 运行您的应用程序,以确认在类加载期间发生的所有FilePermission 评估。至于文档,URLClassLoader 的概述中简要提到了此行为,其中指出实例在加载类/资源时“继承”其创建者的 AccessControlContext
    • 为了证明我的观点(一个类将无法访问另一个类,即使是公共的并且在它的包中,除非被授予读取它的 .class 文件的权限)我附加了一个例子对我的回答。希望它能把事情弄清楚一点。
    猜你喜欢
    • 1970-01-01
    • 2023-03-08
    • 2021-07-15
    • 1970-01-01
    • 2011-08-06
    • 2011-10-02
    • 1970-01-01
    • 1970-01-01
    • 2010-12-27
    相关资源
    最近更新 更多