8164705: Remove pathname canonicalization from FilePermission

Reviewed-by: alanb, bpb
This commit is contained in:
Weijun Wang 2016-10-10 08:28:50 +08:00
parent 0f9a011475
commit ba9df3533c
19 changed files with 1436 additions and 194 deletions

View File

@ -25,11 +25,20 @@
package java.io;
import java.net.URI;
import java.nio.file.*;
import java.security.*;
import java.util.Enumeration;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import jdk.internal.misc.JavaIOFilePermissionAccess;
import jdk.internal.misc.SharedSecrets;
import sun.nio.fs.DefaultFileSystemProvider;
import sun.security.action.GetPropertyAction;
import sun.security.util.FilePermCompat;
import sun.security.util.SecurityConstants;
/**
@ -41,8 +50,11 @@ import sun.security.util.SecurityConstants;
* the file separator character, <code>File.separatorChar</code>) indicates
* all the files and directories contained in that directory. A pathname
* that ends with "/-" indicates (recursively) all files
* and subdirectories contained in that directory. A pathname consisting of
* the special token "&lt;&lt;ALL FILES&gt;&gt;" matches <b>any</b> file.
* and subdirectories contained in that directory. Such a pathname is called
* a wildcard pathname. Otherwise, it's a simple pathname.
* <P>
* A pathname consisting of the special token {@literal "<<ALL FILES>>"}
* matches <b>any</b> file.
* <P>
* Note: A pathname consisting of a single "*" indicates all the files
* in the current directory, while a pathname consisting of a single "-"
@ -75,12 +87,12 @@ import sun.security.util.SecurityConstants;
* <P>
* Be careful when granting FilePermissions. Think about the implications
* of granting read and especially write access to various files and
* directories. The "&lt;&lt;ALL FILES&gt;&gt;" permission with write action is
* directories. The {@literal "<<ALL FILES>>"} permission with write action is
* especially dangerous. This grants permission to write to the entire
* file system. One thing this effectively allows is replacement of the
* system binary, including the JVM runtime environment.
*
* <p>Please note: Code can always read a file from the same
* <P>
* Please note: Code can always read a file from the same
* directory it's in (or a subdirectory of that directory); it does not
* need explicit permission to do so.
*
@ -145,33 +157,126 @@ public final class FilePermission extends Permission implements Serializable {
private String actions; // Left null as long as possible, then
// created and re-used in the getAction function.
// canonicalized dir path. In the case of
// directories, it is the name "/blah/*" or "/blah/-" without
// the last character (the "*" or "-").
// canonicalized dir path. used by the "old" behavior (nb == false).
// In the case of directories, it is the name "/blah/*" or "/blah/-"
// without the last character (the "*" or "-").
private transient String cpath;
// Following fields used by the "new" behavior (nb == true), in which
// input path is not canonicalized. For compatibility (so that granting
// FilePermission on "x" allows reading "`pwd`/x", an alternative path
// can be added so that both can be used in an implies() check. Please note
// the alternative path only deals with absolute/relative path, and does
// not deal with symlink/target.
private transient Path npath; // normalized dir path.
private transient Path npath2; // alternative normalized dir path.
private transient boolean allFiles; // whether this is <<ALL FILES>>
// static Strings used by init(int mask)
private static final char RECURSIVE_CHAR = '-';
private static final char WILD_CHAR = '*';
/*
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("***\n");
sb.append("cpath = "+cpath+"\n");
sb.append("mask = "+mask+"\n");
sb.append("actions = "+getActions()+"\n");
sb.append("directory = "+directory+"\n");
sb.append("recursive = "+recursive+"\n");
sb.append("***\n");
return sb.toString();
}
*/
// public String toString() {
// StringBuffer sb = new StringBuffer();
// sb.append("*** FilePermission on " + getName() + " ***");
// for (Field f : FilePermission.class.getDeclaredFields()) {
// if (!Modifier.isStatic(f.getModifiers())) {
// try {
// sb.append(f.getName() + " = " + f.get(this));
// } catch (Exception e) {
// sb.append(f.getName() + " = " + e.toString());
// }
// sb.append('\n');
// }
// }
// sb.append("***\n");
// return sb.toString();
// }
private static final long serialVersionUID = 7930732926638008763L;
/**
* Always use the internal default file system, in case it was modified
* with java.nio.file.spi.DefaultFileSystemProvider.
*/
private static final java.nio.file.FileSystem builtInFS =
DefaultFileSystemProvider.create()
.getFileSystem(URI.create("file:///"));
/**
* Creates FilePermission objects with special internals.
* See {@link FilePermCompat#newPermPlusAltPath(Permission)} and
* {@link FilePermCompat#newPermUsingAltPath(Permission)}.
*/
private static final Path here = builtInFS.getPath(
GetPropertyAction.privilegedGetProperty("user.dir"));
/**
* A private constructor like a clone, only npath2 is not touched.
* @param input
*/
private FilePermission(FilePermission input) {
super(input.getName());
this.npath = input.npath;
this.actions = input.actions;
this.allFiles = input.allFiles;
this.recursive = input.recursive;
this.directory = input.directory;
this.cpath = input.cpath;
this.mask = input.mask;
}
/**
* Returns the alternative path as a Path object, i.e. absolute path
* for a relative one, or vice versa.
*
* @param in a real path w/o "-" or "*" at the end, and not <<ALL FILES>>.
* @return the alternative path, or null if cannot find one.
*/
private static Path altPath(Path in) {
try {
if (!in.isAbsolute()) {
return here.resolve(in).normalize();
} else {
return here.relativize(in).normalize();
}
} catch (IllegalArgumentException e) {
return null;
}
}
static {
SharedSecrets.setJavaIOFilePermissionAccess(
new JavaIOFilePermissionAccess() {
public FilePermission newPermPlusAltPath(FilePermission input) {
if (input.npath2 == null && !input.allFiles) {
Path npath2 = altPath(input.npath);
if (npath2 != null) {
FilePermission np = new FilePermission(input);
np.npath2 = npath2;
return np;
}
}
return input;
}
public FilePermission newPermUsingAltPath(FilePermission input) {
if (!input.allFiles) {
Path npath2 = altPath(input.npath);
if (npath2 != null) {
FilePermission np = new FilePermission(input);
np.npath = npath2;
return np;
}
}
return null;
}
}
);
}
/**
* initialize a FilePermission object. Common to all constructors.
* Also called during de-serialization.
@ -186,60 +291,106 @@ public final class FilePermission extends Permission implements Serializable {
if (mask == NONE)
throw new IllegalArgumentException("invalid actions mask");
if ((cpath = getName()) == null)
if (FilePermCompat.nb) {
String name = getName();
if (name == null)
throw new NullPointerException("name can't be null");
this.mask = mask;
this.mask = mask;
if (cpath.equals("<<ALL FILES>>")) {
directory = true;
recursive = true;
cpath = "";
return;
}
// store only the canonical cpath if possible
cpath = AccessController.doPrivileged(new PrivilegedAction<>() {
public String run() {
try {
String path = cpath;
if (cpath.endsWith("*")) {
// call getCanonicalPath with a path with wildcard character
// replaced to avoid calling it with paths that are
// intended to match all entries in a directory
path = path.substring(0, path.length()-1) + "-";
path = new File(path).getCanonicalPath();
return path.substring(0, path.length()-1) + "*";
} else {
return new File(path).getCanonicalPath();
}
} catch (IOException ioe) {
return cpath;
}
if (name.equals("<<ALL FILES>>")) {
allFiles = true;
npath = builtInFS.getPath("");
// other fields remain default
return;
}
});
int len = cpath.length();
char last = ((len > 0) ? cpath.charAt(len - 1) : 0);
boolean rememberStar = false;
if (name.endsWith("*")) {
rememberStar = true;
recursive = false;
name = name.substring(0, name.length()-1) + "-";
}
if (last == RECURSIVE_CHAR &&
cpath.charAt(len - 2) == File.separatorChar) {
directory = true;
recursive = true;
cpath = cpath.substring(0, --len);
} else if (last == WILD_CHAR &&
cpath.charAt(len - 2) == File.separatorChar) {
directory = true;
//recursive = false;
cpath = cpath.substring(0, --len);
try {
// new File() can "normalize" some name, for example, "/C:/X" on
// Windows. Some JDK codes generate such illegal names.
npath = builtInFS.getPath(new File(name).getPath())
.normalize();
} catch (InvalidPathException ipe) {
// Still invalid. For compatibility reason, accept it
// but make this permission useless.
npath = builtInFS.getPath("-u-s-e-l-e-s-s-");
this.mask = NONE;
}
// lastName should always be non-null now
Path lastName = npath.getFileName();
if (lastName != null && lastName.toString().equals("-")) {
directory = true;
recursive = !rememberStar;
npath = npath.getParent();
}
if (npath == null) {
npath = builtInFS.getPath("");
}
} else {
// overkill since they are initialized to false, but
// commented out here to remind us...
//directory = false;
//recursive = false;
}
if ((cpath = getName()) == null)
throw new NullPointerException("name can't be null");
// XXX: at this point the path should be absolute. die if it isn't?
this.mask = mask;
if (cpath.equals("<<ALL FILES>>")) {
directory = true;
recursive = true;
cpath = "";
return;
}
// store only the canonical cpath if possible
cpath = AccessController.doPrivileged(new PrivilegedAction<>() {
public String run() {
try {
String path = cpath;
if (cpath.endsWith("*")) {
// call getCanonicalPath with a path with wildcard character
// replaced to avoid calling it with paths that are
// intended to match all entries in a directory
path = path.substring(0, path.length() - 1) + "-";
path = new File(path).getCanonicalPath();
return path.substring(0, path.length() - 1) + "*";
} else {
return new File(path).getCanonicalPath();
}
} catch (IOException ioe) {
return cpath;
}
}
});
int len = cpath.length();
char last = ((len > 0) ? cpath.charAt(len - 1) : 0);
if (last == RECURSIVE_CHAR &&
cpath.charAt(len - 2) == File.separatorChar) {
directory = true;
recursive = true;
cpath = cpath.substring(0, --len);
} else if (last == WILD_CHAR &&
cpath.charAt(len - 2) == File.separatorChar) {
directory = true;
//recursive = false;
cpath = cpath.substring(0, --len);
} else {
// overkill since they are initialized to false, but
// commented out here to remind us...
//directory = false;
//recursive = false;
}
// XXX: at this point the path should be absolute. die if it isn't?
}
}
/**
@ -254,7 +405,7 @@ public final class FilePermission extends Permission implements Serializable {
* indicates all the files and directories contained in that directory.
* A pathname that ends with "/-" indicates (recursively) all files and
* subdirectories contained in that directory. The special pathname
* "&lt;&lt;ALL FILES&gt;&gt;" matches any file.
* {@literal "<<ALL FILES>>"} matches any file.
*
* <p>A pathname consisting of a single "*" indicates all the files
* in the current directory, while a pathname consisting of a single "-"
@ -264,6 +415,28 @@ public final class FilePermission extends Permission implements Serializable {
*
* <p>A pathname containing an empty string represents an empty path.
*
* @implNote In this implementation, the
* {@code jdk.io.permissionsUseCanonicalPath} system property dictates how
* the {@code path} argument is processed and stored.
* <P>
* If the value of the system property is set to {@code true}, {@code path}
* is canonicalized and stored as a String object named {@code cpath}.
* This means a relative path is converted to an absolute path, a Windows
* DOS-style 8.3 path is expanded to a long path, and a symbolic link is
* resolved to its target, etc.
* <P>
* If the value of the system property is set to {@code false}, {@code path}
* is converted to a {@link java.nio.file.Path} object named {@code npath}
* after {@link Path#normalize() normalization}. No canonicalization is
* performed which means the underlying file system is not accessed.
* <P>
* In either case, the "*" or "-" character at the end of a wildcard
* {@code path} is removed before canonicalization or normalization.
* It is stored in a separate wildcard flag field.
* <P>
* The default value of the {@code jdk.io.permissionsUseCanonicalPath}
* system property is {@code false} in this implementation.
*
* @param path the pathname of the file/directory.
* @param actions the action string.
*
@ -305,6 +478,38 @@ public final class FilePermission extends Permission implements Serializable {
* "/tmp/*" encompasses all files in the "/tmp" directory,
* including the one named "foo".
* </ul>
* <P>
* Precisely, a simple pathname implies another simple pathname
* if and only if they are equal. A simple pathname never implies
* a wildcard pathname. A wildcard pathname implies another wildcard
* pathname if and only if all simple pathnames implied by the latter
* are implied by the former. A wildcard pathname implies a simple
* pathname if and only if
* <ul>
* <li>if the wildcard flag is "*", the simple pathname's path
* must be right inside the wildcard pathname's path.
* <li>if the wildcard flag is "-", the simple pathname's path
* must be recursively inside the wildcard pathname's path.
* </ul>
* <P>
* {@literal "<<ALL FILES>>"} implies every other pathname. No pathname,
* except for {@literal "<<ALL FILES>>"} itself, implies
* {@literal "<<ALL FILES>>"}.
*
* @implNote
* If {@code jdk.io.permissionsUseCanonicalPath} is {@code true}, a
* simple {@code cpath} is inside a wildcard {@code cpath} if and only if
* after removing the base name (the last name in the pathname's name
* sequence) from the former the remaining part equals to the latter,
* a simple {@code cpath} is recursively inside a wildcard {@code cpath}
* if and only if the former starts with the latter.
* <p>
* If {@code jdk.io.permissionsUseCanonicalPath} is {@code false}, a
* simple {@code npath} is inside a wildcard {@code npath} if and only if
* {@code simple_npath.relativize(wildcard_npath)} is exactly "..",
* a simple {@code npath} is recursively inside a wildcard {@code npath}
* if and only if {@code simple_npath.relativize(wildcard_npath)}
* is a series of one or more "..".
*
* @param p the permission to check against.
*
@ -334,45 +539,125 @@ public final class FilePermission extends Permission implements Serializable {
* @return the effective mask
*/
boolean impliesIgnoreMask(FilePermission that) {
if (this.directory) {
if (this.recursive) {
// make sure that.path is longer then path so
// something like /foo/- does not imply /foo
if (that.directory) {
return (that.cpath.length() >= this.cpath.length()) &&
that.cpath.startsWith(this.cpath);
} else {
return ((that.cpath.length() > this.cpath.length()) &&
that.cpath.startsWith(this.cpath));
if (FilePermCompat.nb) {
if (allFiles) {
return true;
}
if (that.allFiles) {
return false;
}
// Left at least same level of wildness as right
if ((this.recursive && that.recursive) != that.recursive
|| (this.directory && that.directory) != that.directory) {
return false;
}
// Same npath is good as long as both or neither are directories
if (this.npath.equals(that.npath)
&& this.directory == that.directory) {
return true;
}
int diff = containsPath(this.npath, that.npath);
// Right inside left is good if recursive
if (diff >= 1 && recursive) {
return true;
}
// Right right inside left if it is element in set
if (diff == 1 && directory && !that.directory) {
return true;
}
// Hack: if a npath2 field exists, apply the same checks
// on it as a fallback.
if (this.npath2 != null) {
if (this.npath2.equals(that.npath)
&& this.directory == that.directory) {
return true;
}
} else {
if (that.directory) {
// if the permission passed in is a directory
// specification, make sure that a non-recursive
// permission (i.e., this object) can't imply a recursive
// permission.
if (that.recursive)
return false;
else
return (this.cpath.equals(that.cpath));
} else {
int last = that.cpath.lastIndexOf(File.separatorChar);
if (last == -1)
return false;
else {
// this.cpath.equals(that.cpath.substring(0, last+1));
// Use regionMatches to avoid creating new string
return (this.cpath.length() == (last + 1)) &&
this.cpath.regionMatches(0, that.cpath, 0, last+1);
}
diff = containsPath(this.npath2, that.npath);
if (diff >= 1 && recursive) {
return true;
}
if (diff == 1 && directory && !that.directory) {
return true;
}
}
} else if (that.directory) {
// if this is NOT recursive/wildcarded,
// do not let it imply a recursive/wildcarded permission
return false;
} else {
return (this.cpath.equals(that.cpath));
if (this.directory) {
if (this.recursive) {
// make sure that.path is longer then path so
// something like /foo/- does not imply /foo
if (that.directory) {
return (that.cpath.length() >= this.cpath.length()) &&
that.cpath.startsWith(this.cpath);
} else {
return ((that.cpath.length() > this.cpath.length()) &&
that.cpath.startsWith(this.cpath));
}
} else {
if (that.directory) {
// if the permission passed in is a directory
// specification, make sure that a non-recursive
// permission (i.e., this object) can't imply a recursive
// permission.
if (that.recursive)
return false;
else
return (this.cpath.equals(that.cpath));
} else {
int last = that.cpath.lastIndexOf(File.separatorChar);
if (last == -1)
return false;
else {
// this.cpath.equals(that.cpath.substring(0, last+1));
// Use regionMatches to avoid creating new string
return (this.cpath.length() == (last + 1)) &&
this.cpath.regionMatches(0, that.cpath, 0, last + 1);
}
}
}
} else if (that.directory) {
// if this is NOT recursive/wildcarded,
// do not let it imply a recursive/wildcarded permission
return false;
} else {
return (this.cpath.equals(that.cpath));
}
}
}
/**
* Returns the depth between an outer path p1 and an inner path p2. -1
* is returned if
*
* - p1 does not contains p2.
* - this is not decidable. For example, p1="../x", p2="y".
* - the depth is not decidable. For example, p1="/", p2="x".
*
* This method can return 2 if the depth is greater than 2.
*
* @param p1 the expected outer path, normalized
* @param p2 the expected inner path, normalized
* @return the depth in between
*/
private static int containsPath(Path p1, Path p2) {
Path p;
try {
p = p2.relativize(p1).normalize();
if (p.getName(0).toString().isEmpty()) {
return 0;
} else {
for (Path item: p) {
String s = item.toString();
if (!s.equals("..")) {
return -1;
}
}
return p.getNameCount();
}
} catch (IllegalArgumentException iae) {
return -1;
}
}
@ -380,6 +665,12 @@ public final class FilePermission extends Permission implements Serializable {
* Checks two FilePermission objects for equality. Checks that <i>obj</i> is
* a FilePermission, and has the same pathname and actions as this object.
*
* @implNote More specifically, two pathnames are the same if and only if
* they have the same wildcard flag and their {@code cpath}
* (if {@code jdk.io.permissionsUseCanonicalPath} is {@code true}) or
* {@code npath} (if {@code jdk.io.permissionsUseCanonicalPath}
* is {@code false}) are equal. Or they are both {@literal "<<ALL FILES>>"}.
*
* @param obj the object we are testing for equality with this object.
* @return <code>true</code> if obj is a FilePermission, and has the same
* pathname and actions as this FilePermission object,
@ -395,10 +686,18 @@ public final class FilePermission extends Permission implements Serializable {
FilePermission that = (FilePermission) obj;
return (this.mask == that.mask) &&
this.cpath.equals(that.cpath) &&
(this.directory == that.directory) &&
(this.recursive == that.recursive);
if (FilePermCompat.nb) {
return (this.mask == that.mask) &&
(this.allFiles == that.allFiles) &&
this.npath.equals(that.npath) &&
(this.directory == that.directory) &&
(this.recursive == that.recursive);
} else {
return (this.mask == that.mask) &&
this.cpath.equals(that.cpath) &&
(this.directory == that.directory) &&
(this.recursive == that.recursive);
}
}
/**
@ -408,7 +707,11 @@ public final class FilePermission extends Permission implements Serializable {
*/
@Override
public int hashCode() {
return 0;
if (FilePermCompat.nb) {
return Objects.hash(mask, allFiles, directory, recursive, npath);
} else {
return 0;
}
}
/**

View File

@ -27,7 +27,9 @@ package java.security;
import java.util.ArrayList;
import java.util.List;
import sun.security.util.Debug;
import sun.security.util.FilePermCompat;
import sun.security.util.SecurityConstants;
@ -175,7 +177,7 @@ public final class AccessControlContext {
/**
* package private to allow calls from ProtectionDomain without performing
* the security check for {@linkplain SecurityConstants.CREATE_ACC_PERMISSION}
* the security check for {@linkplain SecurityConstants#CREATE_ACC_PERMISSION}
* permission
*/
AccessControlContext(AccessControlContext acc,
@ -253,7 +255,8 @@ public final class AccessControlContext {
if (perms[i].getClass() == AllPermission.class) {
parent = null;
}
tmp[i] = perms[i];
// Add altPath into permission for compatibility.
tmp[i] = FilePermCompat.newPermPlusAltPath(perms[i]);
}
}
@ -443,7 +446,7 @@ public final class AccessControlContext {
}
for (int i=0; i< context.length; i++) {
if (context[i] != null && !context[i].implies(perm)) {
if (context[i] != null && !context[i].impliesWithAltFilePerm(perm)) {
if (dumpDebug) {
debug.println("access denied " + perm);
}

View File

@ -32,13 +32,14 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import jdk.internal.misc.JavaSecurityAccess;
import jdk.internal.misc.JavaSecurityProtectionDomainAccess;
import static jdk.internal.misc.JavaSecurityProtectionDomainAccess.ProtectionDomainCache;
import jdk.internal.misc.SharedSecrets;
import sun.security.provider.PolicyFile;
import sun.security.util.Debug;
import sun.security.util.FilePermCompat;
import sun.security.util.SecurityConstants;
/**
@ -303,14 +304,74 @@ public class ProtectionDomain {
}
if (!staticPermissions &&
Policy.getPolicyNoCheck().implies(this, perm))
Policy.getPolicyNoCheck().implies(this, perm)) {
return true;
if (permissions != null)
}
if (permissions != null) {
return permissions.implies(perm);
}
return false;
}
/**
* This method has the same logic flow as {@link #implies} except that
* when the {@link FilePermCompat#compat} flag is on it ensures
* FilePermission compatibility after JDK-8164705. {@code implies()}
* is called when compat flag is not on or user has extended
* {@code ProtectionDomain}.
*
* This method is called by {@link AccessControlContext#checkPermission}
* and not intended to be called by an application.
*/
boolean impliesWithAltFilePerm(Permission perm) {
// If this is a subclass of ProtectionDomain. Call the old method.
if (!FilePermCompat.compat || getClass() != ProtectionDomain.class) {
return implies(perm);
}
if (hasAllPerm) {
// internal permission collection already has AllPermission -
// no need to go to policy
return true;
}
Permission p2 = null;
boolean p2Calculated = false;
if (!staticPermissions) {
Policy policy = Policy.getPolicyNoCheck();
if (policy instanceof PolicyFile) {
// The PolicyFile implementation supports compatibility
// inside and it also covers the static permissions.
return policy.implies(this, perm);
} else {
if (policy.implies(this, perm)) {
return true;
}
p2 = FilePermCompat.newPermUsingAltPath(perm);
p2Calculated = true;
if (p2 != null && policy.implies(this, p2)) {
return true;
}
}
}
if (permissions != null) {
if (permissions.implies(perm)) {
return true;
} else {
if (!p2Calculated) {
p2 = FilePermCompat.newPermUsingAltPath(perm);
}
if (p2 != null) {
return permissions.implies(p2);
}
}
}
return false;
}
// called by the VM -- do not remove
boolean impliesCreateAccessControlContext() {
return implies(SecurityConstants.CREATE_ACC_PERMISSION);

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.misc;
import java.io.FilePermission;
public interface JavaIOFilePermissionAccess {
/**
* Returns a new FilePermission plus an alternative path.
*
* @param input the input
* @return the new FilePermission plus the alt path (as npath2)
* or the input itself if no alt path is available.
*/
FilePermission newPermPlusAltPath(FilePermission input);
/**
* Returns a new FilePermission using an alternative path.
*
* @param input the input
* @return the new FilePermission using the alt path (as npath)
* or null if no alt path is available
*/
FilePermission newPermUsingAltPath(FilePermission input);
}

View File

@ -29,6 +29,7 @@ import java.lang.module.ModuleDescriptor;
import java.util.jar.JarFile;
import java.io.Console;
import java.io.FileDescriptor;
import java.io.FilePermission;
import java.io.ObjectInputStream;
import java.io.RandomAccessFile;
import java.security.ProtectionDomain;
@ -58,6 +59,7 @@ public class SharedSecrets {
private static JavaNetSocketAccess javaNetSocketAccess;
private static JavaNioAccess javaNioAccess;
private static JavaIOFileDescriptorAccess javaIOFileDescriptorAccess;
private static JavaIOFilePermissionAccess javaIOFilePermissionAccess;
private static JavaSecurityProtectionDomainAccess javaSecurityProtectionDomainAccess;
private static JavaSecurityAccess javaSecurityAccess;
private static JavaUtilZipFileAccess javaUtilZipFileAccess;
@ -201,6 +203,17 @@ public class SharedSecrets {
javaIOFileDescriptorAccess = jiofda;
}
public static JavaIOFilePermissionAccess getJavaIOFilePermissionAccess() {
if (javaIOFilePermissionAccess == null)
unsafe.ensureClassInitialized(FilePermission.class);
return javaIOFilePermissionAccess;
}
public static void setJavaIOFilePermissionAccess(JavaIOFilePermissionAccess jiofpa) {
javaIOFilePermissionAccess = jiofpa;
}
public static JavaIOFileDescriptorAccess getJavaIOFileDescriptorAccess() {
if (javaIOFileDescriptorAccess == null)
unsafe.ensureClassInitialized(FileDescriptor.class);

View File

@ -41,10 +41,6 @@ import sun.net.www.*;
import java.util.*;
import java.text.SimpleDateFormat;
import sun.security.action.GetPropertyAction;
import sun.security.action.GetIntegerAction;
import sun.security.action.GetBooleanAction;
public class FileURLConnection extends URLConnection {
static String CONTENT_LENGTH = "content-length";
@ -224,8 +220,13 @@ public class FileURLConnection extends URLConnection {
if (File.separatorChar == '/') {
permission = new FilePermission(decodedPath, "read");
} else {
// decode could return /c:/x/y/z.
if (decodedPath.length() > 2 && decodedPath.charAt(0) == '/'
&& decodedPath.charAt(2) == ':') {
decodedPath = decodedPath.substring(1);
}
permission = new FilePermission(
decodedPath.replace('/',File.separatorChar), "read");
decodedPath.replace('/', File.separatorChar), "read");
}
}
return permission;

View File

@ -45,11 +45,7 @@ import java.util.concurrent.atomic.AtomicReference;
import jdk.internal.misc.JavaSecurityProtectionDomainAccess;
import static jdk.internal.misc.JavaSecurityProtectionDomainAccess.ProtectionDomainCache;
import jdk.internal.misc.SharedSecrets;
import sun.security.util.PolicyUtil;
import sun.security.util.PropertyExpander;
import sun.security.util.Debug;
import sun.security.util.ResourcesMgr;
import sun.security.util.SecurityConstants;
import sun.security.util.*;
import sun.net.www.ParseUtil;
/**
@ -534,8 +530,6 @@ public class PolicyFile extends java.security.Policy {
/**
* Reads a policy configuration into the Policy object using a
* Reader object.
*
* @param policyFile the policy Reader object.
*/
private boolean init(URL policy, PolicyInfo newInfo, boolean defPolicy) {
@ -1099,7 +1093,7 @@ public class PolicyFile extends java.security.Policy {
synchronized (pc) {
Enumeration<Permission> e = pc.elements();
while (e.hasMoreElements()) {
perms.add(e.nextElement());
perms.add(FilePermCompat.newPermPlusAltPath(e.nextElement()));
}
}
}
@ -1127,7 +1121,7 @@ public class PolicyFile extends java.security.Policy {
* object with additional permissions granted to the specified
* ProtectionDomain.
*
* @param perm the Permissions to populate
* @param perms the Permissions to populate
* @param pd the ProtectionDomain associated with the caller.
*
* @return the set of Permissions according to the policy.
@ -1157,8 +1151,8 @@ public class PolicyFile extends java.security.Policy {
* object with additional permissions granted to the specified
* CodeSource.
*
* @param permissions the permissions to populate
* @param codesource the codesource associated with the caller.
* @param perms the permissions to populate
* @param cs the codesource associated with the caller.
* This encapsulates the original location of the code (where the code
* came from) and the public key(s) of its signer.
*
@ -1386,7 +1380,7 @@ public class PolicyFile extends java.security.Policy {
accPs,
perms);
} else {
perms.add(p);
perms.add(FilePermCompat.newPermPlusAltPath(p));
}
}
}
@ -1458,9 +1452,9 @@ public class PolicyFile extends java.security.Policy {
}
try {
// first try to instantiate the permission
perms.add(getInstance(sp.getSelfType(),
perms.add(FilePermCompat.newPermPlusAltPath(getInstance(sp.getSelfType(),
sb.toString(),
sp.getSelfActions()));
sp.getSelfActions())));
} catch (ClassNotFoundException cnfe) {
// ok, the permission is not in the bootclasspath.
// before we add an UnresolvedPermission, check to see

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.util;
import sun.security.action.GetPropertyAction;
import java.io.FilePermission;
import java.security.Permission;
import jdk.internal.misc.SharedSecrets;
/**
* Take care of FilePermission compatibility after JDK-8164705.
*/
public class FilePermCompat {
/**
* New behavior? Keep compatibility? Both default true.
*/
public static final boolean nb;
public static final boolean compat;
static {
String flag = GetPropertyAction.privilegedGetProperty(
"jdk.io.permissionsUseCanonicalPath", "false");
switch (flag) {
case "true":
nb = false;
compat = false;
break;
case "false":
nb = true;
compat = true;
break;
default:
throw new RuntimeException(
"Invalid jdk.io.permissionsUseCanonicalPath: " + flag);
}
}
public static Permission newPermPlusAltPath(Permission input) {
if (compat && input instanceof FilePermission) {
return SharedSecrets.getJavaIOFilePermissionAccess()
.newPermPlusAltPath((FilePermission) input);
}
return input;
}
public static Permission newPermUsingAltPath(Permission input) {
if (input instanceof FilePermission) {
return SharedSecrets.getJavaIOFilePermissionAccess()
.newPermUsingAltPath((FilePermission) input);
}
return null;
}
}

View File

@ -0,0 +1,182 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
*
* @test
* @bug 8164705
* @summary Remove pathname canonicalization from FilePermission
*/
import java.io.FilePermission;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Correctness {
static boolean err = false;
static Method containsMethod;
static boolean isWindows =
System.getProperty("os.name").contains("Windows");
public static void main(String args[]) throws Exception {
check("/", "/");
checkNo("/", "/x");
checkNo("/", "/../x");
checkNo("/", "x");
check("/-", "/*");
checkNo("/*", "/-");
check("/*", "/x");
check("/-", "/x");
check("/-", "/x/*");
check("/-", "/x/-");
check("/-", "/x/y");
checkNo("/*", "/x/y");
check("/x/*", "/x/x");
checkNo("/x/-", "/x");
checkNo("/x/*", "/x");
check("/x/-", "/x/x");
check("/x/-", "/x/x/y");
checkNo("/x/*", "/x/x/y");
checkNo("/x/*", "/x");
check("*", "x");
checkNo("", "x");
check("-", "x");
check("-", "*");
check("-", "a/-");
check("-", "a/*");
checkNo("*", "a/b");
check("a/*", "a/b");
check("a/-", "a/*");
check("a/-", "a/b/c");
checkNo("a/*", "a/b/c");
check("../", "../");
check("../-", "../*");
check("../../*", "../../a");
// If we allow .. and abs/rel checks
check("../-", "a");
check("../../-", "-");
checkNo("../../*", "a");
//check("/-", "a");
//checkNo("/*", "a");
//check("/-", "-");
try {
// containsPath is broken on Windows.
containsMethod = FilePermission.class.getDeclaredMethod(
"containsPath", Path.class, Path.class);
containsMethod.setAccessible(true);
System.out.println();
contains("x", "x", 0);
contains("x", "x/y", 1);
contains("x", "x/y/z", 2);
contains("x", "y", -1);
contains("x", "", -1);
contains("", "", 0);
contains("", "x", 1);
contains("", "x/y", 2);
contains("/", "/", 0);
contains("/", "/x", 1);
contains("/", "/x/y", 2);
contains("/x", "/x/y", 1);
contains("/x", "/y", -1);
//contains("/", "..", Integer.MAX_VALUE);
//contains("/", "x", Integer.MAX_VALUE);
//contains("/", "x/y", Integer.MAX_VALUE);
//contains("/", "../x", Integer.MAX_VALUE);
contains("/x", "y", -1);
contains("x", "/y", -1);
contains("", "..", -1);
contains("", "../x", -1);
contains("..", "", 1);
contains("..", "x", 2);
contains("..", "x/y", 3);
contains("../x", "x", -1);
contains("../x", "y", -1);
contains("../x", "../x/y", 1);
contains("../../x", "../../x/y", 1);
contains("../../../x", "../../../x/y", 1);
contains("../x", "../y", -1);
} catch (NoSuchMethodException e) {
// Ignored
}
if (err) throw new Exception("Failed.");
}
// Checks if s2 is inside s1 and depth is expected.
static void contains(String s1, String s2, int expected) throws Exception {
contains0(s1, s2, expected);
if (isWindows) {
contains0("C:" + s1, s2, -1);
contains0(s1, "C:" + s2, -1);
contains0("C:" + s1, "D:" + s2, -1);
contains0("C:" + s1, "C:" + s2, expected);
}
}
static void contains0(String s1, String s2, int expected) throws Exception {
Path p1 = Paths.get(s1);
Path p2 = Paths.get(s2);
int d = (int)containsMethod.invoke(null, p1, p2);
Path p;
try {
p = p2.relativize(p1);
} catch (Exception e) {
p = null;
}
System.out.printf("%-20s -> %-20s: %20s %5d %5d %s\n", s1, s2, p,
d, expected, d==expected?"":" WRONG");
if (d != expected) {
err = true;
}
}
static void check(String s1, String s2, boolean expected) {
FilePermission fp1 = new FilePermission(s1, "read");
FilePermission fp2 = new FilePermission(s2, "read");
boolean b = fp1.implies(fp2);
System.out.printf("%-30s -> %-30s: %5b %s\n",
s1, s2, b, b==expected?"":" WRONG");
if (b != expected) {
err = true;
System.out.println(fp1);
System.out.println(fp2);
}
}
static void check(String s1, String s2) {
check(s1, s2, true);
}
static void checkNo(String s1, String s2) {
check(s1, s2, false);
}
}

View File

@ -47,14 +47,14 @@ public class FilePermissionCollection {
("test 1: add throws IllegalArgExc for wrong perm type");
try {
perms.add(new SecurityPermission("createAccessControlContext"));
System.err.println("Expected IllegalArgumentException");
System.out.println("Expected IllegalArgumentException");
testFail++;
} catch (IllegalArgumentException iae) {}
// test 2
System.out.println("test 2: implies returns false for wrong perm type");
if (perms.implies(new SecurityPermission("getPolicy"))) {
System.err.println("Expected false, returned true");
System.out.println("Expected false, returned true");
testFail++;
}
@ -63,7 +63,7 @@ public class FilePermissionCollection {
"name and action");
perms.add(new FilePermission("/tmp/foo", "read"));
if (!perms.implies(new FilePermission("/tmp/foo", "read"))) {
System.err.println("Expected true, returned false");
System.out.println("Expected true, returned false");
testFail++;
}
@ -71,7 +71,7 @@ public class FilePermissionCollection {
System.out.println("test 4: implies returns false for match on " +
"name but not action");
if (perms.implies(new FilePermission("/tmp/foo", "write"))) {
System.err.println("Expected false, returned true");
System.out.println("Expected false, returned true");
testFail++;
}
@ -80,7 +80,7 @@ public class FilePermissionCollection {
"name and subset of actions");
perms.add(new FilePermission("/tmp/bar", "read, write"));
if (!perms.implies(new FilePermission("/tmp/bar", "write"))) {
System.err.println("Expected true, returned false");
System.out.println("Expected true, returned false");
testFail++;
}
@ -90,11 +90,11 @@ public class FilePermissionCollection {
perms.add(new FilePermission("/tmp/baz", "read"));
perms.add(new FilePermission("/tmp/baz", "write"));
if (!perms.implies(new FilePermission("/tmp/baz", "read"))) {
System.err.println("Expected true, returned false");
System.out.println("Expected true, returned false");
testFail++;
}
if (!perms.implies(new FilePermission("/tmp/baz", "write,read"))) {
System.err.println("Expected true, returned false");
System.out.println("Expected true, returned false");
testFail++;
}
@ -103,7 +103,7 @@ public class FilePermissionCollection {
"and match on action");
perms.add(new FilePermission("/usr/tmp/*", "read"));
if (!perms.implies(new FilePermission("/usr/tmp/foo", "read"))) {
System.err.println("Expected true, returned false");
System.out.println("Expected true, returned false");
testFail++;
}
@ -111,7 +111,7 @@ public class FilePermissionCollection {
System.out.println
("test 8: implies returns false for non-match on wildcard");
if (perms.implies(new FilePermission("/usr/tmp/bar/foo", "read"))) {
System.err.println("Expected false, returned true");
System.out.println("Expected false, returned true");
testFail++;
}
@ -120,25 +120,25 @@ public class FilePermissionCollection {
("test 9: implies returns true for deep wildcard match");
perms.add(new FilePermission("/usr/tmp/-", "read"));
if (!perms.implies(new FilePermission("/usr/tmp/bar/foo", "read"))) {
System.err.println("Expected true, returned false");
System.out.println("Expected true, returned false");
testFail++;
}
// test 10
System.out.println("test 10: implies returns true for relative match");
//System.out.println("test 10: implies returns true for relative match");
perms.add(new FilePermission(".", "read"));
if (!perms.implies(new FilePermission(System.getProperty("user.dir"),
"read"))) {
System.err.println("Expected true, returned false");
testFail++;
}
//if (!perms.implies(new FilePermission(System.getProperty("user.dir"),
// "read"))) {
// System.out.println("Expected true, returned false");
// testFail++;
//}
// test 11
System.out.println("test 11: implies returns true for all " +
"wildcard and match on action");
perms.add(new FilePermission("<<ALL FILES>>", "read"));
if (!perms.implies(new FilePermission("/tmp/foobar", "read"))) {
System.err.println("Expected true, returned false");
System.out.println("Expected true, returned false");
testFail++;
}
@ -146,7 +146,7 @@ public class FilePermissionCollection {
System.out.println("test 12: implies returns false for wildcard " +
"and non-match on action");
if (perms.implies(new FilePermission("/tmp/foobar", "write"))) {
System.err.println("Expected false, returned true");
System.out.println("Expected false, returned true");
testFail++;
}
@ -160,7 +160,7 @@ public class FilePermissionCollection {
}
// the two "/tmp/baz" entries were combined into one
if (numPerms != 7) {
System.err.println("Expected 7, got " + numPerms);
System.out.println("Expected 7, got " + numPerms);
testFail++;
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8164705
* @library /lib/testlibrary /test/lib
* @modules java.base/jdk.internal.misc
* @run main ReadFileOnPath
* @summary Still able to read file on the same path
*/
import jdk.test.lib.process.ProcessTools;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ReadFileOnPath {
private static final Path SRC_DIR = Paths.get(System.getProperty("test.src"));
private static final Path HERE_DIR = Paths.get(".");
private static final Path MODS_DIR = Paths.get("modules");
public static void main(String args[]) throws Exception {
CompilerUtils.compile(SRC_DIR.resolve("m"), MODS_DIR.resolve("m"));
Files.write(MODS_DIR.resolve("m/base"), "base".getBytes());
Files.write(MODS_DIR.resolve("m/p/child"), "child".getBytes());
JarUtils.createJarFile(HERE_DIR.resolve("old.jar"),
MODS_DIR.resolve("m"),
"base", "p/App.class", "p/child");
JarUtils.createJarFile(HERE_DIR.resolve("new.jar"),
MODS_DIR.resolve("m"),
"module-info.class", "base", "p/App.class", "p/child");
// exploded module
test("--module-path", "modules", "-m", "m/p.App", "SS+++++");
// module in jar
test("--module-path", "new.jar", "-m", "m/p.App", "SSSS++0");
// exploded classpath
test("-cp", "modules/m", "p.App", "SS+++++");
// classpath in jar
test("-cp", "old.jar", "p.App", "SSSS++0");
}
static void test(String... args) throws Exception {
List<String> cmds = new ArrayList<>();
cmds.add("-Djava.security.manager");
cmds.addAll(Arrays.asList(args));
cmds.addAll(List.of(
"x", "modules/m", "modules/m/base", "modules/m/p/child",
"-", "child", "/base", "../base"));
ProcessTools.executeTestJvm(cmds.toArray(new String[cmds.size()]))
.shouldHaveExitValue(0);
}
}

View File

@ -0,0 +1,3 @@
module m {
exports p;
}

View File

@ -0,0 +1,44 @@
package p;
import java.io.InputStream;
import java.io.FileInputStream;
public class App {
public static void main(String[] args) throws Exception {
boolean f = true;
StringBuilder sb = new StringBuilder();
String expected = null;
for (String s: args) {
if (expected == null) {
expected = s;
} else if (s.equals("-")) {
f = false;
} else if (f) {
try (InputStream is = new FileInputStream(s)) {
is.readAllBytes();
sb.append('+');
} catch (SecurityException se) {
System.out.println(se);
sb.append('S');
} catch (Exception e) {
System.out.println(e);
sb.append('-');
}
} else {
try (InputStream is = App.class.getResourceAsStream(s)) {
is.readAllBytes();
sb.append('+');
} catch (NullPointerException npe) {
System.out.println(npe);
sb.append('0');
} catch (Exception e) {
System.out.println(e);
sb.append('-');
}
}
}
if (!sb.toString().equals(expected)) {
throw new Exception("Expected " + expected + ", actually " + sb);
} else {
System.out.println("OK");
}
}
}

View File

@ -26,12 +26,12 @@ import java.io.*;
public class Test {
public static void main (String[] args) throws Exception {
test1();
test1(args[0]);
}
public static void test1 () throws Exception {
public static void test1 (String s) throws Exception {
URLClassLoader cl = new URLClassLoader (new URL[] {
new URL ("file:./test.jar")
new URL ("file:" + s)
});
Class clazz = Class.forName ("Test\u00a3", true, cl);
InputStream is = clazz.getResourceAsStream ("Test\u00a3.class");

View File

@ -39,18 +39,33 @@ POLICY
checkExit () {
if [ $? != 0 ]; then
exit 1;
exit $1;
fi
}
${COMPILEJAVA}/bin/javac ${TESTJAVACOPTS} ${TESTTOOLVMOPTS} -d . ${TESTSRC}/Test.java
cp ${TESTSRC}/test.jar .
${TESTJAVA}/bin/java ${TESTVMOPTS} Test
checkExit
${TESTJAVA}/bin/java ${TESTVMOPTS} Test ./test.jar
checkExit 1
# try with security manager
${TESTJAVA}/bin/java ${TESTVMOPTS} -Djava.security.policy=file:./policy -Djava.security.manager Test
checkExit
${TESTJAVA}/bin/java ${TESTVMOPTS} -Djava.security.policy=file:./policy \
-Djava.security.manager Test ./test.jar
checkExit 2
mkdir tmp
cd tmp
${TESTJAVA}/bin/java ${TESTVMOPTS} -Djava.security.policy=file:../policy \
-cp .. -Djava.security.manager Test ../test.jar
checkExit 3
cd ..
THISDIR=$(basename $(pwd))
cd ..
${TESTJAVA}/bin/java ${TESTVMOPTS} -Djava.security.policy=file:$THISDIR/policy \
-cp $THISDIR -Djava.security.manager Test $THISDIR/test.jar
checkExit 4
exit 0

View File

@ -29,6 +29,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Permission;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
@ -37,6 +38,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@ -49,7 +51,8 @@ import java.util.stream.Stream;
* .args("x") // with args
* .env("env", "value") // and an environment variable
* .prop("key","value") // and a system property
* .perm(perm) // with granted permissions
* .grant(file) // grant codes in this codebase
* .perm(perm) // with the permission
* .start(); // and start
*
* create/start must be called, args/env/prop/perm can be called zero or
@ -57,7 +60,7 @@ import java.util.stream.Stream;
*
* The controller can call inheritIO to share its I/O to the process.
* Otherwise, it can send data into a proc's stdin with write/println, and
* read its stdout with readLine. stderr is always redirected to DFILE
* read its stdout with readLine. stderr is always redirected to a file
* unless nodump() is called. A protocol is designed to make
* data exchange among the controller and the processes super easy, in which
* useful data are always printed with a special prefix ("PROCISFUN:").
@ -84,10 +87,10 @@ import java.util.stream.Stream;
*
* As the Proc objects are hidden so deeply, two static methods, d(String) and
* d(Throwable) are provided to output info into stderr, where they will
* normally be appended messages to DFILE (unless nodump() is called).
* normally be appended messages to a debug file (unless nodump() is called).
* Developers can view the messages in real time by calling
*
* tail -f proc.debug
* {@code tail -f stderr.<debug>}
*
* TODO:
*
@ -104,19 +107,24 @@ public class Proc {
private BufferedReader br; // the stdout of a process
private String launcher; // Optional: the java program
private List<Permission> perms = new ArrayList<>();
private List<String> args = new ArrayList<>();
private Map<String,String> env = new HashMap<>();
private Map<String,String> prop = new HashMap();
private boolean inheritIO = false;
private boolean noDump = false;
private List<String> cp; // user-provided classpath
private String clazz; // Class to launch
private String debug; // debug flag, controller will show data
// transfer between procs
// transfer between procs. If debug is set,
// it MUST be different between Procs.
final private static String PREFIX = "PROCISFUN:";
final private static String DFILE = "proc.debug";
// policy file
final private StringBuilder perms = new StringBuilder();
// temporary saving the grant line in a policy file
final private StringBuilder grant = new StringBuilder();
// The following methods are called by controllers
@ -168,10 +176,68 @@ public class Proc {
prop.put(a, b);
return this;
}
// Adds a perm to policy. Can be called multiple times. In order to make it
// effective, please also call prop("java.security.manager", "").
// Sets classpath. If not called, Proc will choose a classpath. If called
// with no arg, no classpath will be used. Can be called multiple times.
public Proc cp(String... s) {
if (cp == null) {
cp = new ArrayList<>();
}
cp.addAll(Arrays.asList(s));
return this;
}
// Adds a permission to policy. Can be called multiple times.
// All perm() calls after a series of grant() calls are grouped into
// a single grant block. perm() calls before any grant() call are grouped
// into a grant block with no restriction.
// Please note that in order to make permissions effective, also call
// prop("java.security.manager", "").
public Proc perm(Permission p) {
perms.add(p);
if (grant.length() != 0) { // Right after grant(s)
if (perms.length() != 0) { // Not first block
perms.append("};\n");
}
perms.append("grant ").append(grant).append(" {\n");
grant.setLength(0);
} else {
if (perms.length() == 0) { // First block w/o restriction
perms.append("grant {\n");
}
}
if (p.getActions().isEmpty()) {
String s = String.format("%s \"%s\"",
p.getClass().getCanonicalName(),
p.getName()
.replace("\\", "\\\\").replace("\"", "\\\""));
perms.append(" permission ").append(s).append(";\n");
} else {
String s = String.format("%s \"%s\", \"%s\"",
p.getClass().getCanonicalName(),
p.getName()
.replace("\\", "\\\\").replace("\"", "\\\""),
p.getActions());
perms.append(" permission ").append(s).append(";\n");
}
return this;
}
// Adds a grant option to policy. If called in a row, a single grant block
// with all options will be created. If there are perm() call(s) between
// grant() calls, they belong to different grant blocks
// grant on a principal
public Proc grant(Principal p) {
grant.append("principal ").append(p.getClass().getName())
.append(" \"").append(p.getName()).append("\", ");
return this;
}
// grant on a codebase
public Proc grant(File f) {
grant.append("codebase \"").append(f.toURI()).append("\", ");
return this;
}
// arbitrary grant
public Proc grant(String v) {
grant.append(v).append(", ");
return this;
}
// Starts the proc
@ -191,30 +257,22 @@ public class Proc {
Collections.addAll(cmd, splitProperty("test.vm.opts"));
Collections.addAll(cmd, splitProperty("test.java.opts"));
cmd.add("-cp");
cmd.add(System.getProperty("test.class.path") + File.pathSeparator +
System.getProperty("test.src.path"));
if (cp == null) {
cmd.add("-cp");
cmd.add(System.getProperty("test.class.path") + File.pathSeparator +
System.getProperty("test.src.path"));
} else if (!cp.isEmpty()) {
cmd.add("-cp");
cmd.add(cp.stream().collect(Collectors.joining(File.pathSeparator)));
}
for (Entry<String,String> e: prop.entrySet()) {
cmd.add("-D" + e.getKey() + "=" + e.getValue());
}
if (!perms.isEmpty()) {
Path p = Files.createTempFile(
Paths.get(".").toAbsolutePath(), "policy", null);
StringBuilder sb = new StringBuilder();
sb.append("grant {\n");
for (Permission perm: perms) {
// Sometimes a permission has no name or actions.
// but it's safe to use an empty string.
String s = String.format("%s \"%s\", \"%s\"",
perm.getClass().getCanonicalName(),
perm.getName()
.replace("\\", "\\\\").replace("\"", "\\\""),
perm.getActions());
sb.append(" permission ").append(s).append(";\n");
}
sb.append("};\n");
Files.write(p, sb.toString().getBytes());
if (perms.length() > 0) {
Path p = Paths.get(getId("policy")).toAbsolutePath();
perms.append("};\n");
Files.write(p, perms.toString().getBytes());
cmd.add("-Djava.security.policy=" + p.toString());
}
cmd.add(clazz);
@ -223,6 +281,15 @@ public class Proc {
}
if (debug != null) {
System.out.println("PROC: " + debug + " cmdline: " + cmd);
for (String c : cmd) {
if (c.indexOf('\\') >= 0 || c.indexOf(' ') > 0) {
System.out.print('\'' + c + '\'');
} else {
System.out.print(c);
}
System.out.print(' ');
}
System.out.println();
}
ProcessBuilder pb = new ProcessBuilder(cmd);
for (Entry<String,String> e: env.entrySet()) {
@ -233,12 +300,17 @@ public class Proc {
} else if (noDump) {
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
} else {
pb.redirectError(ProcessBuilder.Redirect.appendTo(new File(DFILE)));
pb.redirectError(ProcessBuilder.Redirect
.appendTo(new File(getId("stderr"))));
}
p = pb.start();
br = new BufferedReader(new InputStreamReader(p.getInputStream()));
return this;
}
String getId(String prefix) {
if (debug != null) return prefix + "." + debug;
else return prefix + "." + System.identityHashCode(this);
}
// Reads a line from stdout of proc
public String readLine() throws IOException {
String s = br.readLine();

View File

@ -150,9 +150,9 @@ public class CanonPath {
//
// on unix, /- implies everything
if (w.implies(u) || !u.implies(w)) {
throw new Exception("SLASH/- test failed");
}
//if (w.implies(u) || !u.implies(w)) {
// throw new Exception("SLASH/- test failed");
//}
}

View File

@ -0,0 +1,270 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8164705
* @summary check compatibility after FilePermission change
* @library /java/security/testlibrary/
* @modules java.base/jdk.internal.misc
* @run main CompatImpact prepare
* @run main CompatImpact builtin
* @run main CompatImpact mine
* @run main CompatImpact dopriv
*/
import java.io.File;
import java.io.FilePermission;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.SecurityPermission;
public class CompatImpact {
public static void main(String[] args) throws Exception {
switch (args[0]) {
// copy class files to future classpath
case "prepare":
// cp in .
String cp = System.getProperty("test.classes");
Files.copy(Paths.get(cp, "CompatImpact.class"),
Paths.get("CompatImpact.class"));
Files.copy(Paths.get(cp, "CompatImpact$MP.class"),
Paths.get("CompatImpact$MP.class"));
Files.write(Paths.get("f"), new byte[10]);
// cp in ./sub
Files.createDirectory(Paths.get("sub"));
Files.copy(Paths.get(cp, "CompatImpact.class"),
Paths.get("sub", "CompatImpact.class"));
Files.copy(Paths.get(cp, "CompatImpact$MP.class"),
Paths.get("sub", "CompatImpact$MP.class"));
Files.write(Paths.get("sub", "f"), new byte[10]);
// cp in ./inner
Files.createDirectory(Paths.get("inner"));
Files.copy(Paths.get(cp, "CompatImpact$DoPrivInner.class"),
Paths.get("inner", "CompatImpact$DoPrivInner.class"));
break;
// run tests with different policy impls
case "builtin":
case "mine":
cp = System.getProperty("test.classes");
Proc p;
String failed = "";
String testcase = "";
String cwd = System.getProperty("user.dir");
// Granting a FilePermission on an absolute path
testcase = "PonA";
p = p(args[0], cwd + "/f")
.args("f", cwd + "/f")
.debug(testcase)
.start();
if (p.waitFor() != 0) {
Files.copy(Paths.get("stderr." + testcase), System.out);
failed += testcase + " ";
}
// Granting a FilePermission on a relative path
testcase = "PonR";
p = p(args[0], "f")
.args("f", cwd + "/f")
.debug(testcase)
.start();
if (p.waitFor() != 0) {
Files.copy(Paths.get("stderr." + testcase), System.out);
failed += testcase + " ";
}
// Reading file on classpath, not cwd
testcase = "cp";
String cprel = Paths.get(cwd).relativize(Paths.get(cp))
.normalize().toString();
p = p(args[0], "x")
.args(cp + "/f", cprel + "/f")
.debug(testcase)
.start();
if (p.waitFor() != 0) {
Files.copy(Paths.get("stderr." + testcase), System.out);
failed += testcase + " ";
}
// Reading file on classpath, cwd
testcase = "cpHere";
p = p(args[0], "x")
.args(cwd + "/f", "f", "RES")
.cp(".") // Must! cancel the old CLASSPATH.
.debug(testcase)
.start();
if (p.waitFor() != 0) {
Files.copy(Paths.get("stderr." + testcase), System.out);
failed += testcase + " ";
}
// Reading file on classpath, cwd
testcase = "cpSub";
p = p(args[0], "x")
.args(cwd + "/sub/f", "sub/f", "RES")
.cp("sub") // Must! There's CLASSPATH.
.debug(testcase)
.start();
if (p.waitFor() != 0) {
Files.copy(Paths.get("stderr." + testcase), System.out);
failed += testcase + " ";
}
if (!failed.isEmpty()) {
throw new Exception(failed + "failed");
}
break;
// test <policy_type> <grant> <read...>
case "test":
if (args[1].equals("mine")) {
Policy.setPolicy(new MP(args[2]));
}
Exception e = null;
for (int i = 3; i < args.length; i++) {
try {
System.out.println(args[i]);
if (args[i].equals("RES")) {
CompatImpact.class.getResourceAsStream("f")
.close();
} else {
new File(args[i]).exists();
}
} catch (Exception e2) {
e = e2;
e2.printStackTrace(System.out);
}
}
if (e != null) {
System.err.println("====================");
throw e;
}
break;
// doPrivWithPerm test launcher
case "dopriv":
cwd = System.getProperty("user.dir");
// caller (CompatImpact doprivouter, no permission) in sub,
// executor (DoPrivInner, AllPermission) in inner.
p = Proc.create("CompatImpact")
.args("doprivouter")
.prop("java.security.manager", "")
.grant(new File("inner"))
.perm(new AllPermission())
.cp("sub", "inner")
.debug("doPriv")
.args(cwd)
.start();
if (p.waitFor() != 0) {
throw new Exception("dopriv test fails");
}
break;
// doprivouter <cwd>
case "doprivouter":
DoPrivInner.main(args);
break;
default:
throw new Exception("unknown " + args[0]);
}
}
// Call by CompatImpact doprivouter, with AllPermission
public static class DoPrivInner {
public static void main(String[] args) throws Exception {
AccessController.doPrivileged((PrivilegedAction<Boolean>)
() -> new File("x").exists(),
null,
new FilePermission(args[1] + "/x", "read"));
AccessController.doPrivileged((PrivilegedAction<Boolean>)
() -> new File(args[1] + "/x").exists(),
null,
new FilePermission("x", "read"));
try {
AccessController.doPrivileged((PrivilegedAction<Boolean>)
() -> new File("x").exists(),
null,
new FilePermission("y", "read"));
throw new Exception("Should not read");
} catch (SecurityException se) {
// Expected
}
}
}
// Return a Proc object for different policy types
private static Proc p(String type, String f) throws Exception {
Proc p = Proc.create("CompatImpact")
.prop("java.security.manager", "");
p.args("test", type);
switch (type) {
case "builtin":
// For builtin policy, reading access to f can be
// granted as a permission
p.perm(new FilePermission(f, "read"));
p.args("-");
break;
case "mine":
// For my policy, f is passed into test and new MP(f)
// will be set as new policy
p.perm(new SecurityPermission("setPolicy"));
p.args(f);
break;
default:
throw new Exception("unknown " + type);
}
return p;
}
// My own Policy impl, with only one granted permission, also not smart
// enough to know whether ProtectionDomain grants any permission
static class MP extends Policy {
final PermissionCollection pc;
MP(String f) {
FilePermission p = new FilePermission(f, "read");
pc = p.newPermissionCollection();
pc.add(p);
}
@Override
public PermissionCollection getPermissions(CodeSource codesource) {
return pc;
}
@Override
public PermissionCollection getPermissions(ProtectionDomain domain) {
return pc;
}
@Override
public boolean implies(ProtectionDomain domain, Permission permission) {
return pc.implies(permission);
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8164705
* @summary check jdk.filepermission.canonicalize
* @library /java/security/testlibrary/
* @modules java.base/jdk.internal.misc
* @run main/othervm -Djdk.io.permissionsUseCanonicalPath=true Flag truetrue
* @run main/othervm -Djdk.io.permissionsUseCanonicalPath=false Flag falsetrue
* @run main/othervm Flag falsetrue
*/
import java.io.File;
import java.io.FilePermission;
import java.lang.*;
import java.security.Permission;
import java.security.Policy;
import java.security.ProtectionDomain;
public class Flag {
public static void main(String[] args) throws Exception {
boolean test1;
boolean test2;
String here = System.getProperty("user.dir");
File abs = new File(here, "x");
FilePermission fp1 = new FilePermission("x", "read");
FilePermission fp2 = new FilePermission(abs.toString(), "read");
test1 = fp1.equals(fp2);
Policy pol = new Policy() {
@java.lang.Override
public boolean implies(ProtectionDomain domain, Permission permission) {
return fp1.implies(permission);
}
};
Policy.setPolicy(pol);
System.setSecurityManager(new SecurityManager());
try {
System.getSecurityManager().checkPermission(fp2);
test2 = true;
} catch (SecurityException se) {
test2 = false;
}
if (!args[0].equals(test1 + "" + test2)) {
throw new Exception("Test failed: " + test1 + test2);
}
}
}