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:
Enginex0
2026-02-06 00:31:53 +01:00
committed by GKI Builder
parent 88781ff31d
commit c367aa5efc
@@ -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) {