tokuhirom's Blog

Perl Web application に於ける Data::UUID の利用について

本文書について

本文書では Perl における Data::UUID での UUID 生成について議論する。 特に、Linux 環境に於ける Data::UUID->new->create_str の挙動について考察する。

Data::UUID について

Data::UUID は Alexander Golomshtok 氏が開発したモジュールで、メンテナンスされなくなった結果として、現在は rjbs 氏がパッチの適用などの消極的なメンテナンスを実施している。

このモジュールは以下の文言からドキュメントが開始されている。

This module provides a framework for generating v3 UUIDs

しかし、期待に反して、実際にはほとんどのユーザーは v1 UUID を生成している。 Data::UUID->new->create_str を利用するのが一般的なケースであるためだ。

UUIDv1 の構造について

timestamp 60bit, clock sequence 16bit, node 48bit

となっている。

  UUID                   = time-low "-" time-mid "-"
                           time-high-and-version "-"
                           clock-seq-and-reserved
                           clock-seq-low "-" node

↓つまり以下のような形式となる。

   TIMESTAM-PTIM-ESTA-CLOC-NODEID

/tmp/.UUID_NODEID について

Data::UUID->new で nodeid は生成されます。

/tmp/.UUID_NODEID が存在しない場合、nodeid は md5(gettimeofday(), gethostname()) で生成され、 /tmp/.UUID_NODEID に保存されます。 /tmp/.UUID_NODEID が存在する場合、slurp(/tmp/.UUID_NODEID) + $$ の結果が nodeid となります。

このロジックにより、二回目以後の nodeid 生成は process ごとに独立した nodeid になります。

/tmp/.UUID_STATE について

/tmp/.UUID_STATE に clockseq などが保存されます。これにより、プロセスが再起動した場合などにも動作を続けることができる、という設計に見えます。

このファイルの保存タイミングは以下。

しかし、現実的には、あまり意味ないかも。

Data::UUID を web application で利用した場合に発生する可能性がある問題について

以下では pre-fork モデルのウェブアプリケーションで Data::UUID を利用した場合に発生し得る問題について議論する。 (Starlet, Starman などの web server が pre-forking モデルです)

時刻の巻戻り

UUID v1 の特性上、NTP などにより時刻が巻き戻った場合、重複した uuid が発行される可能性があります。 時刻の巻き戻りが発生した場合に発生する重複が許容出来ない場合、Data::UUID の利用は避けるべきでしょう。

clock sequence を初期化するタイミングで初期値を random にするなどの軽減策は取られているものの、問題が発生する可能性はそこそこあります。

local 攻撃への脆弱性

の2つのファイルが生成されるが、tmp directory に生成されるために、悪意のあるユーザーが設置することが可能。

nodeid の生成ロジックの問題

/tmp/.UUID_NODEID がある場合にだけ pid が nodeid に考慮される。 このため、Data::UUID を一度も利用したことがない新しいサーバーの初回起動時などに nodeid が同一の Data::UUID インスタンスを持つ process が複数発生する可能性がある。

AWS などで頻繁にインスタンスの作り直しを実施している場合、この問題が発生しやすいと考えられる。

Data::UUID->new() を fork 前に呼んではいけない

Data::UUID->new の中で nodeid を初期化しているので、fork 前に呼ぶと同一 nodeid ですべての子プロセスで利用されることになり、相手は死ぬ。

srand の呼び出し

Data::UUID->new の中で、現在時刻をシードに srand() を呼び出し、それ以後は rand() をコールしている。。 現在時刻は srand のソースとして使うにはよろしくないので、これは、他の rand() を利用している XS module に悪影響を与えている可能性がある。 (Perl 本体は drand48() を利用しているので問題ない)

対案

完全に random な UUID 生成方法である UUID v4 を利用しとくか、sfujiwara さんが作ってたみたいなやつ使うのがいいんじゃないすかね。 ただし、UUID v4 の生成系を利用する場合、fork 時の seed 最初期化などについては考慮を忘れずに。