Security of Embedded Java Virtual Machine
This article introduces the security of embedded Java virtual machine (EJVM).
Meeting this topic, you may consider it an introduction of J2ME security mechanism. No! Although security model of MIDP 2 seems perfect, it’s only a part of security solution of embedded Java virtual machine.
picture 1: J2ME technical architecture
Picture 1 shows the technical architecture of J2ME, security solution should cover all these levels. In my opinion, I consider security of EJVM in four aspects:
MIDP 2.0 Security Mechanism
Java language
Class files verifying
Implementation of JVM
Let me introduce them respectively.
1 MIDP 2.0 Security Mechanism
MIDP (Mobile Information Device Profile) 2.0 has a complete authorization model.
1.1 Sensitive Operations
In mobile information device, some operations are very sensitive for the users. Network connection is one of them. Because the hardware resource is very limited, and data transferring is expensive.
The MIDP 2.0 specification defines a set of operations as sensitive. To perform any one of it, the application must get approval from user.
javax.microedition.io.Connector.http |
javax.microedition.io.Connector.socket |
javax.microedition.io.Connector.https |
javax.microedition.io.Connector.ssl |
javax.microedition.io.Connector.datagram |
javax.microedition.io.Connector.serversocket |
javax.microedition.io.Connector.datagramreceiver |
javax.microedition.io.Connector.comm |
javax.microedition.io.PushRegistry |
table 1: sensitive operations/permissions list
As table 1 shows, permission names are not same as package names, but they are similar.
For example, if a MIDlet want to connect to some server with HTTP, is must get permission to do that. Because connecting HTTP is a sensitive operation, only when user approve, the MIDlet can go on.
1.2 Permission
Normally, there are several kinds of permission level user can choose. Take WTK 2.0 of SUN for example, three kinds of permission can be selected by user:
Oneshot: The MIDP implementation retains no memory of the user's decision and will ask for a permission every time it is needed.
Session: The implementation remembers the user's decision until the MIDlet is terminated.
Blanket: The user's decision sticks until the MIDlet suite is uninstalled.
1.3 Protection Domain
If user must choose to grant or deny the authorization, it’ll be a lot of trouble. In fact, users don’t have to do that every time, at least for these safe MIDlets. MIDlet suites are classified into different protection domains. Each domain has different permissions authorization. A domain stands for some level of safety, some permissions are granted and some denied by default.
Usually, there will be at least three kinds of protection domain defined:
Manufactory: These MIDlet suites are made by device vendors or operators, they are absolutely safe, so every permission are granted, the MIDlet suite can do any sensitive operation on the platform.
Trusted: These MIDlet suites are made by trusty vendors, but there still be security risk, so some critical permission are denied by default, when such operations are performed, user will be queried.
Untrusted: Vendor of MIDlet suite is unknown, the program may be dangerous, so every sensitive operation must get user’s grant.
2 Java language
As a language, Java has some mechanism to enhance the security inner itself.
2.1 Structured memory access
One measure to enhance the security in language level is structured memory access. It means that developer can’t access memory directly.
Why unrestrained memory access would be a security risk is that a wily cracker could potentially use memory pointer to subvert the security system. If, for example, a cracker could learn where in memory a class loader is stored, it could assign a pointer to that memory and manipulate the class loader's data. By enforcing structured access to memory, the Java virtual machine yields programs that are robust, but also frustrates crackers who dream of harnessing the internal memory of the Java virtual machine for their own devious plots.
2.2 Checking of array boundary
Another aspect of enhanced security is about array. A common error of C/C++ programmer is covering bytes beyond boundary while writing data to an array. Some times, this will cause serious problem, even make the program crash. And this is common method used by crackers to attack a system. But this will never happen in Java language.
According to Java language specification, Java Virtual Machine will check the use of array, if a programmer wants to write or read a member of array by index which is beyond the boundary, an exception will be threw out.
2.3 Garbage collection
Automatic garbage collection is another vantage of Java language. In C/C++ language, programmers have to free the memory blocks which are allocated manually. Memory leakage won’t give attack chances to crackers, but it will bring down the performance of program, especially in embedded system, the memory resource is very limited, memory leakage may cause serious problem.
Java language can release programmers from these troubles. Unused memory collection will be performed by VM. If an object is not referenced by any handle, it’ll be marked as “unreferenced”, and will be freed in some appropriate occasion.
3 Class files verifying
To ensure the security, some kinds of class file scanning will be performed before executing. Each scanning is to verify some different content.
However, these verifying are not strictly prescribed in JVM specification, it depends on implementation. Usually, PC JVM will perform all these scanning, but for EJVM it may me different.
3.1 Common Verifying of class files
Although compliant Java compilers should not generate malformed class files, a Java virtual machine can't tell how a particular class file was created. Because a class file is just a sequence of bytes, a virtual machine can't know whether a particular class file was generated by a well-meaning Java compiler or by shady crackers bent on compromising the integrity of the virtual machine. As a consequence, all Java virtual machine implementations have a class file verifier that can be invoked on class files, to make sure the types they define are safe to use.
For common used JVM on PC, four scanning will be performed during loading a class file:
3.1.1 Structural Checks on the Class File
The first scanning is to ensure the structure of class file.
The verifier performs many checks during this pass. For example, every class file must start with the same four bytes, the magic number: 0xCAFEBABE. The purpose of the magic number is to make it easy for the class file parser to reject files that were either damaged or were never intended to be class files in the first place. The verifier also makes sure the major and minor version numbers declared in the class file are within the range supported by that implementation of the Java virtual machine.
Also during pass one, the class file verifier checks to make sure the class file is neither truncated nor enhanced with extra trailing bytes. Although different class files can be different lengths, each individual component contained inside a class file indicates its length as well as its type. The verifier can use the component types and lengths to determine the correct total length for each individual class file.
3.1.2 Semantic Checks on the Type Data
During this pass, the verifier looks at individual components, to make sure they are well-formed instances of their type of component. For example, a method descriptor (its return type and the number and types of its parameters) is stored in the class file as a string that must adhere to a certain context-free grammar. One check the verifier performs on individual components is to make sure each method descriptor is a well-formed string of the appropriate grammar.
3.1.3 Byte code Verification
During this pass, which is commonly called the "byte code verifier," the Java virtual machine performs a data-flow analysis on the streams of byte codes that represent the methods of the class.
This step is the longest one. The byte code verifier does a great deal of checking. It checks to make sure that no matter what path of execution is taken to get to a certain “opcode” in the byte code stream, the operand stack always contains the same number and types of items. It checks to make sure no local variable is accessed before it is known to contain a proper value. It checks that fields of the class are always assigned values of the proper type, and that methods of the class are always invoked with the correct number and types of arguments. The byte code verifier also checks to make sure that each opcode is valid.
After this scanning, the stream of byte code is proved to be safe for the Java virtual machine to execute.
3.1.4 Verification of Symbolic References
Pass four of the class file verifier takes place when the symbolic references contained in a class file are resolved in the process of dynamic linking. During pass four, the Java virtual machine follows the references from the class file being verified to the referenced class files, to make sure the references are correct.
When the Java virtual machine resolves a symbolic reference, pass four of the class file verifier makes sure the reference is valid. If the reference is not valid--for instance, if the class cannot be loaded or if the class exists but doesn't contain the referenced field or method--the class file verifier throws an error.
3.2 Verifying performed by EJVM
For EJVM, if four scanning are all performed each time when loading a new class file, the performance will be greatly reduced. In embedded system, the speed of CPU is much lower then PC, four times of verifying is too time-consuming.
Usually, EJVM use other method to guarantee correctness of class files:
(1) Standard library classes are verified before they are solidified in ROM. During loading period, no verifying is performed.
(2) MIDlet suites are verified during installation. Once they are installed in device, they are considered as safe, no need to verify any more.
4 Implementation of JVM
4.1 Buffer Overflow (Inner JVM)
Buffer overflow, or buffer overrun, is an anomalous condition where a process attempts to store data beyond the boundaries of a fixed-length buffer. The result is that the extra data overwrites adjacent memory locations. The overwritten data may include other buffers, variables and program flow data, and may result in erratic program behavior, a memory access exception, program termination (a crash), incorrect results or a possible breach of system security.
Buffer overflow is the most commonly used method by cracker to infiltrate a system. Usually, cracker will copy more data into the fixed-length buffer, cover the return address of the function. When the function returns, the process will jump to somewhere to go on executing, while a piece of carefully designed code which called “shellcode” is waiting there. Then the system will be managed by cracker.
Although Java language has “array boundary checking” to prevent buffer overflow, for JVM it’s another pair of shoes, because JVM is written in local language (C/C++). Buffer Overflow is not a particular threaten for JVM, but JVM will suffer it as well.
4.2 Native Interface (Out of JVM)
Java Native Interface (JNI) is a topic of J2SE, not J2ME, that’s right! But for EJVM, for the standard library of MIDP, some implementation of EJVM may support JNI to implement features depending on system.
JNI breaks the OO concept of Java language. When EJVM is going to call some native interface, it must load some dynamic link library at first and then call the functions. From view point of security, there is too much risk in this procedure: the library file may be unauthorized, or it may be replaced, the function may be unsafe, etc.
5 Summary
This article summaries the security of embedded Java virtual machine from four aspects. The security solution of J2ME is complete and trustable. But from view of implementation of EJVM, every EJVM is distinguished from each other, so there are still some risks.