Базы данныхИнтернетКомпьютерыОперационные системыПрограммированиеСетиСвязьРазное
Поиск по сайту:
Подпишись на рассылку:

Назад в раздел

Chapter 19 -- Security Issues

Chapter 19

Security Issues


CONTENTS


If you have been reading the trade magazines or browsing the Java sites on the Internet, you have probably heard a lot of the buzz about security in Java. Rightfully so, as the failure to provide adequate security to protect the systems that download Java applets guarantees Java's demise. Realistically, any Internet application that downloads and runs foreign executables, and dynamically links in classes at runtime that have been created by unknown programmers, can be as dangerous as it is powerful. While these features afford you the ability to seamlessly run code from anywhere on the Web, there is no guarantee that this code is not defective. Any programmer can intentionally or unintentionally produce code that can damage your system. There is no way you can predict that the code you plan to execute is faulty. Even the code provided by reputable organizations should not be completely trusted.

You might have already read that many bugs have been discovered in Java's security system. While it is true that many bugs have been found in Java, they are primarily related to the functionality of Java and not to its security. A few important security holes have been discovered at the time of this writing, and they are either fixed in the JDK 1.01 and Netscape Navigator 2.01, or are in the process of being fixed.

These bugs should not scare you away from using Java. Java has gone through beta testing. Sun Microsystems has distributed its JDK for free to anyone who wants it, and Sun has therefore reaped the rewards of the expeditious discovery of bugs and holes in its software by a huge user base that has exposed the product to high levels of scrutiny. With each new beta release, the Java team repaired new bugs. By the time Java 1.1 is released for general availability, it should have repaired the most serious holes in its security model. Inevitably, hackers will find ways to get around Java's security, as they do with many systems, but it is in the best interest of the Java team to be committed to providing constant security updates to its product as usage by the Java community evolves.

http://www.io.org/~mentor/J_Notes.html

Tip:
As you work with Java, be sure to keep an eye out for articles and announcements in magazines and on the Web about bugs and patches. One sight to add a bookmark for is Digital Espresso, a useful source of newly reported Java bugs. You can find it at the following address:
http://www.io.org/~mentor/J___Notes.html

Java's security structure is actually quite promising and is better than anything else out there today with similar power. It is a multilayered security system. This means that Java's security system is implemented in separate steps at each layer of the Java environment that code travels through before it is actually executed on your machine. Such depth of security is important because the Java programming environment is divided into layers. It is conceivable that any Java tool in the environment can be rewritten to circumvent the Java security structure. Although the security specifications implemented at the language and compiler layers allow you to create code with
confidence that it is reasonably secure, they do not guarantee the security of Java code run from the Internet that is created and compiled by unknown programmers. There is nothing stopping a programmer from building a new compiler that allows code with security violations to compile cleanly and therefore destroy your system if you run it. Therefore, the other layers of the Java environment must be able to subject compiled bytecode from foreign sources to extreme levels of scrutiny.

Java's runtime environment that you install on your own system trusts no executable, no class, no instruction, or no parameter. It provides several additional layers of security that interrogate code before it is executed. This chapter examines all of Java's layers of security in detail, as well as the bugs that currently exist in August, 1996.

The Java Language

The first place that security is implemented in Java is, appropriately, in its language. The Java language provides security through its class libraries. Chapter 11, "Reading and Writing with Java," and Chapter 12, "Network Programming with Java," cover the security features of two of the class libraries: Java.io and Java.net. To summarize, Java.net provides the interfaces to handle the various network protocols (FTP, HTTP, Telnet, and so on). This package guards against tampering at the network interface level. The networking package can be configured at different levels of security to

  • Disallow all network accesses.
  • Allow network accesses to only the sources from which the code was imported.
  • Allow network accesses only outside the firewall if the code came from outside.
  • Allow unlimited network accesses.

Java.io provides many classes that have already been extensively tested, so it is recommended that you use these abstract classes in your code. Using Java.io classes to receive and send data to different input and output devices ensures that such activities are performed in the most secure manner. This holds true for all of Java's built-in packages. They have all been tested and should be used to ensure that your code does not violate any rules.

The Java language also adds security by providing access restrictions for encapsulation of classes, methods, and variables. Any class not declared public, for instance, is inaccessible by foreign classes. Any class declared protected is accessible only by its objects and subclasses. Any class declared private limits access to objects instantiated from it.

The Java language also eliminates pointer arithmetic and prevents you from explicitly controlling pointers in any manner. Instead, the compiler assigns symbolic references to methods, and the interpreter automatically assumes the responsibility of managing memory allocation and deallocation. As you have learned, pointers and pointer arithmetic used in other C-type programs are a leading source of bugs that crash systems.

Moreover, you can use the Java language to create your own security manager. The Java runtime environment has its own security manager that is constantly active at runtime. This security manager is an object that authorizes all operations before they are executed. The security manager throws a SecurityException if it rejects an operation. Otherwise, it passes the operation and allows it to run.

The Java.lang package provides an abstract security manager class that you can subclass to create your own security manager. The class provides methods that inspect classloaders on the execution stack.

Note that you cannot install a new security manager in an applet. Applets are subject to the security manager of the application in which they are running (your browser or Java AppletViewer).

The Java Compiler

The Java compiler not only checks that your syntax is correct in your source code that was created in the Java language, but it ensures that the code doesn't violate the language's safety rules. The compiler ensures that you have not made any errors, such as casting objects that are incompatible or using incorrect parameters.

As discussed in Chapter 2, "Getting Started," the Java Compiler works similarly to compilers in C-type languages in that it takes intelligible source code and converts it to code for a machine to interpret. The difference is that the machine that the Java compiler compiles for is the Java Virtual Machine, and the code is not native machine code for your CPU, it is bytecode for the JVM. Additionally, the Java compiler does not convert references to numbers and does not create a memory layout for the program at compile time. Although performance takes a hit since references in Java must be looked up in an object index at runtime instead of referring to exact memory addresses with the code, these changes were made for security reasons.

The compiler enforces sizes for bytecode commands and symbolic address references it creates. Each bytecode command consists of an opcode and an operand. The opcode is the command that the interpreter recognizes. The operand is the data needed by the opcode. Opcodes are executed sequentially and stored in 8-bit numbers. Operands vary in length, but are divided into bytes. Each opcode has a 32-bit symbolic address reference, or handle. The interpreter is able to locate pieces of code in memory using the opcodes assigned by the compiler. It is important that these sizes remain constant for portability, and the compiler ensures that they are.

The Java Interpreter

The Java interpreter performs many functions, some of which are performed solely for the purpose of the security of the system, and others that are performed as a part of the execution of the Java application, but require that security is enforced at each step.

One function that the interpreter performs for the purpose of security is laying out the memory map at runtime. This is unlike C and C++, in which the memory map is laid out by the compiler. The interpreter's allocation of memory in a Java application might vary depending on the user's hardware and software platform. This prevents a hacker from predicting where a class exists in memory, and then directly manipulating it.

Because memory is allocated by the runtime interpreter, Java has the luxury of eliminating the use of pointers in the language that explicitly addresses memory space. This prevents an innocent programmer from accidentally placing the wrong memory address in the code for a method, which would result in crashing your system. The compiled code references memory with handles that are resolved to exact memory addresses at runtime by the Java interpreter. You are unable to forge pointers to memory in Java, because the memory layout and object index do not exist until runtime and are controlled entirely by the Java interpreter.

Without pointers to locate a method, for example, the Java interpreter's memory layout is used to locate the method during runtime. When a method is called for the first time in a program, the interpreter refers to an object index of symbolic references, created by the compiler, that it checks against the memory layout it has created and finds where it placed the method in memory when the class was loaded. Subsequent calls to this method do not require such a lookup because the index contains the proper memory address.

Such symbolic references solve the fragile superclass security problem, which occurs in programs created in C and C++ when a superclass has been updated, possibly changing the memory layout. If a subclass tries to call a method from the updated superclass, its placement might be different in memory, and the program jumps to an obscure area of memory, inadvertently jeopardizing the system. In Java, the subclass calls methods symbolically from the superclass, and the interpreter locates the method using its memory layout and object index. Therefore, the correct method is called from the correct area of memory every time.

In addition to the security inherent in its runtime memory layout and object index referencing, the interpreter enforces security in three layers: the class loader, bytecode verifier, and runtime system. The class loader brings in the Java file, plus any classes referenced or inherited by the classes in the code. The bytecode verifier ensures that the code adheres to Java standards and doesn't violate the integrity of your system. The runtime system executes the code on your hardware.

The Class Loader

The class loader is responsible for loading classes that are called while a Java program is executing and laying them out in memory in such a way that they are not able to interfere with each other without explicit measures set forth in the language. It loads both local classes and foreign classes that have been determined clean by the bytecode verifier.

You can think of the execution environment of a Java application as a set of classes that are partitioned into separate namespaces. The class loader provides a layer of security by placing incoming classes in their own namespaces. Classes do not interfere with classes in other namespaces, or partitions, without explicit calls to their symbolic references and the permission of the target class to be accessed by the foreign classes (the target class must not have declared any access restrictions).

The class loader assigns one namespace for all of the classes that come from the local file system (built-in Java classes), and a separate namespace for the each source of imported classes. This protects local classes from foreign classes. When a class references another class, it first searches the local system's namespace, then the namespace of the referencing class. Foreign classes have no way of simulating a local class. Likewise, built-in classes cannot interfere with imported namespaces without referencing their classes explicitly. Foreign classes are similarly partitioned from each other because they are each assigned their own namespaces.

The Bytecode Verifier

The Java Interpreter passes all incoming code to a bytecode verifier. The responsibility of the bytecode verifier is to subject every piece of code that the interpreter passes it to a rigorous series of integrity tests. It performs a variety of tests that run from simple verification that the format of a line of code fragment is consistent with the language specification, to passing each line of code through a theorem prover to trap the following types of problems:

  • Forged pointers
  • Access restriction violations (private, public, or protected)
  • Mismatching of object types
  • Operand stack overflows and underflows
  • Incorrect bytecode parameters
  • Illegal data conversion

After code has been approved by the bytecode verifier, you can be reasonably sure that the language does not violate your system with harmful instructions that fit any of these conditions. To maintain system performance, after code passes the verifier tests and is approved it will not be checked again. This enables the interpreter to reliably execute the code at full speed without stopping to check its integrity.

The Java class loader and bytecode verifier make no assumptions about the primary source of the bytecode stream. The code may have come from the local system, or it may have come from a system in another country. The bytecode verifier is the last line of defense against errant code. Java requires that imported code passes the verifier's tests before it is executed by any means on the system.

The Execution of Code

Once the code has been loaded, laid out in memory, and verified, it is executed a piece at a time by the interpreter. The interpreter can execute bytecodes that have been coded for the Java Virtual Machine specification directly. It also provides a just-in-time compiler that compiles intermediate bytecode to native machine code at runtime for cases that you are willing to sacrifice portability to allow the bytecode to run at full speed. Security can be implemented at runtime by coding traps and exception handlers into your program.

Java Virtual Machine

At this point in the book, you might be curious as to how the Java Virtual Machine actually works. A grasp of the fine points of the JVM gives you a greater understanding of the security structure of Java. This section unravels the mystery of the JVM.

The JVM is intended to provide a set of specifications that the Java language, compiler, and interpreter adhere to in order to ensure secure, portable programs and runtime environments. The JVM provides a strict set of rules that can be used by a developer to create an original implementation of an interpreter that runs Java code on any machine it is installed on. These rules require that the runtime interpreter include all of the following pieces:

  • A set of bytecode instructions similar to that of a CPU, which contains opcodes and operands, and their values and alignments
  • A set of registers that tracks the state of the program at a given time
  • A Java stack, which stores information about the states of methods in stack frames
  • A garbage collection heap, which stores memory that is to be allocated to objects
  • Memory areas for storage, which store constants and methods

The Bytecode Instruction Set

When Java code is compiled, it is converted to bytecode, which is similar to the assembly
language created by C and C++ compilers. Each instruction in the bytecode contains an opcode followed by an operand. The following list contains examples of opcodes and their descriptions:

  • iload loads an integer
  • aload loads a pointer
  • ior logically or two integer

Opcodes are represented by 8-bit numbers. Operands vary in length. They are aligned to eight bits, and therefore, operands larger than eight bits are divided into multiple bytes. The reason Java uses such small memory spaces is to maintain compactness of memory. The Java team felt that compact code was worth the performance hit on the CPU while locating each instruction, a hit that results from the inability of the interpreter to judge exactly where each instruction is due to the varying lengths of instructions. This decision reclaims lost performance as compact bytecode travels across networks more quickly than code found in other programming languages that contains unused memory space left free as a result of larger, fixed instruction lengths. Of course, code with fixed instruction lengths runs more quickly on the CPU because the interpreter can jump through instructions, anticipating their lengths and exact locations.

The instruction set provides specifications for opcode and operand syntax and values, and identifier values. It also includes instructions for invoking methods.

Opcode recognizes the primitive data types described in Chapter 1, " An Overview of Java." In addition, it recognizes the symbolic object reference, which is a type of 32-bit length. The Java compiler manages these types. It assigns bytecodes that are appropriate for each type and each method.

The JVM Register Set

The JVM contains four 32-bit registers that store information about the current state of the system. These registers are updated after the execution of each bytecode.

  • pc  The counter that keeps track of which bytecode in the program is currently being executed.
  • optop  The pointer to the top of the operand stack in the Java stack that is used when the program performs operations.
  • frame  The pointer to the current execution environment of the current method in the Java stack.
  • vars  The pointer to the first local variable of the current method that is executing in the Java stack.

The processor of your machine deals quickly with these registers.

The Java Stack

The Java stack provides the current parameters to bytecodes during execution of methods. Each method of a class is assigned a stack frame that is stored in the Java stack. Each stack frame holds the current status of local variables, the operand stack, and the execution environment.

The local variables for the method are stored in an array of 32-bit variables indexed by the vars register. Larger variables are divided across two local variables. When local variables are used, they are loaded onto the operand stack for the method. The operand stack is a 32-bit first in, first out (FIFO) stack that stores operands for opcodes in the JVM instruction set. These operands are both parameters used in methods' instructions, as well as results of instructions. The execution environment provides information about the current state of the method in the Java stack. It stores pointers to the previous method, pointers to its local variables, and pointers to the top and bottom of the operand stack. It might also contain debugging information.

The Garbage Collection Heap

As you learned in Chapter 4, "Creating Your Own Objects," Java's garbage collector keeps track of references to objects allocated in memory using symbolic handles. When an object is no longer being referenced during the execution of the program, the garbage collector returns the memory used by the object to its garbage collection heap. This heap is a separate area of memory in Java that is allocated when the runtime system is started. It is provided specially for allocation of memory to new objects. If the system the interpreter runs on supports virtual memory, the size of the garbage collection heap can grow as necessary.

The JVM Memory Areas

The other memory areas provided in the JVM are for storing methods and the constant pool. All of the bytecode for Java methods is stored in the method area. It also stores symbol tables for dynamic linking of classes and additional debugging information associated with a method. The constant pool area encodes string constants, class names, method names, and field names for each class. It is created by the Java compiler. These memory areas are not required to be laid out in any particular location to avoid exposure to hackers who would be able to find their code if they knew the memory map before runtime.

Limitations

The JVM in JDK 1.01 has a few limitations due to its fixed operand and stack sizes that may be resolved in future releases of the JDK:

  • Stack width of 32-bits limits the JVM's internal addressing to 4G of memory.
  • 8-bit offsets into objects limit the number of methods in a class to 256.
  • 16-bit offsets for branching and jumping instructions limit the size of a method to 32K.
  • Unsigned 16-bit indexes into a constant pool limit the number of constant pool entries per method to 32K.
  • Unsigned 8-bit argument counts limit the size of an argument to 255 32-bit words (only 127 long or double words).

These limitations are not issues today because 4G of internal addressing space is not necessary on today's machines that typically have 16 or 32M RAM. However, technology advances quickly, so this limit could conceivably become an issue in the near future. Keep in mind that these limitations might be relaxed in later releases of Java.

Known Bugs

It is important that you are aware of the bugs in Java that have already been discovered and reported before you spend time and energy struggling with them. The latest release of the Java Developer's Kit is version 1.0.1. You should be aware of several bugs that exist in this release. This section lists the open bugs in the JDK 1.0.1 as reported by Sun Microsystems, some workarounds and patches that Sun suggests using, the JDK 1.0 security bugs that have been announced by Sun as fixed in JDK version 1.0.1, and some newer security bugs that have not yet made it to Sun's official list of open bugs.

Additional bugs will be discovered as Java is used by an increasing number of programmers in increasing capacities. It is important that you regularly check Java's Web site for bugs that Sun confirms, and additionally, unofficial Java Web sites, like Digital Espresso, report bugs that have not yet been confirmed by Sun.

Keep in mind that the bulk of these bugs address minor problems with the functionality of Java, and not with Java's security.

An updated list of the open bugs in the JDK can be found at http://java.sun.com/products/JDK/1.0.2/KnownBugs.html.

Future Java Security

Many organizations are currently racing to produce tools that will improve Java security. These organizations are creating their own bytecode verifiers, runtime environments, compilers, and virus scanning software. They are working on providing encryption of binaries that will make it difficult for hackers to reverse engineer Java code. They are developing internal versioning systems and object directory services that will allow for authentication of applets.

Two examples of such organizations are Symantec, which has recently announced its virus scan software package for Java, and The University of Illinois Systems Software Research Group, which has recently announced the release of a package that gives the Java programmer access to a security API. The details as announced are as follows in this excerpt from Symantec Corporation's home page:

"Symantec Corporation, a leading supplier of utilities software products, today announced the development of new technology that lays the foundation for delivery of leading edge antivirus solutions. The Symantec AntiVirus Research Center (SARC) has developed the first native-Java virus scanner for Java applets sent over the Internet. In addition, SARC has also designed an in-house automation technology that can be used to analyze, replicate, detect and define a large subset of the most common computer viruses.

One of the fastest growing development environments is Java. While no current Java virus threats exist, there is a possibility that a virus could be written. In addition, due to Java's inherent portability, a virus of this type could spread over a wide variety of platforms. To address this possibility, SARC has produced a Java class file scanner extension for NAV. This will enable NAV to provide real-time protection and monitor for Java virus activity within Netscape or any other Java supported Web browser.

The current AutoProtect capability in Norton AntiVirus (NAV) is configured to scan Java applets sent over the Internet in .CLASS files, and can detect a potential type of Java (Java Type I) virus that can be propagated by modifying html pages.

The new Java scanner technology can detect another, more complex, type of Java virus (Java Type II) that parasitically infects .CLASS files. The Java .CLASS file scanner provides a much faster and more efficient scan than that achieved with conventional brute-force scanning technology and represents the best scanning technology available today for the Java environment. At the first sign of a Java virus threat, Symantec will make this technology available to customers via an immediate virus definition update."

http://choices.cs.uiuc.edu/Security/JGSS/jgss.html

Caution:
This is a press release by the University of Illinois from the Digital Espresso home page:
"The University of Illinois Systems Software Research Group has released the first alpha version of their JGSS Java package. This package provides Java programs access to the Generic Security Service API defined in RFC-1508 and implemented by MIT's Kerberos system. The package is available for download for personal, educational, and research use at the following address http://choices.cs.uiuc.edu/Security/JGSS/jgss.html."

Summary

The status of security in Java is ever-changing. This chapter has provided you with a solid comprehension of the inner workings of Java's security structure as it exists today. To remain informed of the state of this structure as it grows and changes, and to be sure that you are effectively protecting your code and your computer environment from hackers, you must be sure to regularly check Java's Web site, other Java sites, and magazine articles for announcements of new changes, additional functionality, patches, and bugs as they arise.


Previous Contents




  • Главная
  • Новости
  • Новинки
  • Скрипты
  • Форум
  • Ссылки
  • О сайте




  • Emanual.ru – это сайт, посвящённый всем значимым событиям в IT-индустрии: новейшие разработки, уникальные методы и горячие новости! Тонны информации, полезной как для обычных пользователей, так и для самых продвинутых программистов! Интересные обсуждения на актуальные темы и огромная аудитория, которая может быть интересна широкому кругу рекламодателей. У нас вы узнаете всё о компьютерах, базах данных, операционных системах, сетях, инфраструктурах, связях и программированию на популярных языках!
     Copyright © 2001-2024
    Реклама на сайте