1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util;
  27 
  28 import java.io.IOException;
  29 import java.io.ObjectInputStream;
  30 import java.io.ObjectOutputStream;
  31 import java.io.ObjectStreamField;
  32 import java.io.Serializable;
  33 import java.security.*;
  34 import java.util.Enumeration;
  35 import java.util.Hashtable;
  36 import java.util.concurrent.ConcurrentHashMap;
  37 import sun.security.util.SecurityConstants;
  38 
  39 /**
  40  * This class is for property permissions.
  41  *
  42  * <P>
  43  * The name is the name of the property ("java.home",
  44  * "os.name", etc). The naming
  45  * convention follows the  hierarchical property naming convention.
  46  * Also, an asterisk
  47  * may appear at the end of the name, following a ".", or by itself, to
  48  * signify a wildcard match. For example: "java.*" and "*" signify a wildcard
  49  * match, while "*java" and "a*b" do not.
  50  * <P>
  51  * The actions to be granted are passed to the constructor in a string containing
  52  * a list of one or more comma-separated keywords. The possible keywords are
  53  * "read" and "write". Their meaning is defined as follows:
  54  *
  55  * <DL>
  56  *    <DT> read
  57  *    <DD> read permission. Allows <code>System.getProperty</code> to
  58  *         be called.
  59  *    <DT> write
  60  *    <DD> write permission. Allows <code>System.setProperty</code> to
  61  *         be called.
  62  * </DL>
  63  * <P>
  64  * The actions string is converted to lowercase before processing.
  65  * <P>
  66  * Care should be taken before granting code permission to access
  67  * certain system properties.  For example, granting permission to
  68  * access the "java.home" system property gives potentially malevolent
  69  * code sensitive information about the system environment (the Java
  70  * installation directory).  Also, granting permission to access
  71  * the "user.name" and "user.home" system properties gives potentially
  72  * malevolent code sensitive information about the user environment
  73  * (the user's account name and home directory).
  74  *
  75  * @see java.security.BasicPermission
  76  * @see java.security.Permission
  77  * @see java.security.Permissions
  78  * @see java.security.PermissionCollection
  79  * @see java.lang.SecurityManager
  80  *
  81  *
  82  * @author Roland Schemers
  83  * @since 1.2
  84  *
  85  * @serial exclude
  86  */
  87 
  88 public final class PropertyPermission extends BasicPermission {
  89 
  90     /**
  91      * Read action.
  92      */
  93     private static final int READ    = 0x1;
  94 
  95     /**
  96      * Write action.
  97      */
  98     private static final int WRITE   = 0x2;
  99     /**
 100      * All actions (read,write);
 101      */
 102     private static final int ALL     = READ|WRITE;
 103     /**
 104      * No actions.
 105      */
 106     private static final int NONE    = 0x0;
 107 
 108     /**
 109      * The actions mask.
 110      *
 111      */
 112     private transient int mask;
 113 
 114     /**
 115      * The actions string.
 116      *
 117      * @serial
 118      */
 119     private String actions; // Left null as long as possible, then
 120                             // created and re-used in the getAction function.
 121 
 122     /**
 123      * initialize a PropertyPermission object. Common to all constructors.
 124      * Also called during de-serialization.
 125      *
 126      * @param mask the actions mask to use.
 127      *
 128      */
 129     private void init(int mask) {
 130         if ((mask & ALL) != mask)
 131             throw new IllegalArgumentException("invalid actions mask");
 132 
 133         if (mask == NONE)
 134             throw new IllegalArgumentException("invalid actions mask");
 135 
 136         if (getName() == null)
 137             throw new NullPointerException("name can't be null");
 138 
 139         this.mask = mask;
 140     }
 141 
 142     /**
 143      * Creates a new PropertyPermission object with the specified name.
 144      * The name is the name of the system property, and
 145      * <i>actions</i> contains a comma-separated list of the
 146      * desired actions granted on the property. Possible actions are
 147      * "read" and "write".
 148      *
 149      * @param name the name of the PropertyPermission.
 150      * @param actions the actions string.
 151      *
 152      * @throws NullPointerException if <code>name</code> is <code>null</code>.
 153      * @throws IllegalArgumentException if <code>name</code> is empty or if
 154      * <code>actions</code> is invalid.
 155      */
 156     public PropertyPermission(String name, String actions) {
 157         super(name,actions);
 158         init(getMask(actions));
 159     }
 160 
 161     /**
 162      * Creates a PropertyPermission object with the specified name and
 163      * a pre-calculated mask. Avoids the overhead of re-computing the mask.
 164      * Called by PropertyPermissionCollection.
 165      */
 166     PropertyPermission(String name, int mask) {
 167         super(name, getActions(mask));
 168         this.mask = mask;
 169     }
 170 
 171     /**
 172      * Checks if this PropertyPermission object "implies" the specified
 173      * permission.
 174      * <P>
 175      * More specifically, this method returns true if:
 176      * <ul>
 177      * <li> <i>p</i> is an instanceof PropertyPermission,
 178      * <li> <i>p</i>'s actions are a subset of this
 179      * object's actions, and
 180      * <li> <i>p</i>'s name is implied by this object's
 181      *      name. For example, "java.*" implies "java.home".
 182      * </ul>
 183      * @param p the permission to check against.
 184      *
 185      * @return true if the specified permission is implied by this object,
 186      * false if not.
 187      */
 188     @Override
 189     public boolean implies(Permission p) {
 190         if (!(p instanceof PropertyPermission))
 191             return false;
 192 
 193         PropertyPermission that = (PropertyPermission) p;
 194 
 195         // we get the effective mask. i.e., the "and" of this and that.
 196         // They must be equal to that.mask for implies to return true.
 197 
 198         return ((this.mask & that.mask) == that.mask) && super.implies(that);
 199     }
 200 
 201     /**
 202      * Checks two PropertyPermission objects for equality. Checks that <i>obj</i> is
 203      * a PropertyPermission, and has the same name and actions as this object.
 204      *
 205      * @param obj the object we are testing for equality with this object.
 206      * @return true if obj is a PropertyPermission, and has the same name and
 207      * actions as this PropertyPermission object.
 208      */
 209     @Override
 210     public boolean equals(Object obj) {
 211         if (obj == this)
 212             return true;
 213 
 214         if (! (obj instanceof PropertyPermission))
 215             return false;
 216 
 217         PropertyPermission that = (PropertyPermission) obj;
 218 
 219         return (this.mask == that.mask) &&
 220             (this.getName().equals(that.getName()));
 221     }
 222 
 223     /**
 224      * Returns the hash code value for this object.
 225      * The hash code used is the hash code of this permissions name, that is,
 226      * <code>getName().hashCode()</code>, where <code>getName</code> is
 227      * from the Permission superclass.
 228      *
 229      * @return a hash code value for this object.
 230      */
 231     @Override
 232     public int hashCode() {
 233         return this.getName().hashCode();
 234     }
 235 
 236     /**
 237      * Converts an actions String to an actions mask.
 238      *
 239      * @param actions the action string.
 240      * @return the actions mask.
 241      */
 242     private static int getMask(String actions) {
 243 
 244         int mask = NONE;
 245 
 246         if (actions == null) {
 247             return mask;
 248         }
 249 
 250         // Use object identity comparison against known-interned strings for
 251         // performance benefit (these values are used heavily within the JDK).
 252         if (actions == SecurityConstants.PROPERTY_READ_ACTION) {
 253             return READ;
 254         } if (actions == SecurityConstants.PROPERTY_WRITE_ACTION) {
 255             return WRITE;
 256         } else if (actions == SecurityConstants.PROPERTY_RW_ACTION) {
 257             return READ|WRITE;
 258         }
 259 
 260         char[] a = actions.toCharArray();
 261 
 262         int i = a.length - 1;
 263         if (i < 0)
 264             return mask;
 265 
 266         while (i != -1) {
 267             char c;
 268 
 269             // skip whitespace
 270             while ((i!=-1) && ((c = a[i]) == ' ' ||
 271                                c == '\r' ||
 272                                c == '\n' ||
 273                                c == '\f' ||
 274                                c == '\t'))
 275                 i--;
 276 
 277             // check for the known strings
 278             int matchlen;
 279 
 280             if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') &&
 281                           (a[i-2] == 'e' || a[i-2] == 'E') &&
 282                           (a[i-1] == 'a' || a[i-1] == 'A') &&
 283                           (a[i] == 'd' || a[i] == 'D'))
 284             {
 285                 matchlen = 4;
 286                 mask |= READ;
 287 
 288             } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') &&
 289                                  (a[i-3] == 'r' || a[i-3] == 'R') &&
 290                                  (a[i-2] == 'i' || a[i-2] == 'I') &&
 291                                  (a[i-1] == 't' || a[i-1] == 'T') &&
 292                                  (a[i] == 'e' || a[i] == 'E'))
 293             {
 294                 matchlen = 5;
 295                 mask |= WRITE;
 296 
 297             } else {
 298                 // parse error
 299                 throw new IllegalArgumentException(
 300                         "invalid permission: " + actions);
 301             }
 302 
 303             // make sure we didn't just match the tail of a word
 304             // like "ackbarfaccept".  Also, skip to the comma.
 305             boolean seencomma = false;
 306             while (i >= matchlen && !seencomma) {
 307                 switch(a[i-matchlen]) {
 308                 case ',':
 309                     seencomma = true;
 310                     break;
 311                 case ' ': case '\r': case '\n':
 312                 case '\f': case '\t':
 313                     break;
 314                 default:
 315                     throw new IllegalArgumentException(
 316                             "invalid permission: " + actions);
 317                 }
 318                 i--;
 319             }
 320 
 321             // point i at the location of the comma minus one (or -1).
 322             i -= matchlen;
 323         }
 324 
 325         return mask;
 326     }
 327 
 328 
 329     /**
 330      * Return the canonical string representation of the actions.
 331      * Always returns present actions in the following order:
 332      * read, write.
 333      *
 334      * @return the canonical string representation of the actions.
 335      */
 336     static String getActions(int mask) {
 337         switch (mask & (READ|WRITE)) {
 338             case READ:
 339                 return SecurityConstants.PROPERTY_READ_ACTION;
 340             case WRITE:
 341                 return SecurityConstants.PROPERTY_WRITE_ACTION;
 342             case READ|WRITE:
 343                 return SecurityConstants.PROPERTY_RW_ACTION;
 344             default:
 345                 return "";
 346         }
 347     }
 348 
 349     /**
 350      * Returns the "canonical string representation" of the actions.
 351      * That is, this method always returns present actions in the following order:
 352      * read, write. For example, if this PropertyPermission object
 353      * allows both write and read actions, a call to <code>getActions</code>
 354      * will return the string "read,write".
 355      *
 356      * @return the canonical string representation of the actions.
 357      */
 358     @Override
 359     public String getActions() {
 360         if (actions == null)
 361             actions = getActions(this.mask);
 362 
 363         return actions;
 364     }
 365 
 366     /**
 367      * Return the current action mask.
 368      * Used by the PropertyPermissionCollection
 369      *
 370      * @return the actions mask.
 371      */
 372     int getMask() {
 373         return mask;
 374     }
 375 
 376     /**
 377      * Returns a new PermissionCollection object for storing
 378      * PropertyPermission objects.
 379      *
 380      * @return a new PermissionCollection object suitable for storing
 381      * PropertyPermissions.
 382      */
 383     @Override
 384     public PermissionCollection newPermissionCollection() {
 385         return new PropertyPermissionCollection();
 386     }
 387 
 388     @java.io.Serial
 389     private static final long serialVersionUID = 885438825399942851L;
 390 
 391     /**
 392      * WriteObject is called to save the state of the PropertyPermission
 393      * to a stream. The actions are serialized, and the superclass
 394      * takes care of the name.
 395      */
 396     @java.io.Serial
 397     private synchronized void writeObject(java.io.ObjectOutputStream s)
 398         throws IOException
 399     {
 400         // Write out the actions. The superclass takes care of the name
 401         // call getActions to make sure actions field is initialized
 402         if (actions == null)
 403             getActions();
 404         s.defaultWriteObject();
 405     }
 406 
 407     /**
 408      * readObject is called to restore the state of the PropertyPermission from
 409      * a stream.
 410      */
 411     @java.io.Serial
 412     private synchronized void readObject(java.io.ObjectInputStream s)
 413          throws IOException, ClassNotFoundException
 414     {
 415         // Read in the action, then initialize the rest
 416         s.defaultReadObject();
 417         init(getMask(actions));
 418     }
 419 }
 420 
 421 /**
 422  * A PropertyPermissionCollection stores a set of PropertyPermission
 423  * permissions.
 424  *
 425  * @see java.security.Permission
 426  * @see java.security.Permissions
 427  * @see java.security.PermissionCollection
 428  *
 429  *
 430  * @author Roland Schemers
 431  *
 432  * @serial include
 433  */
 434 final class PropertyPermissionCollection extends PermissionCollection
 435     implements Serializable
 436 {
 437 
 438     /**
 439      * Key is property name; value is PropertyPermission.
 440      * Not serialized; see serialization section at end of class.
 441      */
 442     private transient ConcurrentHashMap<String, PropertyPermission> perms;
 443 
 444     /**
 445      * Boolean saying if "*" is in the collection.
 446      *
 447      * @see #serialPersistentFields
 448      */
 449     // No sync access; OK for this to be stale.
 450     private boolean all_allowed;
 451 
 452     /**
 453      * Create an empty PropertyPermissionCollection object.
 454      */
 455     public PropertyPermissionCollection() {
 456         perms = new ConcurrentHashMap<>(32);     // Capacity for default policy
 457         all_allowed = false;
 458     }
 459 
 460     /**
 461      * Adds a permission to the PropertyPermissions. The key for the hash is
 462      * the name.
 463      *
 464      * @param permission the Permission object to add.
 465      *
 466      * @exception IllegalArgumentException - if the permission is not a
 467      *                                       PropertyPermission
 468      *
 469      * @exception SecurityException - if this PropertyPermissionCollection
 470      *                                object has been marked readonly
 471      */
 472     @Override
 473     public void add(Permission permission) {
 474         if (! (permission instanceof PropertyPermission))
 475             throw new IllegalArgumentException("invalid permission: "+
 476                                                permission);
 477         if (isReadOnly())
 478             throw new SecurityException(
 479                 "attempt to add a Permission to a readonly PermissionCollection");
 480 
 481         PropertyPermission pp = (PropertyPermission) permission;
 482         String propName = pp.getName();
 483 
 484         // Add permission to map if it is absent, or replace with new
 485         // permission if applicable. NOTE: cannot use lambda for
 486         // remappingFunction parameter until JDK-8076596 is fixed.
 487         perms.merge(propName, pp,
 488             new java.util.function.BiFunction<>() {
 489                 @Override
 490                 public PropertyPermission apply(PropertyPermission existingVal,
 491                                                 PropertyPermission newVal) {
 492 
 493                     int oldMask = existingVal.getMask();
 494                     int newMask = newVal.getMask();
 495                     if (oldMask != newMask) {
 496                         int effective = oldMask | newMask;
 497                         if (effective == newMask) {
 498                             return newVal;
 499                         }
 500                         if (effective != oldMask) {
 501                             return new PropertyPermission(propName, effective);
 502                         }
 503                     }
 504                     return existingVal;
 505                 }
 506             }
 507         );
 508 
 509         if (!all_allowed) {
 510             if (propName.equals("*"))
 511                 all_allowed = true;
 512         }
 513     }
 514 
 515     /**
 516      * Check and see if this set of permissions implies the permissions
 517      * expressed in "permission".
 518      *
 519      * @param permission the Permission object to compare
 520      *
 521      * @return true if "permission" is a proper subset of a permission in
 522      * the set, false if not.
 523      */
 524     @Override
 525     public boolean implies(Permission permission) {
 526         if (! (permission instanceof PropertyPermission))
 527             return false;
 528 
 529         PropertyPermission pp = (PropertyPermission) permission;
 530         PropertyPermission x;
 531 
 532         int desired = pp.getMask();
 533         int effective = 0;
 534 
 535         // short circuit if the "*" Permission was added
 536         if (all_allowed) {
 537             x = perms.get("*");
 538             if (x != null) {
 539                 effective |= x.getMask();
 540                 if ((effective & desired) == desired)
 541                     return true;
 542             }
 543         }
 544 
 545         // strategy:
 546         // Check for full match first. Then work our way up the
 547         // name looking for matches on a.b.*
 548 
 549         String name = pp.getName();
 550         //System.out.println("check "+name);
 551 
 552         x = perms.get(name);
 553 
 554         if (x != null) {
 555             // we have a direct hit!
 556             effective |= x.getMask();
 557             if ((effective & desired) == desired)
 558                 return true;
 559         }
 560 
 561         // work our way up the tree...
 562         int last, offset;
 563 
 564         offset = name.length()-1;
 565 
 566         while ((last = name.lastIndexOf('.', offset)) != -1) {
 567 
 568             name = name.substring(0, last+1) + "*";
 569             //System.out.println("check "+name);
 570             x = perms.get(name);
 571 
 572             if (x != null) {
 573                 effective |= x.getMask();
 574                 if ((effective & desired) == desired)
 575                     return true;
 576             }
 577             offset = last -1;
 578         }
 579 
 580         // we don't have to check for "*" as it was already checked
 581         // at the top (all_allowed), so we just return false
 582         return false;
 583     }
 584 
 585     /**
 586      * Returns an enumeration of all the PropertyPermission objects in the
 587      * container.
 588      *
 589      * @return an enumeration of all the PropertyPermission objects.
 590      */
 591     @Override
 592     @SuppressWarnings("unchecked")
 593     public Enumeration<Permission> elements() {
 594         /**
 595          * Casting to rawtype since Enumeration<PropertyPermission>
 596          * cannot be directly cast to Enumeration<Permission>
 597          */
 598         return (Enumeration)perms.elements();
 599     }
 600 
 601     @java.io.Serial
 602     private static final long serialVersionUID = 7015263904581634791L;
 603 
 604     // Need to maintain serialization interoperability with earlier releases,
 605     // which had the serializable field:
 606     //
 607     // Table of permissions.
 608     //
 609     // @serial
 610     //
 611     // private Hashtable permissions;
 612     /**
 613      * @serialField permissions java.util.Hashtable
 614      *     A table of the PropertyPermissions.
 615      * @serialField all_allowed boolean
 616      *     boolean saying if "*" is in the collection.
 617      */
 618     private static final ObjectStreamField[] serialPersistentFields = {
 619         new ObjectStreamField("permissions", Hashtable.class),
 620         new ObjectStreamField("all_allowed", Boolean.TYPE),
 621     };
 622 
 623     /**
 624      * @serialData Default fields.
 625      */
 626     /*
 627      * Writes the contents of the perms field out as a Hashtable for
 628      * serialization compatibility with earlier releases. all_allowed
 629      * unchanged.
 630      */
 631     @java.io.Serial
 632     private void writeObject(ObjectOutputStream out) throws IOException {
 633         // Don't call out.defaultWriteObject()
 634 
 635         // Copy perms into a Hashtable
 636         Hashtable<String, Permission> permissions =
 637             new Hashtable<>(perms.size()*2);
 638         permissions.putAll(perms);
 639 
 640         // Write out serializable fields
 641         ObjectOutputStream.PutField pfields = out.putFields();
 642         pfields.put("all_allowed", all_allowed);
 643         pfields.put("permissions", permissions);
 644         out.writeFields();
 645     }
 646 
 647     /*
 648      * Reads in a Hashtable of PropertyPermissions and saves them in the
 649      * perms field. Reads in all_allowed.
 650      */
 651     @java.io.Serial
 652     private void readObject(ObjectInputStream in)
 653         throws IOException, ClassNotFoundException
 654     {
 655         // Don't call defaultReadObject()
 656 
 657         // Read in serialized fields
 658         ObjectInputStream.GetField gfields = in.readFields();
 659 
 660         // Get all_allowed
 661         all_allowed = gfields.get("all_allowed", false);
 662 
 663         // Get permissions
 664         @SuppressWarnings("unchecked")
 665         Hashtable<String, PropertyPermission> permissions =
 666             (Hashtable<String, PropertyPermission>)gfields.get("permissions", null);
 667         perms = new ConcurrentHashMap<>(permissions.size()*2);
 668         perms.putAll(permissions);
 669     }
 670 }