Chapter 5. Atomicity and Transactions

Smart cards are emerging as a preferred device in such applications as storing personal confidential data and providing authentication services in a mobile and distributed environment. However, with smart cards, there is a risk of failure at any time during applet execution. Failure can happen due to a computational error, or, more often, a user of the smart card may accidentally remove the card from the CAD, cutting off the power supply to the card CPU and terminating execution of any applets. Such premature removal of the smart card from the CAD is called tearing, or card tear. The risk of incomplete execution presents a challenge for preserving the integrity of operations on sensitive data in a smart card.

The JCRE provides a robust mechanism to ensure atomic operations. This mechanism is supported at two levels. First, the Java Card platform guarantees that any update to a single field in a persistent object or a single class field is atomic. Second, the Java Card platform supports a transactional model, in which an applet can group a set of updates into a transaction. In this model, the atomicity of all the updates is ensured.

This chapter explains what atomicity means in the Java Card platform and how applet developers can program an applet by using transactions to protect data integrity.

Atomicity

On the Java Card platform, atomicity means that any update to a single persistent object field (including an array element) or to a class field is guaranteed to either complete successfully or else be restored to its original value if an error occurs during the update. For example, a field in an object currently contains a value 1, and it is being updated with a value 2. The card is accidentally torn from the CAD at the critical moment when the card is overwriting the field. When power comes back, the field is not left with a random value but is restored to its previous value, 1.

The concept of atomicity applies to the contents of persistent storage. It defines how the JCRE handles a single data element in the case of a power loss or other error during an update to that element. The JCRE atomicity feature does not apply to transient arrays. Updating an element of a transient array does not preserve the element's previous value in case of power loss. The next time the card is inserted into a CAD, elements of a transient array are set to their default values (zero, false, or null).

Block Data Updates in an Array

The class javacard.framework.Util provides a method arrayCopy that guarantees atomicity for block updates of multiple data elements in an array:

public static short arrayCopy
(byte[] src, short srcOff, byte[] dest, short desOff, short length)

The Util.arrayCopy method guarantees that either all bytes are correctly copied or the destination array is restored to its previous byte values. If the destination array is transient, the atomic feature does not hold.

However, arrayCopy requires extra EEPROM writes to support atomicity, and thus it is slow. An applet might not require atomicity for array updates. The Util.arrayCopyNonAtomic method is provided for this purpose:

public static short arrayCopyNonAtomic
(byte[] src, short srcOff, byte[] dest, short desOff, short length)

The method arrayCopyNonAtomic does not use the transaction facility during the copy operation even if a transaction is in progress. So this method should be used only if the contents of the destination array can be left in a partially modified state in the event of power loss in the middle of the copy operation. A similar method, Util.arrayFillNonAtomic, nonatomically fills the elements of a byte array with a specified value:

public static short arrayFillNonAtomic
(byte[] bArray, short bOff, short bLen, byte bValue)

Transactions

Atomicity guarantees atomic modification of a single data element. However, an applet may need to atomically update several different fields in several different objects. For example, a credit or debit transaction might require a purse applet to increment the transaction number, update the purse balance, and write a transaction log all as an atomic unit of work.

Readers may be familiar with the use of the database transaction notions of begin, commit, and rollback to ensure that updates to multiple values are either completed in their entirety or not at all. Java Card technology supports a similar transactional model, with commit and rollback capability to guarantee that complex operations can be accomplished atomically; either they successfully complete or their partial results are not put into effect. The transaction mechanism protects against such events as power loss in the middle of a transaction and against program errors that might cause data corruption should all steps of a transaction not complete normally.

Commit Transaction

A transaction is started by invoking the method JCSystem.beginTransaction and ended by calling the method JCSystem.commitTransaction:

// begin a transaction
JCSystem.beginTransaction();

// all modifications in a set of updates of persistent data
// are temporary until the transaction is committed
...

// commit a transaction
JCSystem.commitTransaction();

The changes within a transaction are conditional—the fields or array elements appear to be updated. Reading the fields or array elements back yields their latest conditional values, but the updates are not committed until the JCSystem.commitTransaction method is called.

Abort Transaction

Transactions can be aborted either by an applet or by the JCRE. If an applet encounters an internal problem, it can expressly cancel the transaction by calling the JCSystem.abortTransaction method. Aborting a transaction causes the JCRE to throw away any changes made during the transaction and to restore conditionally updated fields or array elements to their previous values. A transaction must be in progress when the abortTransaction method is invoked; otherwise, the JCRE throws a TransactionException.

When the JCRE regains programmatic control on return from an applet with a transaction still in progress—that is, when the applet did not explicitly commit or abort an ongoing transaction—the JCRE automatically calls the abortTransaction method. Similarly, the JCRE aborts a transaction if an exception is thrown within the transaction and the exception is not handled by the applet.

If power is lost or an error occurs during a transaction, the JCRE invokes a JCRE internal rollback facility the next time the card is powered on to restore the data involved in the transaction to their pretransaction values.

In any case, both transient and persistent objects created during a transaction that fails (due to power loss, card reset, computational error, or a program abort action) are deleted and their memory is freed by the JCRE.

Nested Transaction

Unlike most database transactions, transactions in the Java Card platform cannot be nested. There can be only one transaction in progress at a time. This requirement is due to the limited computing resources of smart cards.

If JCSystem.beginTransaction is called while a transaction is already in progress, the JCRE throws a TransactionException. An applet can discover whether a transaction is in progress by invoking the method JCSystem.transactionDepth. The method returns 1 if a transaction is in progress, 0 otherwise.

Commit Capacity

To support the rollback of uncommitted transactions, the JCRE maintains a commit buffer where the original contents of the updated fields are stored until the transaction is committed. Should a failure occur before a transaction is completed, the participating fields in the transaction are restored to their original contents from the commit buffer. The more operations inside a transaction block, the larger the commit buffer needs to be to accommodate them.

The size of the commit buffer varies from one implementation to another, depending on the available card memory. In general, the commit buffer allocated in a JCRE implementation is large enough to accommodate most applets' needs—an applet typically accumulates tens of bytes during a transaction. However, because smart card resources are limited, it is important that only the updates in a logical unit of operations are included in a transaction. Putting too many things in a transaction may not be possible.

Before attempting a transaction, an applet can check the size of the available commit buffer against the size of the data requiring an atomic update. The class JCSystem provides two methods to help applets determine how much commit capacity is available on a Java Card platform implementation.

  • JCSystem.getMaxCommitCapacity() returns the total number of bytes in the commit buffer.

  • JCSystem.getUnusedCommitCapacity() returns the number of unused bytes left in the commit buffer.

In addition to storing the contents of fields modified during a transaction, the commit buffer holds additional bytes of overhead data, such as the locations of the fields. The amount of overhead data depends on the number of fields being modified and on the transaction system implementation. The commit capacity returned by the two methods is the total number of bytes of persistent data—including overhead—that can be modified during a transaction.

If the commit capacity is exceeded during a transaction, the JCRE throws a TransactionException. Even so, the transaction is still in progress unless it is explicitly aborted by the applet or by the JCRE.

TransactionException

The JCRE throws a TransactionException if certain kinds of problems, such as a nested transaction or a commit buffer overflow, are detected within a transaction.

TransactionException is a subclass of RuntimeException. It provides a reason code to indicate the cause of the exception. Java Card exceptions and reason codes are explained in Chapter 6. Following are the reason codes defined in the class TransactionException:

  • IN_PROGRESS—. beginTransaction was called while a transaction was already in progress.

  • NOT_IN_PROGRESS—. commitTransaction or abortTransaction was called while a transaction was not in progress.

  • BUFFER_FULL—. During a transaction, an update to persistent memory was attempted that would have caused the commit buffer to overflow.

  • INTERNAL_FAILURE—. An internal fatal problem occurred within the transaction system.

If a TransactionException is not caught by the applet, it will be caught by the JCRE. In the latter case, the JCRE automatically aborts the transaction.

Local Variables and Transient Objects during a Transaction

Readers should be aware that only updates to persistent objects participate in a transaction. Updates to transient objects and local variables (including method parameters) are never undone regardless of whether or not they were “inside” a transaction. Local variables are created on the Java Card stack, which resides in RAM.

The following code fragment demonstrates three copy operations involving a transient array key_buffer. When the transaction aborts, neither of the array copy operations nor any single update of the key_buffer element in the for loop are protected by the transaction. Similarly, the local variable a_local retains the new value 1.

byte[] key_buffer = JCSystem.makeTransientByteArray
                    (KEY_LENGTH, JCSytem.CLEAR_ON_RESET);

JCSystem.beginTransaction();

Util.arrayCopy(src, src_off, key_buffer, 0, KEY_LENGTH);
Util.arrayCopyNonAtomic(src, src_off, key_buffer, 0, KEY_LENGTH);

for (byte i = 0; i < KEY_LENGTH; i++)
   key_buffer[i] = 0;

byte a_local = 1;

JCSystem.abortTransaction();

Because local variables or transient array elements do not participate in a transaction, creating an object and assigning the object to a local variable or a transient array element need to be considered carefully. Here is a code example:

JCSystem.beginTransaction();

// ref_1 is an instance (object) field
ref_1 = JCSystem.makeTransientObjectArray
        (LENGTH, JCSystem.CLEAR_ON_DESELECT);
// ref_2 is a local variable
ref_2 = new SomeClass();

// check status
if (!condition)
   JCSystem.abortTransaction();
else
   JCSystem.commitTransaction();

return ref_2;

In the example, the instance field ref_1 stores a reference to a transient object, and the local variable ref_2 stores a reference to a persistent object. As described previously, if a transaction aborts, persistent and transient objects created during a transaction are automatically destroyed. This has no side effect on the instance field ref_1, because its content is restored to the original value if the transaction does not complete normally. However, a potential problem occurs in the next line, when a newly created object is assigned to a local variable. On transaction failure, the JCRE deletes the object; however, ref_2 still points to the location where no object exists anymore. The situation gets worse if ref_2 is later used as a return value. In this case, the caller receives a dangling pointer.

To avoid generating a dangling pointer, which compromises the Java language type security, the JCRE ensures that references to objects created during an aborted transaction are set to null. In the example, if the abortTransaction method is invoked, the local variable ref_2 is set to null. This solution may not be ideal, but it avoids security violation while minimizing system overhead.

This example is not applicable to most applets, because casually creating objects within a method is strongly discouraged. When possible, an applet should allocate all the objects it needs during applet initialization (see Chapter 7). However, an implementor of a Java Card installer might need to deal with considerable object creation within a transaction and should avoid the scenario described in the code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.27.244