From 9cf29e4a6c289c2e848a9fd4385df90758d78d6e Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 7 Aug 2026 23:49:27 +0400 Subject: [PATCH 1/2] server: prevent concurrent SKEY commands from overwriting queue sender key --- src/Simplex/Messaging/Server/QueueStore/Postgres.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Simplex/Messaging/Server/QueueStore/Postgres.hs b/src/Simplex/Messaging/Server/QueueStore/Postgres.hs index ce1cf25db..09574b91d 100644 --- a/src/Simplex/Messaging/Server/QueueStore/Postgres.hs +++ b/src/Simplex/Messaging/Server/QueueStore/Postgres.hs @@ -341,8 +341,10 @@ instance StoreQueueClass q => QueueStoreClass q (PostgresQueueStore q) where secureQueue st sq sKey = withQueueRec sq "secureQueue" $ \q -> do verify q + -- `verify` checks a snapshot of the queue record that a concurrent command may have + -- invalidated, so the update repeats the check atomically - the loser matches no rows. assertUpdated $ withDB' "secureQueue" st $ \db -> - DB.execute db "UPDATE msg_queues SET sender_key = ? WHERE recipient_id = ? AND deleted_at IS NULL" (sKey, rId) + DB.execute db "UPDATE msg_queues SET sender_key = ? WHERE recipient_id = ? AND deleted_at IS NULL AND (sender_key IS NULL OR sender_key = ?)" (sKey, rId, sKey) atomically $ writeTVar (queueRec sq) $ Just q {senderKey = Just sKey} withLog "secureQueue" st $ \s -> logSecureQueue s rId sKey where From 2e46e56aa87196492e5ed3d944857818a721cf05 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Sat, 8 Aug 2026 00:08:04 +0400 Subject: [PATCH 2/2] wip --- src/Simplex/Messaging/Server/QueueStore/Postgres.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Simplex/Messaging/Server/QueueStore/Postgres.hs b/src/Simplex/Messaging/Server/QueueStore/Postgres.hs index 09574b91d..8738bee99 100644 --- a/src/Simplex/Messaging/Server/QueueStore/Postgres.hs +++ b/src/Simplex/Messaging/Server/QueueStore/Postgres.hs @@ -341,8 +341,6 @@ instance StoreQueueClass q => QueueStoreClass q (PostgresQueueStore q) where secureQueue st sq sKey = withQueueRec sq "secureQueue" $ \q -> do verify q - -- `verify` checks a snapshot of the queue record that a concurrent command may have - -- invalidated, so the update repeats the check atomically - the loser matches no rows. assertUpdated $ withDB' "secureQueue" st $ \db -> DB.execute db "UPDATE msg_queues SET sender_key = ? WHERE recipient_id = ? AND deleted_at IS NULL AND (sender_key IS NULL OR sender_key = ?)" (sKey, rId, sKey) atomically $ writeTVar (queueRec sq) $ Just q {senderKey = Just sKey}