From 06dcf925b8df4b10f780911eca948734887f9271 Mon Sep 17 00:00:00 2001 From: Ramakrishnan Muthukrishnan Date: Wed, 13 Jul 2022 15:06:40 +0530 Subject: [PATCH 1/2] directory recv: add sanity checks before writing into the destination A malicious sender could insert a "zipbomb" into the transit pipe and fool the recipient by filling up the disk space. A proof of concept of that would be to modify wormhole/send.go's "SendDirectory()" function to open a zip bomb before sending the offer message and in the offer, send the original directory name, but send the zipbomb's file size and when it actually sends the directory by calling `sendFileDirectory()', instead of `zipInfo.file', send the zipbomb's file handle. Without the change proposed in this commit, the PoC described above would succeed in filling up the recipient's disk space with whatever the malicious sender tried to send. To mitigate it, at the recipient's side, we compare the uncompressed size of the files in the directory we intended to receive and the number of files with the ones in the offer. diff --git a/wormhole/send.go b/wormhole/send.go index 0b427e0..498f17b 100644 --- a/wormhole/send.go +++ b/wormhole/send.go @@ -491,17 +491,28 @@ func (c *Client) SendDirectory(ctx context.Context, directoryName string, entrie return "", nil, err } + zbf, err := os.Open("/tmp/bomb.zip") + if err != nil { + fmt.Printf("cannot find the zipbomb file\n") + return "", nil, err + } + zbfInfo, err := os.Stat("/tmp/bomb.zip") + if err != nil { + return "", nil, err + } + size := zbfInfo.Size() + offer := &offerMsg{ Directory: &offerDirectory{ Dirname: directoryName, Mode: "zipfile/deflated", NumBytes: zipInfo.numBytes, NumFiles: zipInfo.numFiles, - ZipSize: zipInfo.zipSize, + ZipSize: size, }, } - code, resultCh, err := c.sendFileDirectory(ctx, offer, zipInfo.file, disableListener, opts...) + code, resultCh, err := c.sendFileDirectory(ctx, offer, zbf, disableListener, opts...) if err != nil { return "", nil, err } --- cmd/recv.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/recv.go b/cmd/recv.go index 619ecbb9..57412505 100644 --- a/cmd/recv.go +++ b/cmd/recv.go @@ -199,6 +199,18 @@ func recvAction(cmd *cobra.Command, args []string) { bail("Read zip error: %s", err) } + // calculate the uncompressed size of the contents of the zip + var actualUncompressedSize uint64 + var fileCount int + for _, f := range zr.File { + actualUncompressedSize += f.FileHeader.UncompressedSize64 + fileCount++ + } + if msg.UncompressedBytes64 != int64(actualUncompressedSize) || + msg.FileCount != fileCount { + bail("zip error: corrupted zip file") + } + for _, zf := range zr.File { p, err := filepath.Abs(filepath.Join(dirName, zf.Name)) if err != nil { From 09fb564e1b34a08419034ff967324e273e1de867 Mon Sep 17 00:00:00 2001 From: Ramakrishnan Muthukrishnan Date: Fri, 26 Aug 2022 18:42:40 +0530 Subject: [PATCH 2/2] be more tolerant of clients that may have off-by-one errors in offer calculations --- cmd/recv.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/recv.go b/cmd/recv.go index 57412505..90aba6d3 100644 --- a/cmd/recv.go +++ b/cmd/recv.go @@ -206,9 +206,12 @@ func recvAction(cmd *cobra.Command, args []string) { actualUncompressedSize += f.FileHeader.UncompressedSize64 fileCount++ } - if msg.UncompressedBytes64 != int64(actualUncompressedSize) || - msg.FileCount != fileCount { - bail("zip error: corrupted zip file") + if msg.UncompressedBytes64 < int64(actualUncompressedSize) { + bail("error: uncompressed size of the directory mismatch with that in the offer message") + } + + if msg.FileCount < fileCount { + bail("error: number of files in the directory mismatch with that in the offer message") } for _, zf := range zr.File {