Atomicity and Transactions
Smart cards are becoming increasingly popular devices for applications such as storing personal confidential information, as well as providing authorized access in mobile and distributed environments. However, when executing an applet in a smart card, there is a possibility of an error. The interruption of work may occur due to an error in calculations, but
most often this happens when the user unexpectedly removes the card from the reader. At the same time, the power supply to the card stops, and the execution of all applets is interrupted. Such premature removal of the smart card from the reader is called the termination of the communication session. The probability of incomplete execution of applets is a serious problem. Such a situation can lead to a violation of the integrity of important data stored in the memory of the smart card.
To prevent data loss, JCRE provides a reliable mechanism for indivisible, or atomic, operations. This mechanism is supported on two levels. First, the Java Card platform guarantees that any update of a single field of a permanent object or a field of a class is an atomic operation. Secondly, the Java Card platform supports the transaction model. In the applet, several update operations can be combined into a transaction. This model guarantees the atomicity of any updates.
This chapter discusses the importance of atomicity in the Java Card platform, and also describes the use of transactions in the development of applets to ensure data integrity.
For the Java Card platform, atomicity means the following: it is guaranteed that any update of a single field of a permanent object (including an array element) or a class field will be performed in full or, in case of an error, this field will retain the previous value. For example, imagine that an object field has a value of 1. The update consists in assigning this field a value of 2. At the time of the update, the communication session unexpectedly breaks. After the power is turned on, this field will not contain a random value. It will retain the previous value, i.e. 1.
The concept of atomicity refers to the contents of programmable permanent memory. It defines the procedure for JCRE actions in the event of a power outage or other abnormal situation during a change in the value
of a separate data element. The atomicity mechanism implemented in JCRE does not apply to temporary arrays. In the event of a power outage when updating an element of the temporary array, its previous value is not restored. When the card is inserted into the reader again, the elements of the temporary arrays will be assigned default values (zero, false or null).
In the javacard class.framework.Util has an arrayCopy method, which guarantees the atomicity of updating several elements of the array:
public static short arrayCopy
(byte[] src, short srcOff, byte[] dest, short desOff, short length)
The Util.arrayCopy method guarantees that either all bytes of data will be successfully written to the array, or the previous values of its elements participating in the update will be restored. The atomicity property does not apply to the operation of updating elements of temporary arrays.
However, to implement the arrayCopy method, additional write operations to EEPROM memory are used, and copying slows down. Therefore, atomicity should not be used in applets if it is not needed. The Util.arrayCopyNonAtomic method is provided for simple copying of data to an array:
public static short arrayCopyNonAtomic
(byte[] src, short srcOff, byte[] dest, short desOff, short length)
The arrayCopyNonAtomic method does not use atomicity capabilities when copying, even if copying is performed within the framework of an active transaction. This method should be used only if the contents of the destination array may remain in a partially updated state in the event of a power outage when the values of the elements change. There is a similar method, Util.arrayFillNonAtomic, which is designed for non-atomic filling of a byte array with a certain value:
public static short arrayFillNonAtomic
(byte[] bArray, short bOff, short bLen, byte bValue)
Atomicity guarantees the correctness and consistency of updating an individual data element or restoring its previous value. But sometimes in an applet it is required to ensure the indivisibility of the update operation of several fields belonging to different objects. For example, imagine an applet implementing the functions of an “electronic wallet”. To withdraw money or to replenish the account, you need to increase the transaction number, update the balance and add an entry to the transaction log. All these changes constitute one indivisible process.
The reader may be familiar with the transaction mechanisms that are available in various DBMS. There are usually functions for opening, completing and rolling back transactions, which guarantee either a complete update of all data, or a return to the previous state. Java Card technology implements a similar transaction model. Commit and rollback mechanisms are provided, which guarantee the indivisibility of complex update operations. The entire set of updates is either completed completely, or the values of all fields remain the same. The transaction mechanism allows you to protect data from damage in situations such as a power outage during the update of values or due to software errors.
Transaction commit
The transaction begins with a call to the JCSystem.beginTransaction method and
ends with a call to the JCSystem.commitTransaction method:
// start of transaction
JCSystem.beginTransaction();
// all changes to the values of permanent fields
// are temporary until the transaction is committed
…
// transaction commit
JCSystem.commitTransaction();
During the execution of the transaction, all field changes are visible, and their values can be used for work. During the transaction, all fields retain the assigned values and issue them on repeated access, but confirmation of updates occurs only after calling the JCSystem.commitTransaction method.
Abort a transaction
Either the applet itself or JCRE can interrupt the execution of the transaction. If an internal problem occurs during the execution of the applet, it can urgently interrupt the execution of the transaction by calling the JCSystem.abortTransaction method. After receiving a request to abort the transaction, JCRE cancels all changes made within the transaction and assigns previous values to all fields and array elements that participated in the transaction. Calling the abortTransaction method is allowed only during the execution of the transaction. Otherwise, JCRE throws a TransactionException.
If the applet transfers control to JCRE during the execution of the transaction, i.e. in the case when the applet has not explicitly completed the transaction, JCRE automatically calls the abortTransaction method. JCRE also aborts a transaction if an exception was raised during its execution, the processing of which is not provided by the applet.
If a power outage or any error occurs during the execution of the transaction, the internal rollback mechanism provided in JCRE is activated. At the beginning of the next communication session, all fields that participated in the transaction will be assigned the previous values.
In any case, all permanent and temporary objects created during a transaction whose execution was interrupted (due to a power outage, card reset, computational error, or program execution interruption) are deleted, and JCRE frees up the memory they occupy.
Nested transactions
Unlike many DBMS, the Java Card platform does not support nested transactions. Only one transaction can be started at a time. This is because the smart card has limited computing resources.
If the JCSystem.beginTransaction method is called during the execution of a transaction, JCRE throws a TransactionException exception. The applet can find out if any transaction is currently in progress by calling the JCSystem.transactionDepth method. This method returns a value of 1 if the transaction is in progress, or 0 if there are no active transactions.
Transaction Buffer Size
To support the rollback of incomplete transactions, JCRE provides a buffer in which the previous values of fields updated within the transaction are stored. These values are stored in the buffer until the transaction is committed. If an error occurs during the execution of a transaction, the permanent fields that were updated during the execution of the transaction are assigned values extracted from this buffer. The more update operations occur during the execution of the transaction, the larger the buffer volume should be. The size of the transaction buffer depends on the specific implementation and the available amount of memory. Usually, a buffer size is provided that allows you to meet the needs of an average applet. Several tens of bytes are allocated for each transaction. But when developing applets, it is necessary to take into account that the smart card has limited resources, and only logically justified operations should be included in the transaction. You should not perform too many updates within the transaction.
Before starting a transaction, the applet can make a request to find out the available buffer size for temporary data storage. The JCSystem class provides two methods that allow the applet to find out how much memory is available for servicing the transaction mechanism in this implementation of the Java Card platform.
· The JCSystem.getMaxCommitCapacity() method returns the full size of the transaction buffer in bytes.
· The JCSystem.getUnusedCommitCapacity() method returns the number of free bytes in the buffer.
In addition to the initial values of the updated fields, the buffer contains service information, such as links to these fields. The amount of service information depends on the number of updated fields and on the specific implementation of the system. Both methods return the total available buffer size in permanent memory, including the amount needed to store service information.
If the buffer is completely full during the execution of the transaction, JCRE throws a TransactionException. But even in this case, the execution of the transaction continues until it is explicitly interrupted by the applet or JCRE.
TransactionException
If certain problems occur during the execution of a transaction, such as a transaction buffer overflow or an attempt to open a nested transaction, JCRE throws a TransactionException.
The TransactionException exception is a subclass of Runtime Exception. It contains the exception reason code. Java Card exceptions and their codes are discussed in detail in Chapter 6. The following exception codes are defined in the TransactionException class:
· IN_PROGRESS – the beginTransaction method is called, but an active transaction is already being performed.
· NOT_IN_PROGRESS – the commitTransaction or abortTransaction method is called, but there is no active transaction.
· BUFFER_FULL – during the execution of the transaction, the volume of the transaction buffer in permanent memory has been exhausted.
· INTERNAL_FAILURE – an internal system error occurred during the transaction execution.
If the TransactionException exception is not handled by the applet, JCRE handles it. In the latter case, JCRE automatically aborts the transaction.
Changing the values of local variables and temporary objects during the execution of a transaction
It should be remembered that transactions affect only changes to permanent objects. The previous values of the fields of temporary objects and local variables (including method parameters) are never restored automatically, even if they are changed within the framework of an active transaction. Local variables are placed in the Java Card stack, which is located in RAM.
The code snippet below discusses three options for copying data to a temporary key_buffer array. If the transaction is interrupted, it is not possible to roll back both the group copy operation and the update operation of individual elements of the key_buffer array in the for loop. If the transaction is interrupted, the value of the local variable a_local will also remain equal to 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();
Due to the fact that local variables and elements of temporary arrays are not protected by the transaction mechanism, when developing applets, it is necessary to be very careful about creating temporary objects, as well as assigning values to local variables and elements of temporary arrays. Here is an example of the code:
JCSystem.beginTransaction();
// ref_1 – object instance field
ref_1 = JCSystem.makeTransientObjectArray (LENGTH, JCSystem.CLEAR_ON_DESELECT);
// ref_2 – local variable
ref_2 = new SomeClass();
// checking the state
if (!condition) JCSystem.abortTransaction();
else
JCSystem.commitTransaction(); return ref_2;
In this example, the ref_1 field stores a reference to a temporary object, and the ref_2 local variable stores a reference to a permanent object. As discussed earlier, if a transaction is interrupted, all permanent and temporary objects that were created during its execution are automatically destroyed. Therefore, there will be no side effects associated with assigning a value to an instance of the ref_1 field, because in the event of a transaction interruption, its previous value will be restored. But problems can arise in the next line when a local variable is assigned a reference to the newly created object. If the transaction is interrupted, JCRE will delete this object, but the ref_2 variable will still point to the address where there is no object anymore. The situation is further aggravated by the fact that ref_2 is further used as a return value. In this case, the calling program will receive a reference to a non-existent object.
In fact, such a link can become a “loophole” through which the applet will gain access to “someone else’s” memory. Therefore, JCRE sets all references to objects created during the aborted transaction to null. In the example given, during the execution of the abortTransaction method, the ref_2 variable will take the null value. Such a solution, of course, is not ideal, but it ensures the security of the system and does not require significant overhead.
This example is not typical for most applets, because usually the creation of objects in methods is not welcome. If possible, the applet should create all the necessary objects during its initialization. However, the developer of the Java Card installer may encounter the need to create objects during the transaction, so you should avoid the situations illustrated in the example above.