Add file-level locking to prevent race conditions in key persistence
Per-key ReentrantLock prevents concurrent writes to same key file
This commit is contained in:
+25
-1
@@ -7,9 +7,12 @@ import java.io.DataOutputStream
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
||||||
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
|
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
@@ -35,6 +38,13 @@ object GeneratedKeyPersistence {
|
|||||||
private const val FORMAT_VERSION = 1
|
private const val FORMAT_VERSION = 1
|
||||||
private val PERSISTENCE_DIR = File(CONFIG_PATH, "persistent_keys")
|
private val PERSISTENCE_DIR = File(CONFIG_PATH, "persistent_keys")
|
||||||
|
|
||||||
|
// Per-filename locks to prevent concurrent writes to the same key file
|
||||||
|
private val fileLocks = ConcurrentHashMap<String, ReentrantLock>()
|
||||||
|
|
||||||
|
private fun getLockForKey(filename: String): ReentrantLock {
|
||||||
|
return fileLocks.computeIfAbsent(filename) { ReentrantLock() }
|
||||||
|
}
|
||||||
|
|
||||||
fun save(
|
fun save(
|
||||||
keyId: KeyIdentifier,
|
keyId: KeyIdentifier,
|
||||||
keyPair: KeyPair,
|
keyPair: KeyPair,
|
||||||
@@ -48,9 +58,14 @@ object GeneratedKeyPersistence {
|
|||||||
digests: List<Int>,
|
digests: List<Int>,
|
||||||
isAttestationKey: Boolean,
|
isAttestationKey: Boolean,
|
||||||
) {
|
) {
|
||||||
|
val filename = keyFileName(keyId.uid, keyId.alias)
|
||||||
|
val lock = getLockForKey(filename)
|
||||||
|
SystemLogger.debug("[Persistence] Acquiring lock for $filename")
|
||||||
|
lock.lock()
|
||||||
|
try {
|
||||||
|
SystemLogger.debug("[Persistence] Lock acquired for $filename")
|
||||||
runCatching {
|
runCatching {
|
||||||
PERSISTENCE_DIR.mkdirs()
|
PERSISTENCE_DIR.mkdirs()
|
||||||
val filename = keyFileName(keyId.uid, keyId.alias)
|
|
||||||
val finalFile = File(PERSISTENCE_DIR, filename)
|
val finalFile = File(PERSISTENCE_DIR, filename)
|
||||||
val tmpFile = File(PERSISTENCE_DIR, "$filename.tmp")
|
val tmpFile = File(PERSISTENCE_DIR, "$filename.tmp")
|
||||||
|
|
||||||
@@ -94,10 +109,19 @@ object GeneratedKeyPersistence {
|
|||||||
throw IllegalStateException("Failed to atomically rename $tmpFile -> $finalFile")
|
throw IllegalStateException("Failed to atomically rename $tmpFile -> $finalFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify write succeeded - catches disk-full or filesystem errors
|
||||||
|
if (!finalFile.exists() || finalFile.length() < 20) {
|
||||||
|
throw IOException("File write verification failed - possible disk full")
|
||||||
|
}
|
||||||
|
|
||||||
SystemLogger.debug("Persisted key: $keyId")
|
SystemLogger.debug("Persisted key: $keyId")
|
||||||
}.onFailure { e ->
|
}.onFailure { e ->
|
||||||
SystemLogger.error("Failed to persist key $keyId", e)
|
SystemLogger.error("Failed to persist key $keyId", e)
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock()
|
||||||
|
SystemLogger.debug("[Persistence] Lock released for $filename")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(keyId: KeyIdentifier) {
|
fun delete(keyId: KeyIdentifier) {
|
||||||
|
|||||||
Reference in New Issue
Block a user