From cfefafc337fd1c3d79436ec173cdfc7f08162c2f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:20:58 +0000 Subject: [PATCH] core: file invitation size check (#7069) * core: file invitation size check * comment * comment --- src/Simplex/Chat/Library/Internal.hs | 13 ++++++++--- src/Simplex/Chat/Library/Subscriber.hs | 32 +++++++++++++++----------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 0b19c5a261..086c7e69d5 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -700,7 +700,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI ci <- xftpAcceptRcvFT db cxt user fileId filePath userApproved rfd <- getRcvFileDescrByRcvFileId db fileId pure (ci, rfd) - receiveViaCompleteFD user fileId rfd userApproved cryptoArgs + receiveViaCompleteFD user fileId rfd fileSize userApproved cryptoArgs pure ci (Nothing, Just _fileConnReq) -> throwChatError $ CEException "accepting file via a separate connection is deprecated" -- group & direct file protocol @@ -742,10 +742,17 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI || (rcvInline_ == Just True && fileSize <= fileChunkSize * offerChunks) ) -receiveViaCompleteFD :: User -> FileTransferId -> RcvFileDescr -> Bool -> Maybe CryptoFileArgs -> CM () -receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} userApprovedRelays cfArgs = +receiveViaCompleteFD :: User -> FileTransferId -> RcvFileDescr -> Integer -> Bool -> Maybe CryptoFileArgs -> CM () +receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} expectedFileSize userApprovedRelays cfArgs = when fileDescrComplete $ do rd <- parseFileDescription fileDescrText + let FD.ValidFileDescription FD.FileDescription {size = FD.FileSize encSize, redirect} = rd + redirectSize = maybe 0 (\FD.RedirectFileInfo {size = FD.FileSize s} -> toInteger s) redirect + -- for a redirect, encSize is the description blob and redirectSize the final file; take the larger + rcvSize = max (toInteger encSize) redirectSize + -- 10 MB margin: encryption and chunk-size rounding make the transfer larger than the advertised size + maxRcvSize = min expectedFileSize (toInteger FD.maxFileSizeHard) + toInteger (FD.mb 10 :: Int64) + when (rcvSize > maxRcvSize) $ throwChatError $ CEFileRcvChunk "declared file size exceeds the file invitation size" if userApprovedRelays then receive' rd True else do diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 671b7a53df..95500162c8 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -1894,7 +1894,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = processFDMessage fileId aci fileDescr = do ft <- withStore $ \db -> getRcvFileTransfer db user fileId unless (rcvFileCompleteOrCancelled ft) $ do - (rfd@RcvFileDescr {fileDescrComplete}, ft'@RcvFileTransfer {fileStatus, xftpRcvFile, cryptoArgs}) <- withStore $ \db -> do + (rfd@RcvFileDescr {fileDescrComplete}, ft'@RcvFileTransfer {fileStatus, xftpRcvFile, cryptoArgs, fileInvitation = FileInvitation {fileSize}}) <- withStore $ \db -> do rfd <- appendRcvFD db userId fileId fileDescr -- reading second time in the same transaction as appending description -- to prevent race condition with accept @@ -1902,15 +1902,15 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = pure (rfd, ft') when fileDescrComplete $ toView $ CEvtRcvFileDescrReady user aci ft' rfd case (fileStatus, xftpRcvFile) of - (RFSAccepted _, Just XFTPRcvFile {userApprovedRelays}) -> receiveViaCompleteFD user fileId rfd userApprovedRelays cryptoArgs + (RFSAccepted _, Just XFTPRcvFile {userApprovedRelays}) -> receiveViaCompleteFD user fileId rfd fileSize userApprovedRelays cryptoArgs _ -> pure () processFileInvitation :: Maybe FileInvitation -> MsgContent -> (DB.Connection -> FileInvitation -> Maybe InlineFileMode -> Integer -> ExceptT StoreError IO RcvFileTransfer) -> CM (Maybe (RcvFileTransfer, CIFile 'MDRcv)) - processFileInvitation fInv_ mc createRcvFT = forM fInv_ $ \fInv' -> do + processFileInvitation fInv_ mc createRcvFT = forM fInv_ $ \fInv -> do ChatConfig {fileChunkSize} <- asks config - let fInv@FileInvitation {fileName, fileSize} = mkValidFileInvitation fInv' - inline <- receiveInlineMode fInv (Just mc) fileChunkSize - ft@RcvFileTransfer {fileId, xftpRcvFile} <- withStore $ \db -> createRcvFT db fInv inline fileChunkSize + fInv'@FileInvitation {fileName, fileSize} <- validateFileInvitation fInv + inline <- receiveInlineMode fInv' (Just mc) fileChunkSize + ft@RcvFileTransfer {fileId, xftpRcvFile} <- withStore $ \db -> createRcvFT db fInv' inline fileChunkSize let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP (filePath, fileStatus, ft') <- case inline of Just IFMSent -> do @@ -1927,6 +1927,11 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = mkValidFileInvitation :: FileInvitation -> FileInvitation mkValidFileInvitation fInv@FileInvitation {fileName} = fInv {fileName = FP.makeValid $ FP.takeFileName fileName} + validateFileInvitation :: FileInvitation -> CM FileInvitation + validateFileInvitation fInv@FileInvitation {fileName, fileSize} + | fileSize > 0 = pure $ mkValidFileInvitation fInv + | otherwise = throwChatError $ CEFileSize fileName + messageUpdate :: Contact -> SharedMsgId -> MsgContent -> RcvMessage -> MsgMeta -> Maybe Int -> Maybe Bool -> CM () messageUpdate ct@Contact {contactId} sharedMsgId mc msg@RcvMessage {msgId} msgMeta ttl live_ = do updateRcvChatItem `catchCINotFound` \_ -> do @@ -2332,11 +2337,11 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = -- TODO remove once XFile is discontinued processFileInvitation' :: Contact -> FileInvitation -> RcvMessage -> MsgMeta -> CM () - processFileInvitation' ct fInv' msg@RcvMessage {sharedMsgId_} msgMeta = do + processFileInvitation' ct fInv msg@RcvMessage {sharedMsgId_} msgMeta = do ChatConfig {fileChunkSize} <- asks config - let fInv@FileInvitation {fileName, fileSize} = mkValidFileInvitation fInv' - inline <- receiveInlineMode fInv Nothing fileChunkSize - RcvFileTransfer {fileId, xftpRcvFile} <- withStore $ \db -> createRcvFileTransfer db userId ct fInv inline fileChunkSize + fInv'@FileInvitation {fileName, fileSize} <- validateFileInvitation fInv + inline <- receiveInlineMode fInv' Nothing fileChunkSize + RcvFileTransfer {fileId, xftpRcvFile} <- withStore $ \db -> createRcvFileTransfer db userId ct fInv' inline fileChunkSize let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol} content = ciContentNoParse $ CIRcvMsgContent $ MCFile "" @@ -2347,10 +2352,11 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = -- TODO remove once XFile is discontinued processGroupFileInvitation' :: GroupInfo -> GroupMember -> FileInvitation -> RcvMessage -> UTCTime -> CM () - processGroupFileInvitation' gInfo m fInv@FileInvitation {fileName, fileSize} msg@RcvMessage {sharedMsgId_} brokerTs = do + processGroupFileInvitation' gInfo m fInv msg@RcvMessage {sharedMsgId_} brokerTs = do ChatConfig {fileChunkSize} <- asks config - inline <- receiveInlineMode fInv Nothing fileChunkSize - RcvFileTransfer {fileId, xftpRcvFile} <- withStore $ \db -> createRcvGroupFileTransfer db userId gInfo (Just m) fInv inline fileChunkSize + fInv'@FileInvitation {fileName, fileSize} <- validateFileInvitation fInv + inline <- receiveInlineMode fInv' Nothing fileChunkSize + RcvFileTransfer {fileId, xftpRcvFile} <- withStore $ \db -> createRcvGroupFileTransfer db userId gInfo (Just m) fInv' inline fileChunkSize let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol} content = ciContentNoParse $ CIRcvMsgContent $ MCFile ""