1/* 2 * Copyright (c) 1996, 2016, 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 */ 25package sun.rmi.transport; 26 27import java.io.ObjectInputFilter; 28import java.net.SocketPermission; 29import java.rmi.Remote; 30import java.rmi.RemoteException; 31import java.rmi.dgc.DGC; 32import java.rmi.dgc.Lease; 33import java.rmi.dgc.VMID; 34import java.rmi.server.LogStream; 35import java.rmi.server.ObjID; 36import java.rmi.server.RemoteServer; 37import java.rmi.server.ServerNotActiveException; 38import java.rmi.server.UID; 39import java.security.AccessControlContext; 40import java.security.AccessController; 41import java.security.Permissions; 42import java.security.PrivilegedAction; 43import java.security.ProtectionDomain; 44import java.security.Security; 45import java.util.ArrayList; 46import java.util.HashSet; 47import java.util.HashMap; 48import java.util.Iterator; 49import java.util.List; 50import java.util.Map; 51import java.util.Set; 52import java.util.concurrent.Future; 53import java.util.concurrent.ScheduledExecutorService; 54import java.util.concurrent.TimeUnit; 55import sun.rmi.runtime.Log; 56import sun.rmi.runtime.RuntimeUtil; 57import sun.rmi.server.UnicastRef; 58import sun.rmi.server.UnicastServerRef; 59import sun.rmi.server.Util; 60 61/** 62 * This class implements the guts of the server-side distributed GC 63 * algorithm 64 * 65 * @author Ann Wollrath 66 */ 67@SuppressWarnings("deprecation") 68final class DGCImpl implements DGC { 69 70 /* dgc system log */ 71 static final Log dgcLog = Log.getLog("sun.rmi.dgc", "dgc", 72 LogStream.parseLevel(AccessController.doPrivileged( 73 (PrivilegedAction<String>) () -> System.getProperty("sun.rmi.dgc.logLevel")))); 74 75 /** lease duration to grant to clients */ 76 private static final long leaseValue = // default 10 minutes 77 AccessController.doPrivileged( 78 (PrivilegedAction<Long>) () -> Long.getLong("java.rmi.dgc.leaseValue", 600000)); 79 80 /** lease check interval; default is half of lease grant duration */ 81 private static final long leaseCheckInterval = 82 AccessController.doPrivileged( 83 (PrivilegedAction<Long>) () -> Long.getLong("sun.rmi.dgc.checkInterval", leaseValue / 2)); 84 85 /** thread pool for scheduling delayed tasks */ 86 private static final ScheduledExecutorService scheduler = 87 AccessController.doPrivileged( 88 new RuntimeUtil.GetInstanceAction()).getScheduler(); 89 90 /** remote implementation of DGC interface for this VM */ 91 private static DGCImpl dgc; 92 /** table that maps VMID to LeaseInfo */ 93 private Map<VMID,LeaseInfo> leaseTable = new HashMap<>(); 94 /** checks for lease expiration */ 95 private Future<?> checker = null; 96 97 /** 98 * Return the remote implementation of the DGC interface for 99 * this VM. 100 */ 101 static DGCImpl getDGCImpl() { 102 return dgc; 103 } 104 105 /** 106 * Property name of the DGC serial filter to augment 107 * the built-in list of allowed types. 108 * Setting the property in the {@code conf/security/java.security} file 109 * or system property will enable the augmented filter. 110 */ 111 private static final String DGC_FILTER_PROPNAME = "sun.rmi.transport.dgcFilter"; 112 113 /** Registry max depth of remote invocations. **/ 114 private static int DGC_MAX_DEPTH = 5; 115 116 /** Registry maximum array size in remote invocations. **/ 117 private static int DGC_MAX_ARRAY_SIZE = 10000; 118 119 /** 120 * The dgcFilter created from the value of the {@code "sun.rmi.transport.dgcFilter"} 121 * property. 122 */ 123 private static final ObjectInputFilter dgcFilter = 124 AccessController.doPrivileged((PrivilegedAction<ObjectInputFilter>)DGCImpl::initDgcFilter); 125 126 /** 127 * Initialize the dgcFilter from the security properties or system property; if any 128 * @return an ObjectInputFilter, or null 129 */ 130 private static ObjectInputFilter initDgcFilter() { 131 ObjectInputFilter filter = null; 132 String props = System.getProperty(DGC_FILTER_PROPNAME); 133 if (props == null) { 134 props = Security.getProperty(DGC_FILTER_PROPNAME); 135 } 136 if (props != null) { 137 filter = ObjectInputFilter.Config.createFilter(props); 138 if (dgcLog.isLoggable(Log.BRIEF)) { 139 dgcLog.log(Log.BRIEF, "dgcFilter = " + filter); 140 } 141 } 142 return filter; 143 } 144 145 /** 146 * Construct a new server-side remote object collector at 147 * a particular port. Disallow construction from outside. 148 */ 149 private DGCImpl() {} 150 151 /** 152 * The dirty call adds the VMID "vmid" to the set of clients 153 * that hold references to the object associated with the ObjID 154 * id. The long "sequenceNum" is used to detect late dirty calls. If 155 * the VMID "vmid" is null, a VMID will be generated on the 156 * server (for use by the client in subsequent calls) and 157 * returned. 158 * 159 * The client must call the "dirty" method to renew the lease 160 * before the "lease" time expires or all references to remote 161 * objects in this VM that the client holds are considered 162 * "unreferenced". 163 */ 164 public Lease dirty(ObjID[] ids, long sequenceNum, Lease lease) { 165 VMID vmid = lease.getVMID(); 166 /* 167 * The server specifies the lease value; the client has 168 * no say in the matter. 169 */ 170 long duration = leaseValue; 171 172 if (dgcLog.isLoggable(Log.VERBOSE)) { 173 dgcLog.log(Log.VERBOSE, "vmid = " + vmid); 174 } 175 176 // create a VMID if one wasn't supplied 177 if (vmid == null) { 178 vmid = new VMID(); 179 180 if (dgcLog.isLoggable(Log.BRIEF)) { 181 String clientHost; 182 try { 183 clientHost = RemoteServer.getClientHost(); 184 } catch (ServerNotActiveException e) { 185 clientHost = "<unknown host>"; 186 } 187 dgcLog.log(Log.BRIEF, " assigning vmid " + vmid + 188 " to client " + clientHost); 189 } 190 } 191 192 lease = new Lease(vmid, duration); 193 // record lease information 194 synchronized (leaseTable) { 195 LeaseInfo info = leaseTable.get(vmid); 196 if (info == null) { 197 leaseTable.put(vmid, new LeaseInfo(vmid, duration)); 198 if (checker == null) { 199 checker = scheduler.scheduleWithFixedDelay( 200 new Runnable() { 201 public void run() { 202 checkLeases(); 203 } 204 }, 205 leaseCheckInterval, 206 leaseCheckInterval, TimeUnit.MILLISECONDS); 207 } 208 } else { 209 info.renew(duration); 210 } 211 } 212 213 for (ObjID id : ids) { 214 if (dgcLog.isLoggable(Log.VERBOSE)) { 215 dgcLog.log(Log.VERBOSE, "id = " + id + 216 ", vmid = " + vmid + ", duration = " + duration); 217 } 218 219 ObjectTable.referenced(id, sequenceNum, vmid); 220 } 221 222 // return the VMID used 223 return lease; 224 } 225 226 /** 227 * The clean call removes the VMID from the set of clients 228 * that hold references to the object associated with the LiveRef 229 * ref. The sequence number is used to detect late clean calls. If the 230 * argument "strong" is true, then the clean call is a result of a 231 * failed "dirty" call, thus the sequence number for the VMID needs 232 * to be remembered until the client goes away. 233 */ 234 public void clean(ObjID[] ids, long sequenceNum, VMID vmid, boolean strong) 235 { 236 for (ObjID id : ids) { 237 if (dgcLog.isLoggable(Log.VERBOSE)) { 238 dgcLog.log(Log.VERBOSE, "id = " + id + 239 ", vmid = " + vmid + ", strong = " + strong); 240 } 241 242 ObjectTable.unreferenced(id, sequenceNum, vmid, strong); 243 } 244 } 245 246 /** 247 * Register interest in receiving a callback when this VMID 248 * becomes inaccessible. 249 */ 250 void registerTarget(VMID vmid, Target target) { 251 synchronized (leaseTable) { 252 LeaseInfo info = leaseTable.get(vmid); 253 if (info == null) { 254 target.vmidDead(vmid); 255 } else { 256 info.notifySet.add(target); 257 } 258 } 259 } 260 261 /** 262 * Remove notification request. 263 */ 264 void unregisterTarget(VMID vmid, Target target) { 265 synchronized (leaseTable) { 266 LeaseInfo info = leaseTable.get(vmid); 267 if (info != null) { 268 info.notifySet.remove(target); 269 } 270 } 271 } 272 273 /** 274 * Check if leases have expired. If a lease has expired, remove 275 * it from the table and notify all interested parties that the 276 * VMID is essentially "dead". 277 * 278 * @return if true, there are leases outstanding; otherwise leases 279 * no longer need to be checked 280 */ 281 private void checkLeases() { 282 long time = System.currentTimeMillis(); 283 284 /* List of vmids that need to be removed from the leaseTable */ 285 List<LeaseInfo> toUnregister = new ArrayList<>(); 286 287 /* Build a list of leaseInfo objects that need to have 288 * targets removed from their notifySet. Remove expired 289 * leases from leaseTable. 290 */ 291 synchronized (leaseTable) { 292 Iterator<LeaseInfo> iter = leaseTable.values().iterator(); 293 while (iter.hasNext()) { 294 LeaseInfo info = iter.next(); 295 if (info.expired(time)) { 296 toUnregister.add(info); 297 iter.remove(); 298 } 299 } 300 301 if (leaseTable.isEmpty()) { 302 checker.cancel(false); 303 checker = null; 304 } 305 } 306 307 /* Notify and unegister targets without holding the lock on 308 * the leaseTable so we avoid deadlock. 309 */ 310 for (LeaseInfo info : toUnregister) { 311 for (Target target : info.notifySet) { 312 target.vmidDead(info.vmid); 313 } 314 } 315 } 316 317 static { 318 /* 319 * "Export" the singleton DGCImpl in a context isolated from 320 * the arbitrary current thread context. 321 */ 322 AccessController.doPrivileged(new PrivilegedAction<Void>() { 323 public Void run() { 324 ClassLoader savedCcl = 325 Thread.currentThread().getContextClassLoader(); 326 try { 327 Thread.currentThread().setContextClassLoader( 328 ClassLoader.getSystemClassLoader()); 329 330 /* 331 * Put remote collector object in table by hand to prevent 332 * listen on port. (UnicastServerRef.exportObject would 333 * cause transport to listen.) 334 */ 335 try { 336 dgc = new DGCImpl(); 337 ObjID dgcID = new ObjID(ObjID.DGC_ID); 338 LiveRef ref = new LiveRef(dgcID, 0); 339 UnicastServerRef disp = new UnicastServerRef(ref, 340 DGCImpl::checkInput); 341 Remote stub = 342 Util.createProxy(DGCImpl.class, 343 new UnicastRef(ref), true); 344 disp.setSkeleton(dgc); 345 346 Permissions perms = new Permissions(); 347 perms.add(new SocketPermission("*", "accept,resolve")); 348 ProtectionDomain[] pd = { new ProtectionDomain(null, perms) }; 349 AccessControlContext acceptAcc = new AccessControlContext(pd); 350 351 Target target = AccessController.doPrivileged( 352 new PrivilegedAction<Target>() { 353 public Target run() { 354 return new Target(dgc, disp, stub, dgcID, true); 355 } 356 }, acceptAcc); 357 358 ObjectTable.putTarget(target); 359 } catch (RemoteException e) { 360 throw new Error( 361 "exception initializing server-side DGC", e); 362 } 363 } finally { 364 Thread.currentThread().setContextClassLoader(savedCcl); 365 } 366 return null; 367 } 368 }); 369 } 370 371 /** 372 * ObjectInputFilter to filter DGC input objects. 373 * The list of acceptable classes is very short and explicit. 374 * The depth and array sizes are limited. 375 * 376 * @param filterInfo access to class, arrayLength, etc. 377 * @return {@link ObjectInputFilter.Status#ALLOWED} if allowed, 378 * {@link ObjectInputFilter.Status#REJECTED} if rejected, 379 * otherwise {@link ObjectInputFilter.Status#UNDECIDED} 380 */ 381 private static ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo) { 382 if (dgcFilter != null) { 383 ObjectInputFilter.Status status = dgcFilter.checkInput(filterInfo); 384 if (status != ObjectInputFilter.Status.UNDECIDED) { 385 // The DGC filter can override the built-in white-list 386 return status; 387 } 388 } 389 390 if (filterInfo.depth() > DGC_MAX_DEPTH) { 391 return ObjectInputFilter.Status.REJECTED; 392 } 393 Class<?> clazz = filterInfo.serialClass(); 394 if (clazz != null) { 395 while (clazz.isArray()) { 396 if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > DGC_MAX_ARRAY_SIZE) { 397 return ObjectInputFilter.Status.REJECTED; 398 } 399 // Arrays are allowed depending on the component type 400 clazz = clazz.getComponentType(); 401 } 402 if (clazz.isPrimitive()) { 403 // Arrays of primitives are allowed 404 return ObjectInputFilter.Status.ALLOWED; 405 } 406 return (clazz == ObjID.class || 407 clazz == UID.class || 408 clazz == VMID.class || 409 clazz == Lease.class) 410 ? ObjectInputFilter.Status.ALLOWED 411 : ObjectInputFilter.Status.REJECTED; 412 } 413 // Not a class, not size limited 414 return ObjectInputFilter.Status.UNDECIDED; 415 } 416 417 418 private static class LeaseInfo { 419 VMID vmid; 420 long expiration; 421 Set<Target> notifySet = new HashSet<>(); 422 423 LeaseInfo(VMID vmid, long lease) { 424 this.vmid = vmid; 425 expiration = System.currentTimeMillis() + lease; 426 } 427 428 synchronized void renew(long lease) { 429 long newExpiration = System.currentTimeMillis() + lease; 430 if (newExpiration > expiration) 431 expiration = newExpiration; 432 } 433 434 boolean expired(long time) { 435 if (expiration < time) { 436 if (dgcLog.isLoggable(Log.BRIEF)) { 437 dgcLog.log(Log.BRIEF, vmid.toString()); 438 } 439 return true; 440 } else { 441 return false; 442 } 443 } 444 } 445} 446