shutil.make_archive()が止まらない

Posted on Feb 12, 2025

事の発端

最近、自分の作った画像解析ソフトウェアをStreamlitでWebアプリケーション化して、研究所のプログラミングに明るくないメンバーに使ってもらっています。Streamlitは(簡単なものなら)かなり簡単に作れるため、プロトタイピングに大変便利ですね!しかし、今日ユーザーから、いつまで経っても処理が終わらない、という報告を受けました。ログを見ると、shutil.make_archive()No space left on deviceとだけ言い残して死んでいます。あれ、ホストしてるマシンはSSDのスペースまだかなり余っていたはずだけど、と思ってdfコマンドを叩いてみると、/以下の使用率が100%近くなっていました。

何故か

A: shutil.make_archive('test/out', 'zip', root_dir='test')のように、アーカイブ対象のディレクトリ内でZIPファイルを作ったせいで、再帰的にZIPファイルが巨大化してしまった。

Streamlitを使ったウェブアプリでは、解析結果はtempfile.TemporaryDirectory()で作成した一時ディレクトリ内に保存し、それを最後にzipファイルにまとめてダウンロードする仕組みにしています。ここで、うっかり上記のように、root_dir内にzipファイルを作ることで、デバイスの容量を圧迫してプログラムが死ぬまでzipファイルが膨れ上がる現象が生じてしまったようです。

ちなみに、Unixのzipコマンドで同様の事(zip -r test/out.zip test)を行っても、test/内にout.zipが常識的なファイルサイズで作成されます。今回の一件はかなりしょぼいミスが原因(なぜテストで気が付かなかったのか?)でしたが、その代償が大きすぎるというか、zip爆弾のように何らかの脆弱性にもつながりそうだと思いました。

再現コード

.
├── test
│   └── large_image.png
└── test.py

❯ python -V
Python 3.10.14                                             

large_image.pngには、zipファイルの膨張が実感できるように、それなりに大きいファイルを入れておきます。

# test.py
import shutil
shutil.make_archive("test/test", "zip", "test")
❯ python test.py

以下のように、順調に(?)ファイルサイズが大きくなっていくのがわかります。

❯ du
142704	./test
142708	.
❯ du
326996	./test
327000	.
❯ du
559856	./test
559860	.
❯ du
950052	./test
950056	.