1 /* 2 * Copyright (c) 1996, 2018, 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 sun.rmi.registry; 27 28 import java.io.ObjectInputFilter; 29 import java.nio.file.Path; 30 import java.nio.file.Paths; 31 import java.security.PrivilegedAction; 32 import java.security.Security; 33 import java.util.ArrayList; 34 import java.util.Enumeration; 35 import java.util.Hashtable; 36 import java.util.List; 37 import java.util.MissingResourceException; 38 import java.util.ResourceBundle; 39 import java.io.File; 40 import java.io.FilePermission; 41 import java.io.IOException; 42 import java.net.*; 43 import java.rmi.*; 44 import java.rmi.server.ObjID; 45 import java.rmi.server.ServerNotActiveException; 46 import java.rmi.registry.Registry; 47 import java.rmi.server.RMIClientSocketFactory; 48 import java.rmi.server.RMIServerSocketFactory; 49 import java.security.AccessControlContext; 50 import java.security.AccessController; 51 import java.security.CodeSource; 52 import java.security.Policy; 53 import java.security.PrivilegedActionException; 54 import java.security.PrivilegedExceptionAction; 55 import java.security.PermissionCollection; 56 import java.security.Permissions; 57 import java.security.ProtectionDomain; 58 import java.text.MessageFormat; 59 60 import jdk.internal.access.SharedSecrets; 61 import sun.rmi.runtime.Log; 62 import sun.rmi.server.UnicastRef; 63 import sun.rmi.server.UnicastServerRef; 64 import sun.rmi.server.UnicastServerRef2; 65 import sun.rmi.transport.LiveRef; 66 67 /** 68 * A "registry" exists on every node that allows RMI connections to 69 * servers on that node. The registry on a particular node contains a 70 * transient database that maps names to remote objects. When the 71 * node boots, the registry database is empty. The names stored in the 72 * registry are pure and are not parsed. A service storing itself in 73 * the registry may want to prefix its name of the service by a package 74 * name (although not required), to reduce name collisions in the 75 * registry. 76 * 77 * The LocateRegistry class is used to obtain registry for different hosts. 78 * <p> 79 * The default RegistryImpl exported restricts access to clients on the local host 80 * for the methods {@link #bind}, {@link #rebind}, {@link #unbind} by checking 81 * the client host in the skeleton. 82 * 83 * @see java.rmi.registry.LocateRegistry 84 */ 85 public class RegistryImpl extends java.rmi.server.RemoteServer 86 implements Registry 87 { 88 89 /* indicate compatibility with JDK 1.1.x version of class */ 90 private static final long serialVersionUID = 4666870661827494597L; 91 private Hashtable<String, Remote> bindings 92 = new Hashtable<>(101); 93 private static Hashtable<InetAddress, InetAddress> allowedAccessCache 94 = new Hashtable<>(3); 95 private static RegistryImpl registry; 96 private static ObjID id = new ObjID(ObjID.REGISTRY_ID); 97 98 private static ResourceBundle resources = null; 99 100 /** 101 * Property name of the RMI Registry serial filter to augment 102 * the built-in list of allowed types. 103 * Setting the property in the {@code conf/security/java.security} file 104 * will enable the augmented filter. 105 */ 106 private static final String REGISTRY_FILTER_PROPNAME = "sun.rmi.registry.registryFilter"; 107 108 /** Registry max depth of remote invocations. **/ 109 private static final int REGISTRY_MAX_DEPTH = 20; 110 111 /** Registry maximum array size in remote invocations. **/ 112 private static final int REGISTRY_MAX_ARRAY_SIZE = 1_000_000; 113 114 /** 115 * The registryFilter created from the value of the {@code "sun.rmi.registry.registryFilter"} 116 * property. 117 */ 118 private static final ObjectInputFilter registryFilter = 119 AccessController.doPrivileged((PrivilegedAction<ObjectInputFilter>)RegistryImpl::initRegistryFilter); 120 121 /** 122 * Initialize the registryFilter from the security properties or system property; if any 123 * @return an ObjectInputFilter, or null 124 */ 125 @SuppressWarnings("deprecation") 126 private static ObjectInputFilter initRegistryFilter() { 127 ObjectInputFilter filter = null; 128 String props = System.getProperty(REGISTRY_FILTER_PROPNAME); 129 if (props == null) { 130 props = Security.getProperty(REGISTRY_FILTER_PROPNAME); 131 } 132 if (props != null) { 133 filter = SharedSecrets.getJavaObjectInputFilterAccess().createFilter2(props); 134 Log regLog = Log.getLog("sun.rmi.registry", "registry", -1); 135 if (regLog.isLoggable(Log.BRIEF)) { 136 regLog.log(Log.BRIEF, "registryFilter = " + filter); 137 } 138 } 139 return filter; 140 } 141 142 /** 143 * Construct a new RegistryImpl on the specified port with the 144 * given custom socket factory pair. 145 */ 146 public RegistryImpl(int port, 147 RMIClientSocketFactory csf, 148 RMIServerSocketFactory ssf) 149 throws RemoteException 150 { 151 this(port, csf, ssf, RegistryImpl::registryFilter); 152 } 153 154 155 /** 156 * Construct a new RegistryImpl on the specified port with the 157 * given custom socket factory pair and ObjectInputFilter. 158 */ 159 public RegistryImpl(int port, 160 RMIClientSocketFactory csf, 161 RMIServerSocketFactory ssf, 162 ObjectInputFilter serialFilter) 163 throws RemoteException 164 { 165 if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) { 166 // grant permission for default port only. 167 try { 168 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { 169 public Void run() throws RemoteException { 170 LiveRef lref = new LiveRef(id, port, csf, ssf); 171 setup(new UnicastServerRef2(lref, serialFilter)); 172 return null; 173 } 174 }, null, new SocketPermission("localhost:"+port, "listen,accept")); 175 } catch (PrivilegedActionException pae) { 176 throw (RemoteException)pae.getException(); 177 } 178 } else { 179 LiveRef lref = new LiveRef(id, port, csf, ssf); 180 setup(new UnicastServerRef2(lref, serialFilter)); 181 } 182 } 183 184 /** 185 * Construct a new RegistryImpl on the specified port. 186 */ 187 public RegistryImpl(int port) 188 throws RemoteException 189 { 190 if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) { 191 // grant permission for default port only. 192 try { 193 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { 194 public Void run() throws RemoteException { 195 LiveRef lref = new LiveRef(id, port); 196 setup(new UnicastServerRef(lref, RegistryImpl::registryFilter)); 197 return null; 198 } 199 }, null, new SocketPermission("localhost:"+port, "listen,accept")); 200 } catch (PrivilegedActionException pae) { 201 throw (RemoteException)pae.getException(); 202 } 203 } else { 204 LiveRef lref = new LiveRef(id, port); 205 setup(new UnicastServerRef(lref, RegistryImpl::registryFilter)); 206 } 207 } 208 209 /* 210 * Create the export the object using the parameter 211 * <code>uref</code> 212 */ 213 private void setup(UnicastServerRef uref) 214 throws RemoteException 215 { 216 /* Server ref must be created and assigned before remote 217 * object 'this' can be exported. 218 */ 219 ref = uref; 220 uref.exportObject(this, null, true); 221 } 222 223 /** 224 * Returns the remote object for specified name in the registry. 225 * @exception RemoteException If remote operation failed. 226 * @exception NotBoundException If name is not currently bound. 227 */ 228 public Remote lookup(String name) 229 throws RemoteException, NotBoundException 230 { 231 synchronized (bindings) { 232 Remote obj = bindings.get(name); 233 if (obj == null) 234 throw new NotBoundException(name); 235 return obj; 236 } 237 } 238 239 /** 240 * Binds the name to the specified remote object. 241 * @exception RemoteException If remote operation failed. 242 * @exception AlreadyBoundException If name is already bound. 243 */ 244 public void bind(String name, Remote obj) 245 throws RemoteException, AlreadyBoundException, AccessException 246 { 247 // The access check preventing remote access is done in the skeleton 248 // and is not applicable to local access. 249 synchronized (bindings) { 250 Remote curr = bindings.get(name); 251 if (curr != null) 252 throw new AlreadyBoundException(name); 253 bindings.put(name, obj); 254 } 255 } 256 257 /** 258 * Unbind the name. 259 * @exception RemoteException If remote operation failed. 260 * @exception NotBoundException If name is not currently bound. 261 */ 262 public void unbind(String name) 263 throws RemoteException, NotBoundException, AccessException 264 { 265 // The access check preventing remote access is done in the skeleton 266 // and is not applicable to local access. 267 synchronized (bindings) { 268 Remote obj = bindings.get(name); 269 if (obj == null) 270 throw new NotBoundException(name); 271 bindings.remove(name); 272 } 273 } 274 275 /** 276 * Rebind the name to a new object, replaces any existing binding. 277 * @exception RemoteException If remote operation failed. 278 */ 279 public void rebind(String name, Remote obj) 280 throws RemoteException, AccessException 281 { 282 // The access check preventing remote access is done in the skeleton 283 // and is not applicable to local access. 284 bindings.put(name, obj); 285 } 286 287 /** 288 * Returns an enumeration of the names in the registry. 289 * @exception RemoteException If remote operation failed. 290 */ 291 public String[] list() 292 throws RemoteException 293 { 294 String[] names; 295 synchronized (bindings) { 296 int i = bindings.size(); 297 names = new String[i]; 298 Enumeration<String> enum_ = bindings.keys(); 299 while ((--i) >= 0) 300 names[i] = enum_.nextElement(); 301 } 302 return names; 303 } 304 305 /** 306 * Check that the caller has access to perform indicated operation. 307 * The client must be on same the same host as this server. 308 */ 309 public static void checkAccess(String op) throws AccessException { 310 311 try { 312 /* 313 * Get client host that this registry operation was made from. 314 */ 315 final String clientHostName = getClientHost(); 316 InetAddress clientHost; 317 318 try { 319 clientHost = java.security.AccessController.doPrivileged( 320 new java.security.PrivilegedExceptionAction<InetAddress>() { 321 public InetAddress run() 322 throws java.net.UnknownHostException 323 { 324 return InetAddress.getByName(clientHostName); 325 } 326 }); 327 } catch (PrivilegedActionException pae) { 328 throw (java.net.UnknownHostException) pae.getException(); 329 } 330 331 // if client not yet seen, make sure client allowed access 332 if (allowedAccessCache.get(clientHost) == null) { 333 334 if (clientHost.isAnyLocalAddress()) { 335 throw new AccessException( 336 op + " disallowed; origin unknown"); 337 } 338 339 try { 340 final InetAddress finalClientHost = clientHost; 341 342 java.security.AccessController.doPrivileged( 343 new java.security.PrivilegedExceptionAction<Void>() { 344 public Void run() throws java.io.IOException { 345 /* 346 * if a ServerSocket can be bound to the client's 347 * address then that address must be local 348 */ 349 (new ServerSocket(0, 10, finalClientHost)).close(); 350 allowedAccessCache.put(finalClientHost, 351 finalClientHost); 352 return null; 353 } 354 }); 355 } catch (PrivilegedActionException pae) { 356 // must have been an IOException 357 358 throw new AccessException( 359 op + " disallowed; origin " + 360 clientHost + " is non-local host"); 361 } 362 } 363 } catch (ServerNotActiveException ex) { 364 /* 365 * Local call from this VM: allow access. 366 */ 367 } catch (java.net.UnknownHostException ex) { 368 throw new AccessException(op + " disallowed; origin is unknown host"); 369 } 370 } 371 372 public static ObjID getID() { 373 return id; 374 } 375 376 /** 377 * Retrieves text resources from the locale-specific properties file. 378 */ 379 private static String getTextResource(String key) { 380 if (resources == null) { 381 try { 382 resources = ResourceBundle.getBundle( 383 "sun.rmi.registry.resources.rmiregistry"); 384 } catch (MissingResourceException mre) { 385 } 386 if (resources == null) { 387 // throwing an Error is a bit extreme, methinks 388 return ("[missing resource file: " + key + "]"); 389 } 390 } 391 392 String val = null; 393 try { 394 val = resources.getString(key); 395 } catch (MissingResourceException mre) { 396 } 397 398 if (val == null) { 399 return ("[missing resource: " + key + "]"); 400 } else { 401 return (val); 402 } 403 } 404 405 /** 406 * Convert class path specification into an array of file URLs. 407 * 408 * The path of the file is converted to a URI then into URL 409 * form so that reserved characters can safely appear in the path. 410 */ 411 private static URL[] pathToURLs(String path) { 412 List<URL> paths = new ArrayList<>(); 413 for (String entry: path.split(File.pathSeparator)) { 414 Path p = Paths.get(entry); 415 try { 416 p = p.toRealPath(); 417 } catch (IOException x) { 418 p = p.toAbsolutePath(); 419 } 420 try { 421 paths.add(p.toUri().toURL()); 422 } catch (MalformedURLException e) { 423 //ignore / skip entry 424 } 425 } 426 return paths.toArray(new URL[0]); 427 } 428 429 /** 430 * ObjectInputFilter to filter Registry input objects. 431 * The list of acceptable classes is limited to classes normally 432 * stored in a registry. 433 * 434 * @param filterInfo access to the class, array length, etc. 435 * @return {@link ObjectInputFilter.Status#ALLOWED} if allowed, 436 * {@link ObjectInputFilter.Status#REJECTED} if rejected, 437 * otherwise {@link ObjectInputFilter.Status#UNDECIDED} 438 */ 439 private static ObjectInputFilter.Status registryFilter(ObjectInputFilter.FilterInfo filterInfo) { 440 if (registryFilter != null) { 441 ObjectInputFilter.Status status = registryFilter.checkInput(filterInfo); 442 if (status != ObjectInputFilter.Status.UNDECIDED) { 443 // The Registry filter can override the built-in white-list 444 return status; 445 } 446 } 447 448 if (filterInfo.depth() > REGISTRY_MAX_DEPTH) { 449 return ObjectInputFilter.Status.REJECTED; 450 } 451 Class<?> clazz = filterInfo.serialClass(); 452 if (clazz != null) { 453 if (clazz.isArray()) { 454 // Arrays are REJECTED only if they exceed the limit 455 return (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > REGISTRY_MAX_ARRAY_SIZE) 456 ? ObjectInputFilter.Status.REJECTED 457 : ObjectInputFilter.Status.UNDECIDED; 458 } 459 if (String.class == clazz 460 || java.lang.Number.class.isAssignableFrom(clazz) 461 || Remote.class.isAssignableFrom(clazz) 462 || java.lang.reflect.Proxy.class.isAssignableFrom(clazz) 463 || UnicastRef.class.isAssignableFrom(clazz) 464 || RMIClientSocketFactory.class.isAssignableFrom(clazz) 465 || RMIServerSocketFactory.class.isAssignableFrom(clazz) 466 || java.rmi.activation.ActivationID.class.isAssignableFrom(clazz) 467 || java.rmi.server.UID.class.isAssignableFrom(clazz)) { 468 return ObjectInputFilter.Status.ALLOWED; 469 } else { 470 return ObjectInputFilter.Status.REJECTED; 471 } 472 } 473 return ObjectInputFilter.Status.UNDECIDED; 474 } 475 476 /** 477 * Return a new RegistryImpl on the requested port and export it to serve 478 * registry requests. A classloader is initialized from the system property 479 * "env.class.path" and a security manager is set unless one is already set. 480 * <p> 481 * The returned Registry is fully functional within the current process and 482 * is usable for internal and testing purposes. 483 * 484 * @param regPort port on which the rmiregistry accepts requests; 485 * if 0, an implementation specific port is assigned 486 * @return a RegistryImpl instance 487 * @exception RemoteException If remote operation failed. 488 * @since 9 489 */ 490 public static RegistryImpl createRegistry(int regPort) throws RemoteException { 491 // Create and install the security manager if one is not installed 492 // already. 493 if (System.getSecurityManager() == null) { 494 System.setSecurityManager(new SecurityManager()); 495 } 496 497 /* 498 * Fix bugid 4147561: When JDK tools are executed, the value of 499 * the CLASSPATH environment variable for the shell in which they 500 * were invoked is no longer incorporated into the application 501 * class path; CLASSPATH's only effect is to be the value of the 502 * system property "env.class.path". To preserve the previous 503 * (JDK1.1 and JDK1.2beta3) behavior of this tool, however, its 504 * CLASSPATH should still be considered when resolving classes 505 * being unmarshalled. To effect this old behavior, a class 506 * loader that loads from the file path specified in the 507 * "env.class.path" property is created and set to be the context 508 * class loader before the remote object is exported. 509 */ 510 String envcp = System.getProperty("env.class.path"); 511 if (envcp == null) { 512 envcp = "."; // preserve old default behavior 513 } 514 URL[] urls = pathToURLs(envcp); 515 ClassLoader cl = new URLClassLoader(urls); 516 517 /* 518 * Fix bugid 4242317: Classes defined by this class loader should 519 * be annotated with the value of the "java.rmi.server.codebase" 520 * property, not the "file:" URLs for the CLASSPATH elements. 521 */ 522 sun.rmi.server.LoaderHandler.registerCodebaseLoader(cl); 523 524 Thread.currentThread().setContextClassLoader(cl); 525 526 RegistryImpl registryImpl = null; 527 try { 528 registryImpl = AccessController.doPrivileged( 529 new PrivilegedExceptionAction<RegistryImpl>() { 530 public RegistryImpl run() throws RemoteException { 531 return new RegistryImpl(regPort); 532 } 533 }, getAccessControlContext(regPort)); 534 } catch (PrivilegedActionException ex) { 535 throw (RemoteException) ex.getException(); 536 } 537 538 return registryImpl; 539 } 540 541 /** 542 * Main program to start a registry. <br> 543 * The port number can be specified on the command line. 544 */ 545 public static void main(String args[]) 546 { 547 try { 548 final int regPort = (args.length >= 1) ? Integer.parseInt(args[0]) 549 : Registry.REGISTRY_PORT; 550 551 registry = createRegistry(regPort); 552 553 // prevent registry from exiting 554 while (true) { 555 try { 556 Thread.sleep(Long.MAX_VALUE); 557 } catch (InterruptedException e) { 558 } 559 } 560 } catch (NumberFormatException e) { 561 System.err.println(MessageFormat.format( 562 getTextResource("rmiregistry.port.badnumber"), 563 args[0] )); 564 System.err.println(MessageFormat.format( 565 getTextResource("rmiregistry.usage"), 566 "rmiregistry" )); 567 } catch (Exception e) { 568 e.printStackTrace(); 569 } 570 System.exit(1); 571 } 572 573 /** 574 * Generates an AccessControlContext with minimal permissions. 575 * The approach used here is taken from the similar method 576 * getAccessControlContext() in the sun.applet.AppletPanel class. 577 */ 578 private static AccessControlContext getAccessControlContext(int port) { 579 // begin with permissions granted to all code in current policy 580 PermissionCollection perms = AccessController.doPrivileged( 581 new java.security.PrivilegedAction<PermissionCollection>() { 582 public PermissionCollection run() { 583 CodeSource codesource = new CodeSource(null, 584 (java.security.cert.Certificate[]) null); 585 Policy p = java.security.Policy.getPolicy(); 586 if (p != null) { 587 return p.getPermissions(codesource); 588 } else { 589 return new Permissions(); 590 } 591 } 592 }); 593 594 /* 595 * Anyone can connect to the registry and the registry can connect 596 * to and possibly download stubs from anywhere. Downloaded stubs and 597 * related classes themselves are more tightly limited by RMI. 598 */ 599 perms.add(new SocketPermission("*", "connect,accept")); 600 perms.add(new SocketPermission("localhost:"+port, "listen,accept")); 601 602 perms.add(new RuntimePermission("accessClassInPackage.sun.jvmstat.*")); 603 perms.add(new RuntimePermission("accessClassInPackage.sun.jvm.hotspot.*")); 604 605 perms.add(new FilePermission("<<ALL FILES>>", "read")); 606 607 /* 608 * Create an AccessControlContext that consists of a single 609 * protection domain with only the permissions calculated above. 610 */ 611 ProtectionDomain pd = new ProtectionDomain( 612 new CodeSource(null, 613 (java.security.cert.Certificate[]) null), perms); 614 return new AccessControlContext(new ProtectionDomain[] { pd }); 615 } 616 }