From 3441adaa52b6d4c85e0e2b078d2f7e3471c8255c Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:11:23 +0000 Subject: [PATCH] xftp: limit description replicas, no YAML aliases --- src/Simplex/FileTransfer/Description.hs | 13 ++++++++++++- tests/FileDescriptionTests.hs | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Simplex/FileTransfer/Description.hs b/src/Simplex/FileTransfer/Description.hs index 4cbb08014..2418f3005 100644 --- a/src/Simplex/FileTransfer/Description.hs +++ b/src/Simplex/FileTransfer/Description.hs @@ -217,17 +217,28 @@ instance FilePartyI p => StrEncoding (FileDescription p) where instance StrEncoding AFileDescription where strEncode (AFD fd) = strEncode fd - strDecode = decodeFileDescription <=< first show . Y.decodeEither' + strDecode s + | B.elem '*' s = Left "YAML aliases are not allowed" + | otherwise = decodeFileDescription =<< first show (Y.decodeEither' s) strP = strDecode <$?> A.takeByteString validateFileDescription :: FileDescription p -> Either String (ValidFileDescription p) validateFileDescription fd@FileDescription {size, chunks} | chunkNos /= [1 .. length chunks] = Left "chunk numbers are not sequential" | chunksSize chunks /= unFileSize size = Left "chunks total size is different than file size" + | any (> maxChunkReplicas) replicaCounts = Left "too many chunk replicas" + | sum replicaCounts > maxFileReplicas = Left "too many file replicas" | otherwise = Right $ ValidFD fd where chunkNos = map (\FileChunk {chunkNo} -> chunkNo) chunks chunksSize = foldl' (\(s :: Int64) FileChunk {chunkSize} -> s + fromIntegral (unFileSize chunkSize)) 0 + replicaCounts = map (\FileChunk {replicas} -> length replicas) chunks + +maxChunkReplicas :: Int +maxChunkReplicas = 16 + +maxFileReplicas :: Int +maxFileReplicas = 4096 sharedDescriptionHash :: FileDescription p -> ByteString sharedDescriptionHash FileDescription {size, digest, key, nonce, chunkSize, chunks} = diff --git a/tests/FileDescriptionTests.hs b/tests/FileDescriptionTests.hs index ad4557476..238f83e00 100644 --- a/tests/FileDescriptionTests.hs +++ b/tests/FileDescriptionTests.hs @@ -25,6 +25,8 @@ fileDescriptionTests = do it "parse YAML file description" testParseYAMLFileDescription it "serialize YAML file description" testSerializeYAMLFileDescription it "parse file description" testParseFileDescription + it "reject file description with YAML aliases" testRejectYAMLAliases + it "reject file description with too many replicas" testRejectTooManyReplicas it "serialize file description" testSerializeFileDescription describe "file description URIs" $ do it "round trip file description URI" testFileDescriptionURI @@ -158,6 +160,19 @@ testParseFileDescription = do Left e -> expectationFailure $ show e Right fd -> fd `shouldBe` fileDesc +testRejectYAMLAliases :: IO () +testRejectYAMLAliases = do + (yaml, sizeLine) <- B.breakSubstring "size: " <$> B.readFile fileDescPath + let (header, groups) = B.breakSubstring "- chunks:" yaml + aliased = header <> "- &g\n " <> B.drop 2 groups <> "- *g\n" <> sizeLine + (strDecode aliased :: Either String (FileDescription 'FRecipient)) `shouldBe` Left "YAML aliases are not allowed" + +testRejectTooManyReplicas :: IO () +testRejectTooManyReplicas = do + let FileDescription {chunks} = fileDesc + repeatReplicas c@FileChunk {replicas} = (c :: FileChunk) {replicas = concat $ replicate 9 replicas} + validateFileDescription ((fileDesc :: FileDescription 'FRecipient) {chunks = map repeatReplicas chunks}) `shouldBe` Left "too many chunk replicas" + testSerializeFileDescription :: HasTestEnv => IO () testSerializeFileDescription = withRemoveTmpFile $ do B.writeFile tmpFileDescPath $ strEncode fileDesc