Path: blob/master/test/jdk/java/lang/SecurityManager/CheckPackageMatching.java
41149 views
/*1* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 807269226* @summary Check the matching implemented by SecurityManager.checkPackageAccess27* @run main/othervm -Djava.security.manager=allow CheckPackageMatching28*/2930import java.security.Security;31import java.util.ArrayList;32import java.util.Arrays;33import java.util.Collection;34import java.util.Collections;35import java.util.List;36import java.util.StringTokenizer;3738/*39* The purpose of this test is not to verify the content of the package40* access list - but to ensure that the matching implemented by the41* SecurityManager is correct. This is why we have our own pattern matching42* algorithm here.43*/44public class CheckPackageMatching {4546/**47* The restricted packages listed in the package.access property of the48* java.security file.49*/50private static final String[] packages = actual().toArray(new String[0]);5152/**53* Returns the list of restricted packages in the package.access property.54*/55private static List<String> actual() {56String prop = Security.getProperty("package.access");57List<String> packages = new ArrayList<>();58if (prop != null && !prop.equals("")) {59StringTokenizer tok = new StringTokenizer(prop, ",");60while (tok.hasMoreElements()) {61String s = tok.nextToken().trim();62packages.add(s);63}64}65return packages;66}6768/**69* PackageMatcher implements a state machine that matches package70* names against packages parsed from the package access list.71*/72private abstract static class PackageMatcher {73// For each state, chars[state] contains the chars that matches.74private final char[][] chars;75// For each state, states[state][i] contains the next state to go76// to when chars[state][i] matches the current character.77private final int[][] states;7879// Some markers. We're making the assumption that 080// cannot be a valid character for a package name.81//82// We use 0 for marking that we expect an end of string in83// char[state][i].84private static final char END_OF_STRING = 0;85// This special state value indicates that we expect the string to end86// there.87private static final int END_STATE = -1;88// This special state value indicates that we can accept any character89// from now on.90private static final int WILDCARD_STATE = Integer.MIN_VALUE;9192// Create the data for a new state machine to match package names from93// the array of package names passed as argument.94// Each package name in the array is expected to end with '.'95// For each package in packages we're going to compile state data96// that will match the regexp:97// ^packages[i].substring(0, packages[i].length()-1).replace(".","\\.")$|^packages[i].replace(".","\\.").*98//99// Let's say the package array is:100//101// String[] packages = { "sun.", "com.sun.jmx.", "com.sun.proxy.",102// "apple." };103//104// then the state machine will need data that looks like:105//106// char[][] chars = {107// { 'a', 'c', 's' }, { 'p' }, { 'p' }, { 'l' }, { 'e' }, { 0, '.' },108// { 'o' }, { 'm' }, { '.' }, { 's' }, { 'u' }, { 'n' }, { '.' },109// { 'j', 'p'},110// { 'm' }, { 'x' }, { 0, '.' },111// { 'r' }, { 'o' }, { 'x' }, { 'y' }, { 0, '.' },112// { 'u' }, { 'n' }, { 0, '.' }113// }114// int[][] states = {115// { 1, 6, 22 }, { 2 }, { 3 }, { 4 }, { 5 },116// { END_STATE, WILDCARD_STATE },117// { 7 }, { 8 }, { 9 }, { 10 }, { 11 }, { 12 }, { 13 }, { 14, 17 },118// { 15 }, { 16 }, { END_STATE, WILDCARD_STATE },119// { 18 }, { 19 }, { 20 }, { 21 }, { END_STATE, WILDCARD_STATE },120// { 23 }, { 24 }, { END_STATE, WILDCARD_STATE }121// }122//123// The machine will start by loading the chars and states for state 0124// chars[0] => { 'a', 'c', 's' } states[0] => { 1, 6, 22 }125// then it examines the char at index 0 in the candidate name.126// if the char matches one of the characters in chars[0], then it goes127// to the corresponding state in states[0]. For instance - if the first128// char in the candidate name is 's', which corresponds to chars[0][2] -129// then it will proceed with the next char in the candidate name and go130// to state 22 (as indicated by states[0][2]) - where it will load the131// chars and states for states 22: chars[22] = { 'u' },132// states[22] = { 23 } etc... until the candidate char at the current133// index matches no char in chars[states] => the candidate name doesn't134// match - or until it finds a success termination condition: the135// candidate chars are exhausted and states[state][0] is END_STATE, or136// the candidate chars are not exhausted - and137// states[state][chars[state]] is WILDCARD_STATE indicating a '.*' like138// regexp.139//140// [Note that the chars in chars[i] are sorted]141//142// The compile(...) method is reponsible for building the state machine143// data and is called only once in the constructor.144//145// The matches(String candidate) method will tell whether the candidate146// matches by implementing the algorithm described above.147//148PackageMatcher(String[] packages) {149final boolean[] selected = new boolean[packages.length];150Arrays.fill(selected, true);151final ArrayList<char[]> charList = new ArrayList<>();152final ArrayList<int[]> stateList = new ArrayList<>();153compile(0, 0, packages, selected, charList, stateList);154chars = charList.toArray(new char[0][0]);155states = stateList.toArray(new int[0][0]);156}157158/**159* Compiles the state machine data (recursive).160*161* @param step The index of the character which we're looking at in162* this step.163* @param state The current state (starts at 0).164* @param pkgs The list of packages from which the automaton is built.165* @param selected Indicates which packages we're looking at in this166step.167* @param charList The list from which we will build168{@code char[][] chars;}169* @param stateList The list from which we will build170{@code int[][] states;}171* @return the next available state.172*/173private int compile(int step, int state, String[] pkgs,174boolean[] selected, ArrayList<char[]> charList,175ArrayList<int[]> stateList) {176final char[] next = new char[pkgs.length];177final int[] nexti = new int[pkgs.length];178int j = 0;179char min = Character.MAX_VALUE; char max = 0;180for (int i = 0; i < pkgs.length; i++) {181if (!selected[i]) continue;182final String p = pkgs[i];183final int len = p.length();184if (step > len) {185selected[i] = false;186continue;187}188if (len - 1 == step) {189boolean unknown = true;190for (int k = 0; k < j ; k++) {191if (next[k] == END_OF_STRING) {192unknown = false;193break;194}195}196if (unknown) {197next[j] = END_OF_STRING;198j++;199}200nexti[i] = END_STATE;201}202final char c = p.charAt(step);203nexti[i] = len - 1 == step ? END_STATE : c;204boolean unknown = j == 0 || c < min || c > max;205if (!unknown) {206if (c != min || c != max) {207unknown = true;208for (int k = 0; k < j ; k++) {209if (next[k] == c) {210unknown = false;211break;212}213}214}215}216if (unknown) {217min = min > c ? c : min;218max = max < c ? c : max;219next[j] = c;220j++;221}222}223final char[] nc = new char[j];224final int[] nst = new int[j];225System.arraycopy(next, 0, nc, 0, nc.length);226Arrays.sort(nc);227final boolean ns[] = new boolean[pkgs.length];228229charList.ensureCapacity(state + 1);230stateList.ensureCapacity(state + 1);231charList.add(state, nc);232stateList.add(state, nst);233state = state + 1;234for (int k = 0; k < nc.length; k++) {235int selectedCount = 0;236boolean endStateFound = false;237boolean wildcardFound = false;238for (int l = 0; l < nexti.length; l++) {239if (!(ns[l] = selected[l])) {240continue;241}242ns[l] = nexti[l] == nc[k] || nexti[l] == END_STATE243&& nc[k] == '.';244endStateFound = endStateFound || nc[k] == END_OF_STRING245&& nexti[l] == END_STATE;246wildcardFound = wildcardFound || nc[k] == '.'247&& nexti[l] == END_STATE;248if (ns[l]) {249selectedCount++;250}251}252nst[k] = (endStateFound ? END_STATE253: wildcardFound ? WILDCARD_STATE : state);254if (selectedCount == 0 || wildcardFound) {255continue;256}257state = compile(step + 1, state, pkgs, ns, charList, stateList);258}259return state;260}261262/**263* Matches 'pkg' against the list of package names compiled in the264* state machine data.265*266* @param pkg The package name to match. Must not end with '.'.267* @return true if the package name matches, false otherwise.268*/269public boolean matches(String pkg) {270int state = 0;271int i;272final int len = pkg.length();273next: for (i = 0; i <= len; i++) {274if (state == WILDCARD_STATE) {275return true; // all characters will match.276}277if (state == END_STATE) {278return i == len;279}280final char[] ch = chars[state];281final int[] st = states[state];282if (i == len) {283// matches only if we have exhausted the string.284return st[0] == END_STATE;285}286if (st[0] == END_STATE && st.length == 1) {287// matches only if we have exhausted the string.288return i == len;289}290final char c = pkg.charAt(i); // look at next char...291for (int j = st[0] == END_STATE ? 1 : 0; j < ch.length; j++) {292final char n = ch[j];293if (c == n) { // found a match294state = st[j]; // get the next state.295continue next; // go to next state296} else if (c < n) {297break; // chars are sorted. we won't find it. no match.298}299}300break; // no match301}302return false;303}304}305306private static final class TestPackageMatcher extends PackageMatcher {307private final List<String> list;308309TestPackageMatcher(String[] packages) {310super(packages);311this.list = Collections.unmodifiableList(Arrays.asList(packages));312}313314@Override315public boolean matches(String pkg) {316final boolean match1 = super.matches(pkg);317boolean match2 = false;318String p2 = pkg + ".";319for (String p : list) {320if (pkg.startsWith(p) || p2.equals(p)) {321match2 = true;322break;323}324}325if (match1 != match2) {326System.err.println("Test Bug: PackageMatcher.matches(\"" +327pkg + "\") returned " + match1);328System.err.println("Package Access List is: " + list);329throw new Error("Test Bug: PackageMatcher.matches(\"" +330pkg + "\") returned " + match1);331}332return match1;333}334}335336private static void smokeTest() {337// these checks should pass.338System.getSecurityManager().checkPackageAccess("com.sun.blah");339System.getSecurityManager().checkPackageAccess("com.sun.jm");340System.getSecurityManager().checkPackageAccess("com.sun.jmxa");341System.getSecurityManager().checkPackageAccess("jmx");342List<String> actual = Arrays.asList(packages);343if (!actual.contains("sun.misc.")) {344throw new Error("package.access does not contain 'sun.misc.'");345}346}347348// This is a sanity test for our own test code.349private static void testTheTest(String[] pkgs, char[][] chars,350int[][] states) {351352PackageMatcher m = new TestPackageMatcher(pkgs);353String unexpected = "";354if (!Arrays.deepEquals(chars, m.chars)) {355System.err.println("Char arrays differ");356if (chars.length != m.chars.length) {357System.err.println("Char array lengths differ: expected="358+ chars.length + " actual=" + m.chars.length);359}360System.err.println(Arrays.deepToString(m.chars).replace((char)0,361'0'));362unexpected = "chars[]";363}364if (!Arrays.deepEquals(states, m.states)) {365System.err.println("State arrays differ");366if (states.length != m.states.length) {367System.err.println("Char array lengths differ: expected="368+ states.length + " actual=" + m.states.length);369}370System.err.println(Arrays.deepToString(m.states));371if (unexpected.length() > 0) {372unexpected = unexpected + " and ";373}374unexpected = unexpected + "states[]";375}376377if (unexpected.length() > 0) {378throw new Error("Unexpected "+unexpected+" in PackageMatcher");379}380381testMatches(m, pkgs);382}383384// This is a sanity test for our own test code.385private static void testTheTest() {386final String[] packages2 = { "sun.", "com.sun.jmx.",387"com.sun.proxy.", "apple." };388389final int END_STATE = PackageMatcher.END_STATE;390final int WILDCARD_STATE = PackageMatcher.WILDCARD_STATE;391392final char[][] chars2 = {393{ 'a', 'c', 's' }, { 'p' }, { 'p' }, { 'l' }, { 'e' }, { 0, '.' },394{ 'o' }, { 'm' }, { '.' }, { 's' }, { 'u' }, { 'n' }, { '.' },395{ 'j', 'p'},396{ 'm' }, { 'x' }, { 0, '.' },397{ 'r' }, { 'o' }, { 'x' }, { 'y' }, { 0, '.' },398{ 'u' }, { 'n' }, { 0, '.' }399};400401final int[][] states2 = {402{ 1, 6, 22 }, { 2 }, { 3 }, { 4 }, { 5 },403{ END_STATE, WILDCARD_STATE },404{ 7 }, { 8 }, { 9 }, { 10 }, { 11 }, { 12 }, { 13 }, { 14, 17 },405{ 15 }, { 16 }, { END_STATE, WILDCARD_STATE },406{ 18 }, { 19 }, { 20 }, { 21 }, { END_STATE, WILDCARD_STATE },407{ 23 }, { 24 }, { END_STATE, WILDCARD_STATE }408};409410testTheTest(packages2, chars2, states2);411412final String[] packages3 = { "sun.", "com.sun.pro.",413"com.sun.proxy.", "apple." };414415final char[][] chars3 = {416{ 'a', 'c', 's' }, { 'p' }, { 'p' }, { 'l' }, { 'e' }, { 0, '.' },417{ 'o' }, { 'm' }, { '.' }, { 's' }, { 'u' }, { 'n' }, { '.' },418{ 'p' }, { 'r' }, { 'o' }, { 0, '.', 'x' },419{ 'y' }, { 0, '.' },420{ 'u' }, { 'n' }, { 0, '.' }421};422423final int[][] states3 = {424{ 1, 6, 19 }, { 2 }, { 3 }, { 4 }, { 5 },425{ END_STATE, WILDCARD_STATE },426{ 7 }, { 8 }, { 9 }, { 10 }, { 11 }, { 12 }, { 13 }, { 14 },427{ 15 }, { 16 }, { END_STATE, WILDCARD_STATE, 17 },428{ 18 }, { END_STATE, WILDCARD_STATE },429{ 20 }, { 21 }, { END_STATE, WILDCARD_STATE }430};431432testTheTest(packages3, chars3, states3);433}434435private static volatile boolean sanityTesting = false;436437public static void main(String[] args) {438System.setSecurityManager(new SecurityManager());439440// Some smoke tests.441smokeTest();442System.out.println("Smoke tests passed.");443444// Test our own pattern matching algorithm. Here we actually test445// the PackageMatcher class from our own test code.446sanityTesting = true;447try {448testTheTest();449System.out.println("Sanity tests passed.");450} finally {451sanityTesting = false;452}453454// Now test the package matching in the security manager.455PackageMatcher matcher = new TestPackageMatcher(packages);456457// These should not match.458for (String pkg : new String[] {"gloups.machin", "su",459"org.jcp.xml.dsig.inter",460"com.sun.jm", "com.sun.jmxa"}) {461testMatch(matcher, pkg, false, true);462}463464// These should match.465for (String pkg : Arrays.asList(466new String[] {"sun.misc.gloups.machin", "sun.misc",467"sun.reflect"})) {468testMatch(matcher, pkg, true, true);469}470471// Derive a list of packages that should match or not match from472// the list in 'packages' - and check that the security manager473// throws the appropriate exception.474testMatches(matcher, packages);475}476477private static void testMatches(PackageMatcher matcher, String[] pkgs) {478Collection<String> pkglist = Arrays.asList(pkgs);479PackageMatcher ref = new TestPackageMatcher(packages);480481for (String pkg : pkgs) {482String candidate = pkg + "toto";483boolean expected = true;484testMatch(matcher, candidate, expected,485ref.matches(candidate) == expected);486}487488for (String pkg : pkgs) {489String candidate = pkg.substring(0, pkg.length() - 1);490boolean expected = pkglist.contains(candidate + ".");491testMatch(matcher, candidate, expected,492ref.matches(candidate) == expected);493}494495for (String pkg : pkgs) {496String candidate = pkg.substring(0, pkg.length() - 2);497boolean expected = pkglist.contains(candidate + ".");498testMatch(matcher, candidate, expected,499ref.matches(candidate) == expected);500}501}502503private static void testMatch(PackageMatcher matcher, String candidate,504boolean expected, boolean testSecurityManager)505{506final boolean m = matcher.matches(candidate);507if (m != expected) {508final String msg = "\"" + candidate + "\": " +509(m ? "matches" : "does not match");510throw new Error("PackageMatcher does not give expected results: "511+ msg);512}513514if (sanityTesting) {515testSecurityManager = false;516}517518if (testSecurityManager) {519System.out.println("Access to " + candidate + " should be " +520(expected ? "rejected" : "granted"));521final String errormsg = "\"" + candidate + "\" : " +522(expected ? "granted" : "not granted");523try {524System.getSecurityManager().checkPackageAccess(candidate);525if (expected) {526System.err.println(errormsg);527throw new Error("Expected exception not thrown: " +528errormsg);529}530} catch (SecurityException x) {531if (!expected) {532System.err.println(errormsg);533throw new Error(errormsg + " - unexpected exception: " +534x, x);535} else {536System.out.println("Got expected exception: " + x);537}538}539}540}541}542543544