package edu.mit.csail.tc;
import javacard.framework.APDU;
import javacard.framework.JCSystem;
import javacard.framework.Util;
/**
* TEM memory management module.
* @author Victor Costan
*
* This module manages the memories available in a Javacard chip. Its main
* reason of existence is that normal APDUs can only carry 256 bytes of data,
* and TEM procs are much bigger than that. The module now has versatile logic
* which juggles between volatile and non-volatile memory.
*/
class TEMBuffers {
/**
* The buffer file (parallel: register file).
* Allocated as <code>CLEAR_ON_DESELECT</code> memory, since buffers are
* temporary in nature.
*/
private static Object[] buffers;
/** The requested sizes of the buffers in the file. */
private static short[] sizes;
/**
* Pinned and busy flags for each buffer.
* Allocated as <code>CLEAR_ON_DESELECT</code> memory, since buffers are
* temporary in nature.
*/
private static byte[] flags;
/** Number of entries in the buffer file. */
public static final byte NUM_BUFFERS = 8;
/** Returned by {@link #create(short)} to communicate failure. */
public static final byte INVALID_BUFFER = -1;
/** The size of a buffer chunk. */
public static short chunkSize;
/**
* Initializes the TEM buffer module.
*
* Called when the TEM is activated.
*
* @return <code>true</code> if all is good, <code>false</code> if the TEM is
* already initialized
*/
public static final boolean init() {
// Initialize the buffer file.
if (buffers != null)
return false;
buffers = new Object[NUM_BUFFERS];
// The buffer file is reset to an empty state (no layout, no flags) when an
// application connects to the TEM applet.
flags = JCSystem.makeTransientByteArray(NUM_BUFFERS,
JCSystem.CLEAR_ON_DESELECT);
sizes = JCSystem.makeTransientShortArray(NUM_BUFFERS,
JCSystem.CLEAR_ON_DESELECT);
return true;
}
/**
* Lays out the TEM buffers into memory.
*
* Called when the TEM is activated, after all the other modules allocated
* their static objects.
*/
public static final void layout() {
// Partition the RAM into buffers.
for (byte i = 0; i <= 1; i++) {
short availableMemory = JCSystem.getAvailableMemory(
JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT);
availableMemory -= 280;
short nextSize = (i != 0) ? availableMemory : 512;
buffers[i] = JCSystem.makeTransientByteArray(nextSize,
JCSystem.CLEAR_ON_DESELECT);
}
// Partition the EEPROM into buffers.
short reservedMemory = (short)(
JCSystem.getAvailableMemory(JCSystem.MEMORY_TYPE_PERSISTENT) /
(short)4);
if (reservedMemory < 2560) reservedMemory = 2560;
if (reservedMemory > 12288) reservedMemory = 12288;
for (byte i = 5; i >= 0; i--) {
short availableMemory = JCSystem.getAvailableMemory(
JCSystem.MEMORY_TYPE_PERSISTENT);
availableMemory -= reservedMemory;
short nextSize = (i != 0) ? (short)(availableMemory >> 1)
: availableMemory;
buffers[2 + i] = new byte[nextSize];
}
}
/**
* Refreshes the TEM's estimate of the buffer chunk size.
*
* @return the newly estimated buffer chunk size
*/
public static final short guessChunkSize() {
short inSize = APDU.getInBlockSize();
short outSize = APDU.getOutBlockSize();
TEMBuffers.chunkSize = (short)((inSize < outSize ? inSize : outSize) - 7);
// If the card seems to support chunk sizes greater than 255 bytes, fall
// back to 255, to avoid any driver issues.
if (TEMBuffers.chunkSize >= 256)
TEMBuffers.chunkSize = 255;
return TEMBuffers.chunkSize;
}
/**
* Releases all the resources held by the TEM buffer module.
*
* Called when the TEM is de-activated.
*
* @return <code>true</code> if all is good, <code>false</code> if the TEM is
* not initialized
*/
public static final boolean deinit() {
if (buffers == null)
return false;
buffers = null;
flags = null;
sizes = null;
return true;
}
// Flag: a buffer file entry is allocated.
private static final byte BUFFER_ALLOCATED = (byte)1;
// Flag: a buffer file entry is pinned.
private static final byte BUFFER_PINNED = (byte)0x80;
/**
* Allocates a memory zone and an entry in the buffer file that references it.
*
* @param bufferSize the desired buffer size, in bytes
* @return the buffer file entry referencing the allocated buffer, or
* {@link #INVALID_BUFFER} if the buffer could not be allocated
*/
public static final byte create(short bufferSize) {
// TODO: better memory allocation using the pinned flags
// Find the smallest buffer that accomodates the request. Also keep track
// of the largest free buffer for the code below.
byte lastFree = INVALID_BUFFER;
for (byte i = 0; i < NUM_BUFFERS; i++) {
if ((flags[i] & BUFFER_ALLOCATED) != 0)
continue;
lastFree = i;
if (((byte[])buffers[i]).length >= bufferSize) {
sizes[i] = bufferSize;
flags[i] |= BUFFER_ALLOCATED;
return i;
}
}
// No buffer works. If the largest free buffer is in EEPROM, try to "resize"
// it (release / reallocate) to fit the request.
if (JCSystem.isTransient(buffers[lastFree]) !=
JCSystem.MEMORY_TYPE_PERSISTENT)
return INVALID_BUFFER;
if (lastFree != INVALID_BUFFER) {
buffers[lastFree] = new byte[bufferSize];
if (JCSystem.getAvailableMemory(JCSystem.MEMORY_TYPE_PERSISTENT) < 2048)
JCSystem.requestObjectDeletion();
sizes[lastFree] = bufferSize;
flags[lastFree] |= BUFFER_ALLOCATED;
}
return lastFree;
}
/**
* Pins down the memory zone referenced by a buffer file entry.
*
* This method should be called before obtaining a buffer via
* {@link #get(byte)}. The associated method, {@link #unpin(byte)}, should be
* called once the work on the buffer is complete.
*
* @param bufferIndex the buffer file entry whose memory zone will be pinned
* @return <code>true</code> if the pinning succeeded, or <code>false</code>
* if the given buffer file entry is invalid
*/
public static final boolean pin(byte bufferIndex) {
if (bufferIndex < 0 || bufferIndex >= NUM_BUFFERS)
return false;
flags[bufferIndex] |= BUFFER_PINNED;
return true;
}
/**
* Un-pins a memory zone previously pinned by {@link #pin(byte)}.
*
* This method should be called after finishing all the work on a buffer which
* was previously pinned via {@link #pin(byte)}.
*
* @param bufferIndex the buffer file entry whose memory zone will be
* un-pinned
*/
public static final boolean unpin(byte bufferIndex) {
if (bufferIndex < 0 || bufferIndex >= NUM_BUFFERS)
return false;
flags[bufferIndex] &= ~BUFFER_PINNED;
return true;
}
/**
* The memory zone of a buffer referenced by the buffer file.
*
* The buffer must have been pinned via a call to {@link #pin(byte)}, and it
* must remain pinned while it is referenced.
*
* @param bufferIndex the buffer file entry whose buffer will be returned
* @return the byte array that is the buffer in a buffer file
*/
public static final byte[] get(byte bufferIndex) {
if (bufferIndex < 0 || bufferIndex >= NUM_BUFFERS ||
flags[bufferIndex] >= 0)
return null;
return (byte[])buffers[bufferIndex];
}
/**
* The requested buffer size for a buffer file entry.
*
* @param bufferIndex the buffer file entry whose buffer size will be returned
* @return the buffer size requested when the file entry was allocated
*/
public static final short size(byte bufferIndex) {
return sizes[bufferIndex];
}
/**
* Releases a buffer's memory zone and frees the associated buffer file entry.
*
* The buffer must not be pinned.
*
* @param bufferIndex the buffer file entry that will be cleared
*/
public static final void release(byte bufferIndex) {
if (bufferIndex < 0 || bufferIndex >= NUM_BUFFERS)
return;
Util.arrayFillNonAtomic((byte[])buffers[bufferIndex], (short)0,
sizes[bufferIndex], (byte)0);
sizes[bufferIndex] = 0;
flags[bufferIndex] = 0;
}
/**
* Releases all the buffers.
*
* This is equivalent to unpinning all the pinned buffers via calls to
* {@link #unpin(byte)}, followed by calling {@link #release(byte)} on all
* the buffers.
*/
public static final void releaseAll() {
for (byte i = 0; i < NUM_BUFFERS; i++) {
release(i);
}
}
/**
* Checks if a buffer is public, and can be accessed outside the TEM.
*
* @param bufferIndex the buffer file entry whose buffer will be checked
* @return <code>true</code> if the buffer can be accessed by the TEM's client
* application, or <code>false</code> if the buffer contains sensitive
* information
*/
public static final boolean isPublic(byte bufferIndex) {
return bufferIndex != TEMExecution.outBufferIndex &&
bufferIndex != TEMExecution.i_secBufferIndex;
}
/**
* Dumps the state of the TEM memory management module.
*
* This is only useful for driver development / debugging. It should not be
* included in production versions, because it could be used to leak secrets.
*
* @param output the buffer that will receive the stat results
* @param outputOffset the offset of the first byte in the output buffer that
* will receive the stat results
* @return the number of bytes written to the output buffer
*/
public static final short stat(byte[] output, short outputOffset) {
short o = outputOffset;
// Header: 3 shorts indicating available memory of each type.
for (byte memoryType = JCSystem.MEMORY_TYPE_PERSISTENT;
memoryType <= JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT; memoryType++) {
Util.setShort(output, o, JCSystem.getAvailableMemory(memoryType));
o += 2;
}
// Status for each buffer file entry:
// 1 byte - bit 2-0: 0 = EEPROM, 1 = clear on reset, 2 = clear on deselect
// bit 5: 1 = public, 0 = locked
// bit 6: 1 = allocated, 0 = free
// bit 7: 1 = pinned, 0 = unpinned
// 2 bytes - requested buffer size
// 2 bytes - size of actual buffer in the memory layout
for (byte i = 0; i < NUM_BUFFERS; i++) {
output[o] = (byte)(JCSystem.isTransient(buffers[i]) |
(flags[i] & BUFFER_PINNED) | (flags[i] << 6) |
(TEMBuffers.isPublic(i) ? 0x20 : 0));
o++;
Util.setShort(output, o, sizes[i]); o += 2;
Util.setShort(output, o, (short)((byte[])buffers[i]).length); o += 2;
}
return (short)(o - outputOffset);
}
}