9 3.3 Java RMI One major drawback of RPC is that it supports only a limited set of basic data types, and cannot handle arbitrary objects to be sent to procedures as parameters or to be returned). RMI was introduced to overcome this deficiency. 3.3.1 Interface definition Java RMI does not need a special IDL, since it is used when both the client and the server are written in Java. An application that makes its methods available to remote clients must declare the methods in an interface that extends the java.rmi.Remote interface. 1 In the rest of this section, we follow the example available at the Web page “Useful Links”/“Intro to RMI” with some minor modifications. In “Useful Links”/“Getting Started Using RMI”, the client is implemented as an applet, instead of an application, and therefore is more complicated. You may want to read it after you understand what’s given below first. Find.java file (contains an interface definition): // Find.java import java.rmi.*; //Import the rmi package. public interface Find extends Remote { //Must extend java.rmi.Remote. public String findLine(String keyword) throws RemoteException; } //Each method must throw java.rmi.RemoteException. 3.3.2 Server implementation class The method(s) declared in an interface must be implemented by the server and the server object must be exported, i.e., made to begin listening for incoming RMI requests. Exporting is implicit if the object extends the UnicastRemoteObject class in the java.rmi.server package. In this case, the constructor exports the server object.2 This class also creates an object that listens for network requests using a socket. We now give an example of a server (FindImpl3 that finds if a query keyword appears in a given text file containing many lines, and if so, prints out all the lines that contain the query keyword). To provide this service, FindImpl first converts the given file into a Vector by converting the lines of the file into vector elements. With this preparation, checking if a given keyword appears in an element becomes an easy task, since such an operation is provided by a Java API. FindImpl.java file: // FindImpl.java import java.io.*; import java.util.*; import java.rmi.*; import java.rmi.server.*; 1 An interface can be extended to another interface. It can be exported explicitly by exportObject() of the java.rmi.server package. 3 It’s a common practice in the Java community to name the server implementing interface X as XImpl 2 CMPT401 Chapter 3, Summer 04 10 public class FindImpl extends UnicastRemoteObject implements Find { private Vector list = new Vector(); public FindImpl(String aFile) throws RemoteException //constructor { try { FileReader fr = new FileReader(aFile); BufferedReader br = new BufferedReader(fr); String s = null; //convert a line into a vector element while ((s = br.readLine()) != null) list.addElement(s); fr.close(); } catch (Throwable e) { System.err.println("exception"); System.exit(1); } } //end of constructor public String findLine(String keyword) { if (keyword == null) return null; keyword = keyword.toLowerCase(); int n = list.size(); for (int i = 0; i < n; i++) { //For each line in the list String line = (String)list.elementAt(i); if (line.toLowerCase().indexOf(keyword) != -1) //Is "keyword" part of the l return line; //Return the line containing "keyword" } return null; } public static void main(String args[]) { try { RMISecurityManager security = new RMISecurityManager(); System.setSecurityManager(security); String aFile= args[0]; FindImpl server = new FindImpl(aFile); Naming.rebind("//127.0.0.1:2099/FindServer", server); System.out.println("FindServer ready..."); } catch (Throwable e) { 11 CMPT401 Chapter 3, Summer 04 System.err.println("exception: " + e); System.exit(1); } } } FindServer is any string that you choose as the name FindImpl is to be known by. server is an FindImpl object which has the server port number encoded in it. Naming.rebind sends the codebase information to rmiregistry. RMISecurityManager enforces security restrictions on the classes that are downloaded from the network. See §3.4. 3.3.3 Registry We assume here that the RMI registry service is available at port 2099 of the server host. Thus we bind FindImpl to this registry service. If you omit //127.0.0.1:2099 in the above example, the default is "//127.0.0.1:1099", where 1099 is the well-known address of the RMI registry service. Note: As the port designation of the rmiregistry, instead of //127.0.0.1:2099, you can also use //:2099, or rmi://orion.csil.sfu.ca:2099, etc., assuming you are running the RMI registry (and the server) on orion. 2 A simple way of starting an RMI registry is to use the rmiregistry application that comes with the Java SDK. In Unix, just type the command rmiregistry 2099 & and in Windows, type start rmiregistry 2099 Registry Server 1. rmiregistry & 5. Return reference Client Application 4. Lookup by name 2. Export service 3. Bind to Registry 8.RMI 6. Server Application HTTP Server 7. Stub Sleketon Machine A Machine B Figure 5: RMI steps on the host (on which the server will be running), where 2099 is the port number used by the registry server. A server registers its service with a registry whose URL is either well-known or can be known by some means. To register service, the server calls methods, CMPT401 Chapter 3, Summer 04 12 bind or rebind, of the rmi.Naming class, e.g., rmi.Naming.rebind. 4 To find the object corresponding to a service name, a client uses rmi.Naming.lookup, which is URL-formatted. See the client example in the next subsection. The client can then make method calls to the object returned by rmi.Naming.lookup. (The client stub gets in action behind the scenes.) 3.3.4 Client FindClient.java file:5 // FindClient.java import java.rmi.*; import java.rmi.server.*; public class FindClient { public static void main(String args[]) { try { RMISecurityManager security = new RMISecurityManager(); System.setSecurityManager(security); //Assuming the server is running on orion String name = "rmi://" + "orion.csil.sfu.ca:2099" + "/FindServer"; Find ref = (Find)Naming.lookup(name); String results = ref.findLine(args[0]); if (results == null) System.err.println("** not found **"); else System.out.println(results); } catch (Throwable e) { System.err.println("exception: " + e); System.exit(1); } } } 3.3.5 Compiling and Running For details, read FindService.pdf available in the Assignment menu. Server is started with java -Djava.rmi.server.codebase=http://orion.scil.sfu.ca/∼tiko/codebase/ FindImpl 4 bind may throw AlreadyBoundException, while rebind quietly replaces the existing object with the same name as the object being bound, if any. 5 In the following code, ref is cast as interface Find, not as FindImpl, which might appear more logical. Note, however, that FindImpl.class is not normally available in the client machine. 13 CMPT401 Chapter 3, Summer 04 Before starting rmiregistry, make sure that CLASSPATH (local codebase) on the server machine does not contain the path to the client stub. Otherwise, rmiregistry will not pass java.rmi.server.codebase property to client (and client will not be able to download the stub). Read the rmi tutorial on the Sun Web site. • Compile: javac FindImpl.java generates FindImpl.class. • Compile: javac FindClient.java generates FindClient.class. • Run the stub generator: FindImpl Stub.class. rmic FindImpl generates FindImpl Skel.class and • Move FindImpl Stub.class and to codebase, e.g., ∼tiko/codebase. To run the programs, follows the steps below: • Start Registry: rmiregistry 2099 & (Unix), or start rmiregistry 2099 (Windows). • Run server and register java -Djava.rmi.server.codebase=http://serverHost/∼tiko/codebase/ FindImpl phonebook where phonebook is a text file containing a name and his/her phone number on each line. • Move FindClient.class and Find.class to the client machine (may be on another network) unless it was compiled there. Run Client: java FindClient smith 3.4 Java Security In Java, executable contents, such as applets and stubs, can be shipped to other hosts and executed. This has serious implications for security. At the client site, an applet may use classes from the following three groups of classes: 1. The base classes (i.e., java’s built-in classes, e.g., java.lang.*), 2. local classes (on CLASSPATH), or 3. remote classes (remote for the standpoint of the client). The first two are trusted classes and the third is untrusted. An untrusted (and possibly malicious) executable object downloaded from the Internet should be run in a restricted environment (e.g., sandbox). The default is that such an object is not allowed to access the local file system, except for loading trusted classes that are locally available (1. and 2. 14 CMPT401 Chapter 3, Summer 04 02/06/2003 4 Figure 6: Protection domains in the above list). By default, it can access only data/programs from the remote machine where it came from.6 Recent Java versions (1.2 or newer) have more flexibility and fine-grained control regarding security. (See Textbook p. 256.) As shown in Fig. , loaded code can be put in different protection domains, depending on the trust the client has in it. A protection domain contains codes from the same source (URL) and with the same signature. The system code shipped with the JDK (see group 1 above) is in a unique domain. The most trustworthy are allowed to access any system resources (see the top left of the figure), while the least trustworthy are put in a sandbox with the default restrictions stated above. There is a single system-wide policy file, which applies to every user, and a single user policy file for each user. The system policy file is by default located at java.home/lib/security/java.policy (Solaris) 7 java.home\lib\security\java.policy (Windows) The system policy file grants system-wide code permissions. The java.policy file installed with the JDK grants all permissions to standard extensions, allows anyone to listen on un-privileged ports, and allows any code to read certain “standard” properties that are not security-sensitive, such as the os.name and file.separator properties. Here is a part of the java.policy file: // Standard extensions get all permissions by default grant codeBase "file://${java.home}/lib/ext/*" { permission java.security.AllPermission; 6 It is specified as the codebase in the <APPLET> tag in the HTML file. The default for codebase= is the directory where the HTML file came from. 7 java.home refers to the value of the system property named java.home, which specifies the directory into which the JDK was installed. CMPT401 Chapter 3, Summer 04 15 }; grant { // Allows any thread to stop itself using java.lang.Thread.stop(). // Note that this permission is granted by default only to remain // backwards compatible. // It is strongly recommended that you either remove this permission // from this policy file or further restrict it to code sources // that you specify, because Thread.stop() is potentially unsafe. // See "http://java.sun.com/notes" for more information. permission java.lang.RuntimePermission "stopThread"; // allows anyone to listen on un-privileged ports permission java.net.SocketPermission "localhost:1024-", "listen"; // "standard" properties that can be read by anyone permission java.util.PropertyPermission "java.version", "read"; permission java.util.PropertyPermission "java.vendor.url", "read"; permission java.util.PropertyPermission "os.name", "read"; permission java.util.PropertyPermission "os.version", "read"; permission java.util.PropertyPermission "os.arch", "read"; ... } The user policy file is by default located at8 user.home/.java.policy (Solaris) user.home\.java.policy (Windows)9 When the Policy is initialized, the system policy is loaded in first, and then the user policy is added to it. If neither policy is present, a built-in policy is used. This built-in policy is the same as the sandbox policy given earlier in this section. In JDK 1.2 RMI, you need to specify a policy file when you run your server and client. Given below is a general policy file that allows downloaded code, from any code base, to do the following two things: • Connect to or accept connections on unprivileged ports (≥ 1024) on any host • Connect to port 80 (HTTP port) on any host 8 Policy file locations are specified in the security properties file, which is located at java.home/lib/security/java.security (Solaris) or java.home\lib\security\java.security (Windows). A policy file can be placed in the current directory as well. 9 user.home refers to the value of the system property named user.home, which specifies the user’s home directory. Given user name uName, the user.home property value defaults to C:\Winnt\Profiles\uName on multi-user Windows NT systems CMPT401 Chapter 3, Summer 04 16 grant { permission java.net.SocketPermission "*:1024-", "connect,accept"; permission java.net.SocketPermission "*:80", "connect"; }; FilePermission http://java.sun.com/products/jdk/1.2/docs/guide/security/permissions.html The following sample policy file entry grants code from the /home/sysadmin directory read access to the file /tmp/aFile: grant codeBase "file:/home/sysadmin/*" { permission java.io.FilePermission "/tmp/aFile", "read"; }; A java.io.FilePermission represents access to a file or directory. 10 It is followed by a pathname and a set of actions permitted for that pathname. A pathname consisting of a single “*” indicates all the files in the specified directory, while a pathname consisting of a single “-” indicates all the files in the specified directory and (recursively) all files and subdirectories contained in the specified directory. The actions to be granted are passed to the constructor in a string containing a list of zero or more comma-separated keywords. The possible keywords are “read”, “write” (which includes permission to create) “execute” (allows Runtime.exec to be called and checked by SecurityManager.checkExec), and “delete” (checked by SecurityManager.checkDelete). grant { permission java.io.FilePermission "c:\\home\\ann\\public_html\\classes\\-", "read"; permission java.io.FilePermission "c:\\home\\jones\\public_html\\classes\\-", "read"; }; Note that in Windows-style file names, the backslash character needs to be represented by two backslash characters in the policy file. In addition to giving different privileges based on the origin of the code, codes signed by particular owners can be treated preferentially using “grant signedBy”. Policy Enforcement by Security Manager SecurityManager is an object of class11 java.lang.SecurityManager. Various methods in the Java libraries call a check method before performing an operation which could potentially affect the system integrity (e.g., file read/write, network access, access to the environmental variables) A Security Manager, if one is installed, checks if the operation 10 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. 11 It was an abstract class in JDK1.1. CMPT401 Chapter 3, Summer 04 17 is permitted, and if so simply returns; otherwise, it throws a SecurityException. Since access to these key system functions is controlled by calls within the trusted classes, there is no way to avoid this validation. A browser installs AppletSecurityManager as part of the JVM initialization, and once installed, an applet cannot change it (e.g., to expand its privilege). AppletSecurityManager ensures that an applet downloaded over a network12 is subjected to the sandbox policy, i.e., it cannot read/write in the local file system, start other programs on the client host, or make network connections to other sites, except to its originating host. The aim is to prevent an applet from doing the following: 1. Uninvited retrieval of data by reading local files (other than those reachable via CLASSPATH) or extracting local environmental info. For example, a checkRead method of a Security Manager receives a file reference as an argument. A security exception is thrown when an applet attempts to read a file it is not allowed to. Similarly for checkWrite. When an applet invokes Socket(host,port), the constructor method of the Socket class in turn invokes the checkConnect(host,port) method of the Security Manager, which compares host with the source where the applet came from. If the applet is untrusted, host must agree with its origin.13 Otherwise, a security exception is thrown. 2. Overriding trusted built-in classes on the client machine. 3. Using the client machine as a platform to attack other systems. 4. Changing the environment by making calls, e.g., System.exit() in an attempt to kill the browser it is executing in. (checkExit() of AppletSecurityManager throws an exception.) All JDK system code invokes SecurityManager methods (e.g., checkRead) to check the policy currently in effect and perform access control checks. Such a method throws an AccessException when a prohibited method is called. Here are some examples of RMISecurityManager methods: • public synchronized void checkAccept(String host, int port): Does not permit a stub to accept socket connections. • public synchronized void checkConnect(String host, int port): permit a stub to make socket connections unless it is loaded locally. Does not • public synchronized void checkDelete(String file): Does not permit a stub to delete files. 12 An applet stored in the local disk and is on the CLASSPATH of the program that runs it (e.g., appletviewer), then it is loaded by the file system loader, and can access the local file system. If it is loaded by means of an appletloader by a browser, it is subjected to the same restrictions as downloaded applets. 13 Exactly the same name that was used to download the applet must be used to specify this originating host. Using the equivalent IP address, for example, won’t work. CMPT401 Chapter 3, Summer 04 18 • public synchronized void checkExec(String command): Does not permit a stub to execute code. • public synchronized void checkExit(int StatusCode): Does not permit a stub to kill the Virtual Machine. • public synchronized void checkListen(int port): Does not permit a stub to listen to a port. • public synchronized void checkRead(String file): Does not permit a stub to read a file. • public synchronized void checkWrite(String file): Does not permit a stub to write to a file. Typically, a SecurityManager is installed whenever an applet is running; i.e., the appletviewer and most browsers install a security manager. A security manager is not automatically installed when an application is started. To apply the same security policy to an application found on the local file system as to downloaded applets, you can use one of the following two methods: 1. By setting a system property (java.security.manager property) in the command line when lauching the JVM. java -Djava.security.manager myApp. 2. From within your program. System.setSecurityManager (new SecurityManager()). In the first method, it is possible to specify a particular security manager to be utilized, as in java -Djava.security.manager=MySecMgr myApp All of the following are equivalent and result in usage of the default security manager: java -Djava.security.manager myApp java -Djava.security.manager="" myApp java -Djava.security.manager=default myApp JDK 1.2 includes a property named java.class.path. Classes that are stored on the local file system but should not be treated as base classes should be on this path. Classes on this path are loaded with a secure class loader and are thus subjected to the security policy being enforced. There is also a command-line argument whose usage determines what policy files are to be enforced. If you don’t include -Djava.security.policy on the command line, then the policy files specified in the security properties file will be used. For example, if you type the following, where pURL is a URL specifying the location of a policy file, then the specified policy file will be loaded in addition to all the policy files specified in the security properties file: CMPT401 Chapter 3, Summer 04 19 java -Djava.security.manager -Djava.security.policy=pURL myApp If you use a double equals (==) instead of =, then just the specified policy file will be used; all others will be ignored: The RMI runtime requires that a security manager be explicitly set before any remote stub classes can be downloaded over the network. RMISecurityManager allows the stub classes to download necessary class files (from their codebase) over the network. Notes: See http://java.sun.com/docs/books/tutorial/rmi/running.html Class loaders Class loaders are the gate keepers of the JVM, controlling what byte code may be loaded. Each class loader itself must be loaded by another class loader. The chicken-and egg problem is overcome by the initial loader, called the primordial class loader, which is written in a native language (platform-dependent) and is available when the JVM is started. All other class loaders are a subclass of an abstract Java built-in class, java.lang.ClassLoader. It defines the loadClass() method, which reads in class data and calls the defineClass() method to create an instance of the class Class. java.lang.ClassLoader has been extended by a subclass, java.security.SecureClassLoader. There are different loaders for different types of applications. The primordial class loader loads all classes that are part of the Java API, while URLClassLoader loads classes from the CLASSPATH. Since applets have special security needs, an AppletClassLoader, which is a subclass of java.security.SecureClassLoader, is used for them. It dynamically load (locally or remotely, as needed) the classes that the applet extends or the classes which the applet creates instances of. Each applet is loaded by a different AppletClassLoader. If a downloaded applet creates an instance of some class, then the AppletClassLoader first delegates loading to primordial class loader. If it is not locally available via the CLASSPATH environment variable, the AppletClassLoader loads it from the site where the applet came from. 14 The search order is always: (1) Java built-in classes, (2) local classes (in the current directory or reachable via CLASSPATH), and then (3) remote classes. This order is not just for convenience. Assume that someone at a remote site wrote a malicious class, which has the same name as one of the built-in classes, e.g., SecurityManager(), and made it available for downloading. If (3) is searched before (1), this malicious class would be loaded as the security manager! The primary security function of class loaders is to prevent classes loaded from untrusted sources from being confused with trusted classes. RMIClassLoader takes care of loading for RMI. In particular, it loads stub classes, as well as any utility classes needed by these stub classes. As for the stub classes, the java.rmi.server.codebase property is used to locate them. As described in the write-up FindService.pdf in the Assignment menu, this can be set by the −D flag when the Java interpreter is launched to run the server. java -Djava.rmi.server.codebase=http://orion.csil.sfu.ca:2001/ FindImpl phonebook.txt 14 ClassNotFound exception is thrown, if not remotely available either. CMPT401 Chapter 3, Summer 04 20 The server passes this information to rmiregistry, which annotates the reference to the server by its codebase. As for objects passed as parameters or return types, RMIClassLoader first attempts to load classes on the local file system via CLASSPATH just like AppletClassLoader. If it fails, then it extracts the URL annotating the serialized objects, which is then used as the codebase. If you want to force RMI to only load classes locally, the java.rmi.server.useCodebaseOnly property can be set. In this case, if the needed class is not available locally, a ClassNotFound exception is thrown. Class file verifier Verifies that the downloaded untrusted class files obey the rules of JVM (Jave Virtual Machine) by carrying out the following: 1. File integrity check: makes sure that the length of each structure in the file matches the specs in the header. 2. Class integrity check: e.g., the class has a superclass, unless it is of type Object, the superclass is not a final class (which cannot be extended), and it does not override a final method in its superclass, etc. 3. Bytecode Integrity Check: Bytecode generated by the correct Java compiler will be correct, but bytecode can be handcrafted, generated by a “hostile compiler”, or even compiled from a non-Java language. So it could be malicious, and may not obey the language rules. Checks are made for: (a) Illegal bytecode instructions, which will confuse JVM. (b) Incorrect number and types of operands, illegal access to classes, fields (data) or methods. (c) Over(under)-flowing the program stack. Simulated execution is carried out using only the type info (not the actual values) of the items pushed into or popped out of the stack. (d) Illegal casting. From http://java.sun.com/sfaq/#appletCL: The verifier ensures that • There are no stack overflows or underflows. • All register accesses and stores are valid. • The parameters to all bytecode instructions are correct. • There is no illegal data conversion. The verifier accomplishes that by doing a data-flow analysis of the bytecode instruction stream, along with checking the class file format, object signatures, and special analysis of finally clauses that are used for Java exception handling.